涉及知识:文件操作,动态内存管理,结构体,枚举
设计思路:
1.创建两个结构体,一个存放联系人,一个是通讯录实体,包括有效人数,存放联系人结构体的指针,容量(方便与有效人数对比来开辟空间)
2.初始化结构体,为了实现数据的保留用了文件操作,在每次进入通讯录前(也就是初始化阶段)读取文件内容到指定的变量中,然后给变量通讯录赋值, 初始化函数 init_contact
3.增删查改的实现ADD_contact,Dele_contact,Revise_contact,Search_contact
中途涉及里查找名字的Find_by_name,以及展示通讯的Show_contact
4.退出通讯录的时候记得保存数据,也就是把数据写到文件中 Destroy_contact
整体源文件分为三份,contact.h用来存放所有头文件,函数的声明,宏定义,定义的结构体;
contact.c用来存放函数的实现
test.c是main函数位置,是测试源文件
程序涉及重点:
1.每个传递指针的函数都用到了assert(指针),assert的头文件是<assert.h>,作用是断言空指针,如果传递的是空指针,程序会报错并调到该地方,因为函数涉及的修改不能是空指针,否则程序会崩溃。
2.程序部分地方用了strerror(errno)这种形式,errno是一个错误码,在<errno.h>内,是一个全局变量,作用是程序发生错误时,把错误码记录下来,也就是赋值给errno这个全局,然后我们用printf("相关函数的相关操作::%s\n",strerrno(errno));来把错误原因和错误的地方打印出来,方便查找。
3.再让用户输入值的时候,用了一个变量input,然后我们对input 使用switch来判断该进行什么操作,就会出现case 1进入增加函数,case2进入删除函数这些,代码可读性差,因此用到了枚举
把ADD,Dele这些操作变成常数,然后 case ADD,case Dele就简单明了多了,毕竟文件分开两部分写,有时查bug忘记了1的作用是什么很麻烦,也方便后续维护
4.为了不占用太多的内存,采用了动态开辟内存的方式,开多少用contack.h里面宏定义的DEFAULT_SIZE来控制,然后增加联系人的时候与最大容量比较,如果发现不够用了内存就用realloc函数重新开一次,开大点,不过在删除的时候未实现删除的时候改小内存空间
5.初始化阶段 打开文件把数据读到我们程序中的时候,肯定是要把整个文件读完,那怎么表示文件读取结束
可以采用fread函数,二进制的读,
fread(读到哪里,一次读多大,读多少个,流(就是哪个文件))ps:文件流是流的一种,我们的键盘是标准输入流,显示器是标准输出流
fread有一个好处就是他的返回值是他读取的个数,如果没读取到就是返回0,这是就是文件读完了
所以我们可以用 就是每次读取都放进buffer内部,然后检测一下通讯录够不够大,最后把内容放进通讯录的指针指向的空间内(我们malloc开辟的)
while (fread(&buffer, sizeof(PeoInfo), 1, pr))//fread读取成功返回读取的数量,读取失败返回0
{
check_capacity(con);
con->date[con->sz] = buffer;
con->sz++;
}
以下是头文件-》定义了枚举变量,提高代码可读性,#define宏定义了一些大小,如联系人名字的char name数组开多大这些,方便后续修改,以及两个结构体的声明
//函数声明,头文件,各种define定义
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<errno.h>
#include<assert.h>
enum
{
Exit,
ADD,
Delete,
Search,
Revise,
Show,
Sort,
All_Delete,
};
#define NAME_MAX 15
#define SEX_MAX 5
#define TELE_MAX 12
#define ADDRESS_MAX 25
#define DEFAULT_SIZE 3
typedef struct
{
char name[NAME_MAX];
int age;
char sex[SEX_MAX];
char tele[TELE_MAX];
char address[ADDRESS_MAX];
}PeoInfo;//存储存放人的信息——姓名,年龄,性别,电话,住址
typedef struct
{
PeoInfo* date;//指向一个动态内存开辟出来的空间,存放通讯录实体
int sz;//存放人的个数
int capacity;//开辟的空间最多可以放几个人
}Contact;//通讯录结构体
//初始化的时候从文件中读取数据,根据内容初始化
//通讯录的起始数据(可以保存数据)
void init_contact(Contact* con);
void load_conotact(Contact* con);
void check_capacity(Contact* con);
//增删查改
void ADD_contact(Contact* con);
void Dele_contact(Contact* con);
int Find_by_name(Contact* con, char arr[]);
void Search_contact(Contact* con);
void Revise_contact(Contact* con);
//展示
void Show_contact(Contact* con);
//销毁
void destroy_contact(Contact* con);
//全部删除
void add_delete(Contact* con);
//根据姓名排序
void sort_by_name(Contact* con);
以下是test.c源文件
#include"contact.h"
void menu()
{
printf("****************************\n");
printf("******1.ADD 2.Delete***\n");
printf("******3.Search 4.Revise***\n");
printf("******5.Show 6.Sort*****\n");
printf("******7.All_delete 0.Exit*****\n");
printf("****************************\n");
}
void test()
{
Contact con = { 0 };
init_contact(&con);
int input;
do
{
menu();
printf("请选择\n");
scanf("%d", &input);
switch (input)
{
case ADD:
ADD_contact(&con);
break;
case Delete:
Dele_contact(&con);
break;
case Search:
Search_contact(&con);
break;
case Revise:
Revise_contact(&con);
break;
case Show:
Show_contact(&con);
break;
case Sort:
sort_by_name(&con);
break;
case All_Delete:
all_delete(&con);
break;
case Exit:
printf("退出通讯录\n");
destroy_contact(&con);
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while (input);
}
int main()
{
test();
return 0;
}
以下是contact.c
#include"contact.h"
void check_capacity(Contact* con)
{
assert(con);//防止指针为空
if (con->capacity == con->sz)
{
PeoInfo* tmp = (PeoInfo*)realloc(con->date, sizeof(PeoInfo) * (con->capacity + 2));
if (tmp != NULL)
{
printf("增容成功\n");
con->date = tmp;
con->capacity += 2;
}
else
{
printf("check_capacity->增容:%s\n", strerror(errno));
}
}
}
void Load_contact(Contact* con)
{
assert(con);
FILE* pr = fopen("contact.txt", "rb");
if (pr == NULL)
{
printf("Load_contact open::%s\n", strerror(errno));
return;
}
PeoInfo buffer = { 0 };
while (fread(&buffer, sizeof(PeoInfo), 1, pr))//fread读取成功返回读取的数量,读取失败返回0
{
check_capacity(con);
con->date[con->sz] = buffer;
con->sz++;
}
fclose(pr);
pr = NULL;
}
void init_contact(Contact* con)//初始化通讯录,是有效人数sz=0,
//pf指向一个动态开辟的空间,以及开辟的空间容量
{
assert(con);
con->sz = 0;
PeoInfo* tmp = (PeoInfo*)malloc(sizeof(PeoInfo) * DEFAULT_SIZE);
if (tmp == NULL)
{
printf("init_contact::%s\n", strerror(errno));
return;
}
else
{
con->date = tmp;
}
con->capacity = DEFAULT_SIZE;
Load_contact(con);
}
void ADD_contact(Contact* con)
{
assert(con);
check_capacity(con);
printf("请输入姓名\n");
scanf("%s", con->date[con->sz].name);
printf("请输入年龄\n");
scanf("%d", &con->date[con->sz].age);
printf("请输入性别\n");
scanf("%s", con->date[con->sz].sex);
printf("请输入电话号码\n");
scanf("%s", con->date[con->sz].tele);
printf("请输入地址\n");
scanf("%s", con->date[con->sz].address);
con->sz++;
printf("增加联系人成功\n");
}
int Find_by_name(Contact* con, char name[])
{
assert(con);
for (int i = 0; i < con->sz; i++)
{
if (strcmp(con->date[i].name, name) == 0)
return i;
}
return -1;
}
void Dele_contact(Contact* con)
{
assert(con);
if (con->sz == 0)
{
printf("通讯录为空,不能删除\n");
return;
}
char name[NAME_MAX] = { 0 };
printf("请输入删除者的姓名\n");
scanf("%s", name);
int ret = Find_by_name(con, name);
if (ret + 1)//失败返回-1,+1就变成0,其他为真都会打印,打印ret也就是找到的人的坐标
{
memmove(&con->date[ret], &con->date[ret + 1], sizeof(PeoInfo) * (con->sz - ret));
con->sz--;
printf("删除联系人成功\n");
}
else
{
printf("联系人不存在\n");
}
}
void Search_contact(Contact* con)
{
assert(con);
char name[NAME_MAX] = { 0 };
printf("请输入查找人的姓名\n");
scanf("%s", name);
int ret = Find_by_name(con, name);
if (ret + 1)
{
printf("%-10s %-5d %-5s %-15s %-3s\n",
con->date[ret].name,
con->date[ret].age,
con->date[ret].sex,
con->date[ret].tele,
con->date[ret].address);
}
else
{
printf("不存在该联系人\n");
}
}
void Revise_contact(Contact* con)
{
assert(con);
char name[NAME_MAX] = { 0 };
if (con->sz == 0)
{
printf("通讯录为空,无法修改\n");
return;
}
printf("请输入要修改的人的姓名\n");
scanf("%s", name);
int ret = Find_by_name(con, name);
if (ret + 1)
{
memset(&con->date[ret], 0, sizeof(PeoInfo));//重置空间为0
printf("请重新输入姓名\n");
scanf("%s", con->date[ret].name);
printf("请重新输入年龄\n");
scanf("%d", &con->date[ret].age);
printf("请重新输入性别\n");
scanf("%s", con->date[ret].sex);
printf("请重新输入电话号码\n");
scanf("%s", con->date[ret].tele);
printf("请重新输入地址\n");
scanf("%s", con->date[ret].address);
printf("修改成功\n");
}
else
{
printf("该联系人不存在\n");
}
}
void Show_contact(Contact* con)
{
assert(con);
for (int i = 0; i < con->sz; i++)
{
printf("%-10s %-5d %-5s %-15s %-3s\n",
con->date[i].name,
con->date[i].age,
con->date[i].sex,
con->date[i].tele,
con->date[i].address);
}
}
void destroy_contact(Contact* con)
{
FILE* tmp = fopen("contact.txt", "wb");
if (tmp == NULL)
{
printf("destroy_contact 文件保存::%s\n", strerror(errno));
return;
}
for (int i = 0; i < con->sz; i++)
{
fwrite(&con->date[i], sizeof(PeoInfo), 1, tmp);
}
fclose(tmp);
tmp = NULL;
}
void all_delete(Contact* con)
{
assert(con);
con->capacity = 0;
con->sz = 0;
free(con->date);
printf("删除成功\n");
}
//写个qsort的比较函数的指针
void compare_by_name(const char* p1, const char* p2)
{
assert(p1 && p2);
while (*p1 == *p2 && *p1)
{
p1++;
p2++;
}
return (unsigned int)(*p1) - (unsigned int)(*p2);
}
//直接用库函数qsort
void sort_by_name(Contact* con)
{
assert(con);
qsort(con->date, con->sz, sizeof(PeoInfo), compare_by_name);
printf("排序成功\n");
}