我们先思考一个通讯录都有那些信息,很明显通讯录记录的是人
人有哪些信息呢
这里我就写5个吧,分别是姓名,年龄,电话,性别,地址
然后我们把他们写成一个结构体,最好定义在头文件里,这样在使用的时候更方便
我们还可以把要使用的一些常用的或者要修改的常量定义成枚举,后续有什么要添加的,直接就能在枚举中添加
//枚举的一些关键常量
enum NUM
{NAME = 20, //名字TELE = 12, //电话SEX = 10, //性别ADDR = 100, //地址ADD = 1,DEL = 2,SEARCH = 3,MODIFY = 4,SHOW = 5,SORT = 6,EXIT = 0,CLEAR =7,//菜单//ARRDATA = 100SIZE = 3, //通讯录初始容量大小SIZEADD = 2 //每次扩容的增加量
};
//人的信息
typedef struct peo
{char name[NAME]; //姓名int age; //年龄char tele[TELE]; //电话char sex[SEX]; //性别char addr[ADDR]; //地址
}peo;
这里的枚举内容我直接把参数都写里了,后续在思考的过程就不再改动了
接下来思考,光有一个人的信息是不够的,无法完整表达一个通讯录,当然,由于我们只是简单实现,我们就只再记录通讯录的人员数量和通讯录的容量
把他们简单封装成一个结构体
typedef struct contact
{peo* data; //人的相关数据size_t count; //成员数量size_t capacity; //通讯录容量
}contact;
这里我都是进行了重命名了的,方便后续的使用
这些都是头文件里面要用的,还有一些库函数,我就在这里提前定义了
#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<stdlib.h>
#include<errno.h>
然后我们写主函数
我们考虑通讯录运行一定要有菜单
然后我们用switch语句写分支结构
我们需要用户主动输入选择,所以我们还需要一个变量接收,这个变量还可以用于switch语句的判断
菜单我们可以单独封装一个函数
可以实现增加联系人,删除联系人,查找联系人,修改联系人,显示联系人,按名字排序联系人,清空联系人,和退出保存,将数据保存到文件中,把这些功能都封装成函数
还得创建一个结构体变量用于存放通讯录的数据
还需要一个函数对结构体进行初始化,并把保存在文件中的信息放入到结构体中
于是主函数如下
#include"contact.h"
static void menu() //菜单函数
{printf("****************************************\n");printf("****************************************\n");printf("****************************************\n");printf("*** 1.add 2.del ******\n");printf("*** 3.search 4.modify ******\n");printf("*** 5.show 6.sort ******\n");printf("*** 0.exit 7.clear ******\n");printf("****************************************\n");printf("****************************************\n");printf("****************************************\n");
}
int main()
{int input = 0; //存放菜单选择输入的值contact con; //创建存放通讯录数据的结构体init_contact(&con);//初始化结构体do{menu();printf("请输入要进行的功能\n");scanf("%d", &input); //菜单的选择输入switch (input) //菜单选择的判断逻辑{case ADD:add_contact(&con); //添加函数break;case DEL:del_contact(&con); //删除函数break;case SEARCH:search_contact(&con); //查找函数break;case MODIFY:modify_contact(&con); //修改函数break; case SHOW:show_contact(&con); //显示函数break;case SORT:sort_contact(&con); //排序函数break;case CLEAR:clear_contact(&con); //清空函数break;case EXIT:save_contact(&con); //保存函数destroycontact(&con); //推出后内存的销毁printf("程序退出\n");break;default:printf("选择错误,请重新选择\n");//其他逻辑判断break;}} while (input)//do while的循环判断return 0;
}
主函数别忘了引用头文件
接下来我们封装函数
把函数单独放在一个源文件
#include"contact.h"
//初始化函数
//对于初始化我们不仅要初始化结构体内容,还要把之前保存在文件中的信息放入到结构体中,当然还涉及到一个动态扩容的问题,我们的结构体初始设计的是有容量的,如果里面放的信息多了,还要涉及到扩容的问题,
这些都要设计函数
static void expandCapacity(contact* con) //扩容
{assert(con); //断言防止空指针if (con->capacity == con->count) //容量判断{void* p = 0;p = realloc(con->data, (con->capacity + SIZEADD) * sizeof(peo));//追加信息容量if (NULL == p) //报错判断{printf("expandCapacity::%s\n", strerror(errno));return;}con->capacity += SIZEADD;//修改容量标记con->data = (peo*)p; //修改指针位置(其实没变)printf("增容成功\n");}
}
static int read_conact(contact* con) //读取文件中保存的通讯录信息
{static void expandCapacity(contact * con); //函数声明FILE* p = fopen("contact.txt", "r"); //打开文件if (p == NULL) //报错判断{return 1;}peo a = { 0 };while (fread(&a, sizeof(peo), 1, p) == 1)//循环提取信息,这里取决于保存函数的写法{expandCapacity(con); //扩容con->data[con->count] = a;//赋值(con->count)++;//成员计数加一}fclose(p);p = NULL;//关闭文件,指针空置return 0;
}
void init_contact(contact* con) //初始化函数
{static int read_conact(contact * con);//函数调用声明assert(con);////断言防止空指针con->count = 0;//初始化人数void* p = 0;//内存指针p = calloc(SIZE, sizeof(peo));//创建内存if (NULL == con->data)//报错判断{printf("init_contact:calloc:%s\n", strerror(errno));return;}con->data = (peo*)p;//初始化人信息指针con->capacity = SIZE;//初始化容量if (read_conact(con) == 1)//读取之前的数据{perror("读取旧数据失败");//问题判断return;}
}//初始化我们已经完成了
//接下来我们写添加函数
void add_contact(contact* con)
{static void expandCapacity(contact * con); //函数声明assert(con);//断言防止空指针expandCapacity(con);//扩容判断printf("请输入姓名\n");scanf("%s", (con->data+con->count)->name);printf("请输入年龄\n");scanf("%d", &((con->data + con->count)->age));printf("请输入电话\n");scanf("%s", con->data[con->count].tele);printf("请输入性别\n");scanf("%s", con->data[con->count].sex);printf("请输入地址\n");scanf("%s", con->data[con->count].addr);//这些都是信息是输入con->count++;//计数加一printf("增加成功\n");
}//接下来我们写显示函数
void show_contact(contact* con)
{assert(con);//断言防止空指针int i = 0;printf("%-20s\t%-5s\t%5s\t%12s\t%30s\n", "姓名", "年龄", "电话", "性别", "地址");//显示时方便观察,打印一个标签for (i = 0; i < con->count; i++)//循环打印人员信息{printf("%-20s\t%-5d\t%-5s\t%-12s\t%-30s\n",con->data[i].name,con->data[i].age,con->data[i].tele,con->data[i].sex,con->data[i].addr);}
}//查找函数
//查找函数不仅要能查找,还要能显示,我们分成两个函数,因为删除也需要查找
//查找部分
static int find_by_name(const contact* con,const char* arr) //依据名字查找
{assert(con && arr);//断言防止空指针int i = 0;for (i = 0; i < con->count; i++)//循环遍历查找{if (0 == strcmp(con->data[i].name, arr)){return i;//找到返回下标}}return -1;//找不到返回-1
}//查找
void search_contact(contact* con)
{static int find_by_name(const contact * con, const char* arr);//函数声明assert(con);//断言防止空指针if (con->count == 0)//无联系人情况{printf("无联系人可查找\n");return;}printf("请输入要查找的人的姓名\n");char arr[NAME] = { 0 };scanf("%s", arr);int a = find_by_name(con, arr);//套用查找函数if (a == -1){printf("要查找的人不存在\n");return;}printf("%-20s\t%-5s\t%-5s\t%-12s\t%-30s\n", "姓名", "年龄", "电话", "性别", "地址");printf("%-20s\t%-5d\t%-5s\t%-12s\t%-30s\n",con->data[a].name,con->data[a].age,con->data[a].tele,con->data[a].sex,con->data[a].addr);//显示部分
}//删除
void del_contact(contact* con)
{static int find_by_name(const contact * con, const char* arr);//函数声明assert(con);//断言防止空指针char arr[NAME] = { 0 };if (con->count == 0)//无联系人情况{printf("无联系人可删除\n");return;}printf("请输入要删除人的名字\n");scanf("%s", arr);int a = find_by_name(con,arr);//函数回调if (a == -1){printf("要删除的人不存在\n");return;}for (; a < con->count - 1; a++)//删除本质就是把后面的信息提前,覆盖掉这个下标的信息//当然也还有其他的写法{con->data[a] = con->data[a + 1];}con->count--;printf("删除成功\n");
}//修改函数
//修改也要先找void modify_contact(contact* con)
{static int find_by_name(const contact * con, const char* arr);//函数声明assert(con);//断言防止空指针if (con->count == 0){printf("无联系人可修改\n");//没有的情况判断return;}printf("请输入要修改的人的姓名\n");char arr[NAME] = { 0 };scanf("%s", arr);int a = find_by_name(con, arr);//调用函数if (a == -1){printf("要修改的人不存在\n");return;}printf("请输入修改姓名\n");scanf("%s", con->data[a].name);printf("请输入修改年龄\n");scanf("%d", &(con->data[a].age));printf("请输入修改电话\n");scanf("%s", con->data[a].tele);printf("请输入修改性别\n");scanf("%s", con->data[a].sex);printf("请输入修改地址\n");scanf("%s", con->data[a].addr);printf("修改成功\n");//信息的修改
}//排序函数
//这里我使用了qsort
static int cmp_peo_by_name(const void* e1, const void* e2)//qsort调用的排序函数
{return strcmp(((const peo*)e1)->name, ((const peo*)e2)->name);
}void sort_contact(contact* con)//本体
{static int cmp_peo_by_name(const void* e1, const void* e2);//函数调用assert(con);//断言if (con->count == 0){printf("无联系人可排序\n");//没人的情况return;}printf("正在按姓名排序\n");qsort(con->data, con->count, sizeof(peo), cmp_peo_by_name);//qsort排序printf("排序成功\n");show_contact(con);//这里不声明是因为这些函数都会在头文件中声明,但内些静态函数不会
}//销毁,保存,清空
void destroycontact(contact* con)//释放内存
{assert(con);free(con->data);con->data = NULL;
}void save_contact(const contact* con)//保存信息
{assert(con);FILE* p = fopen("contact.txt", "wb");if (p == NULL){perror("保存失败:save_contact:fopen");return;}fwrite(con->data, sizeof(peo), con->count, p);fclose(p);p = NULL;
}void clear_contact(contact* con)//清空文件信息和当前结构体信息
{assert(con);FILE* p = fopen("contact.txt", "w");if (p == NULL)return;fclose(p);p = NULL;con->count = 0;memset(con->data,0, sizeof(peo));printf("已全部清除\n");
}
最后我们再在头文件中声明一下这些函数,为了调用
//初始化通讯录
void init_contact(contact* con);//增加联系人
void add_contact(contact* con);//显示
void show_contact(contact* con);//删除
void del_contact(contact* con);//查找
void search_contact(contact* con);//修改
void modify_contact(contact* con);//排序
void sort_contact(contact* con);//退出后内存销毁
void destroycontact(contact* con);//退出后的保存
void save_contact(const contact*con);//清空通讯录
void clear_contact(contact*con);
静态函数不用声明,主要和一些商业习惯有关
好了,这样就结束了,比较简陋,旨在练手,大佬勿喷