通讯录的实现
- 一.静态版本
- 1.所需要的功能
- 2.大致菜单
- 3.创建通讯录
- 4.增加联系人
- 5.显示联系人
- 6.查找联系人
- 7.删除联系人
- 8.修改联系人
- 9.按名字排序
- 10.源代码
- 二.动态版本
- 1.修改data数组
- 2.修改初始化
- 3.修改增加函数
- 4.完善通讯录
- 5.源代码
- 三.文件版本
- 1.退出前保存
- 2.进入后读取
- 3.源代码
一.静态版本
1.所需要的功能
对于通讯录来说,我们需要它实现以下几个功能。
1.人的信息:姓名+年龄+性别+电话+地址。
2.可以存放100个人的信息。
功能:
1>增加联系人。
2>删除联系人。
3>查找指定联系人信息。
4>修改指定联系人信息。
5>显示所有联系人信息。
6>按名字排序。
接下来分为三个模块,test.c->用来测试通讯录;contact.c->通讯录主体部分;contact.h->用于函数的声明。
2.大致菜单
#include"contact.h" void menu() { printf("********************************\n"); printf("****** 1.增加 2.删除 ******\n"); printf("****** 3.查找 4.修改 ******\n"); printf("****** 5.展示 6.排序 ******\n"); printf("*******0.退出 ******\n"); printf("********************************\n"); } int main() { int input = 0; do { menu(); scanf("%d", &input); switch (input) { case 1: break; case 2: break; case 3: break; case 4: break; case 5: break; case 6: break; case 0: printf("退出通讯录。\n"); break; default: printf("输入无效,请重新输入。\n"); break; } } while (input); return 0; }
这一块很简单,就不再多说。菜单是属于测试部分,所以我将它放入test.c文件里。
3.创建通讯录
对于一个人,肯定有多方面的因数,所以将其封装在一个结构体内。这里使用到typedef,如果不太熟悉可以看看这篇博客typedef的使用
接下来,再封装一个结构体里面存放100个人的信息和当前人的个数。
接着在主函数里使用该结构体创建通讯录。然后进行初始化。
初始化函数在contact.h里声明。
在contact.c里实现,需要使用到memset,如果不太明白可以看看这篇博客memset如何使用
4.增加联系人
在contact.h里声明。
在contact.c里面实现。
5.显示联系人
在contact.h里声明。
在contact.c里实现。这里使用到\t,向后隔开8个字节,用于分隔。同时例如%-20s是右边隔开20个字节,也就是进行左对齐。
6.查找联系人
我们发现无论是查找,删除还是修改都需要先找到这个人。所以我们干脆将寻找封装成一个函数来使用。我们通过名字来查找(需要使用strcmp,如果不熟悉可以看看这篇博客strcmp的使用)
完成后正式进行查找。
老规矩,现在contact.h里进行声明。ps:前面的find不用声明是因为find只在contact.c里使用。
在contact.c里实现。
7.删除联系人
这里采用一种最简单的方法,就是从后往前依次覆盖。首先找到该名字的位置,然后依次将后面的往前挪。
在contact.h里声明。
在contact.c里实现。
8.修改联系人
老规矩在contact.h里进行声明。
在contact.c里实现。修改其实就是重新录入,找到位置,重新写一遍就好了。
9.按名字排序
下面排序需要使用到qsort函数。如果不太熟悉可以看看这篇博客qsort函数
老规矩在contact.h里声明。
在contact.c里实现。
好了,以上就是通讯录静态版本的实现功能啦,下面是源代码。
10.源代码
test.c
#include"contact.h" void menu() { printf("********************************\n"); printf("****** 1.增加 2.删除 ******\n"); printf("****** 3.查找 4.修改 ******\n"); printf("****** 5.展示 6.排序 ******\n"); printf("*******0.退出 ******\n"); printf("********************************\n"); } int main() { int input = 0; //创建通讯录 Contact con;//该结构体包含100个人的信息和已填充人的个数 //初始化通讯录 InitContact(&con);//结构体传参 do { menu(); printf("请选择:"); scanf("%d", &input); switch (input) { case 1: AddContact(&con); break; case 2: Dlete(&con); break; case 3: Search(&con); break; case 4: Modify(&con); break; case 5: ShowContact(&con); break; case 6: Order(&con); break; case 0: printf("退出通讯录。\n"); break; default: printf("输入无效,请重新输入。\n"); break; } } while (input); return 0; }
contact.h
#include #include #include //人的信息 typedef struct PeoInfo { char name[20]; int age; char sex[5]; char addr[30]; char tele[12]; }PeoInfo; typedef struct Contact { PeoInfo data[100];//存放人的信息 int sz;//当前已经放的信息个数 }Contact;//同理,这里也进行了重命名 //声明初始化函数 void InitContact(Contact* pc); //声明增加联系人函数 void AddContact(Contact*pc); //声明显示联系人函数 void ShowContact(const Contact*pc); //声明查找函数 void Search(const Contact*pc);//查找依然不会改变,所以加上const //声明删除函数 void Dlete(Contact*pc); //声明修改函数 void Modify(Contact*pc); //声明排序函数 void Order(Contact*pc);
contact.c
#include"contact.h" //初始化函数的实现 void InitContact(Contact* pc) { pc->sz = 0; memset(pc->data, 0, sizeof(pc->data)); } //增加联系人 void AddContact(Contact* pc) { assert(pc);//一个好的习惯判断是否为空指针(当然不加也没影响) if (pc->sz == 100) { printf("通讯录已满,无法添加。\n"); return; } //开始添加信息 printf("请输入名字:"); scanf("%s", pc->data[pc->sz].name); printf("请输入年龄:"); scanf("%d", &(pc->data[pc->sz].age)); printf("请输入性别:"); scanf("%s", pc->data[pc->sz].sex); printf("请输入地址:"); scanf("%s", pc->data[pc->sz].addr); printf("请输入电话:"); scanf("%s", pc->data[pc->sz].tele); pc->sz++;//别忘了添加完一个人后向后走一步 } //显示联系人 void ShowContact(const Contact* pc)//因为显示不会改变元素,所以最好加上const { assert(pc); printf("%-20s\t%-4s\t%-5s\t%-20s\t%-12s\n", "名字", "年龄", "性别", "地址", "电话");//提示 for (int i = 0; i sz; i++) { printf("%-20s\t%-4d\t%-5s\t%-20s\t%-12s\n", pc->data[i].name, pc->data[i].age, pc->data[i].sex, pc->data[i].addr, pc->data[i].tele); } } //找到特定联系人的位置 int FindByName(const Contact* pc, char name[])//两个参数,一个是通讯录里存的名字,一个是你要查找的名字 { for (int i = 0; i sz; i++) { if (strcmp(pc->data[i].name, name) == 0) { return i;//找到返回下标 } } return -1;//没找到,返回-1 } //查找 void Search(const Contact* pc) { assert(pc); char name[20] = { 0 }; printf("请输入要查找的名字:"); scanf("%s", name); int pos = FindByName(pc, name); if (pos == -1) { printf("查无此人。\n"); return; } //找到了,打印信息 printf("%-20s\t%-4d\t%-5s\t%-20s\t%-12s\n", pc->data[pos].name, pc->data[pos].age, pc->data[pos].sex, pc->data[pos].addr, pc->data[pos].tele); } //删除 void Dlete(Contact* pc) { assert(pc); char name[20] = { 0 }; printf("请输入要删除的名字:"); scanf("%s", name); int dle = FindByName(pc, name);//找到位置 if (dle == -1) { printf("查无此人。\n"); return; } for (int i = dle; i sz-1; i++)//从后往前覆盖,同时-1避免越界 { pc->data[i] = pc->data[i + 1]; } pc->sz--;//删除完成后别忘了个数-1 printf("删除成功\n"); } //修改 void Modify(Contact* pc) { assert(pc); char name[20] = { 0 }; printf("请输入要修改的名字:"); scanf("%s", name); int ret = FindByName(pc, name); if (-1 == ret) { printf("查无此人\n"); return; } printf("请输入名字:"); scanf("%s", pc->data[ret].name); printf("请输入年龄:"); scanf("%d", &(pc->data[ret].age)); printf("请输入性别:"); scanf("%s", pc->data[ret].sex); printf("请输入地址:"); scanf("%s", pc->data[ret].addr); printf("请输入电话:"); scanf("%s", pc->data[ret].tele); printf("修改成功\n"); } //排序 int cmp(const void* e1, const void* e2) { return strcmp(((PeoInfo*)e1)->name, ((PeoInfo*)e2)->name); } void Order(Contact* pc) { assert(pc); qsort(pc->data, pc->sz, sizeof(pc->data[0]), cmp); printf("排序成功\n"); }
二.动态版本
动态版本主要在静态版本的基础上加入扩容功能,如果通讯录已存满,那么会自动扩大通讯录,下面设置初始容量为三个人,如果不满足每次扩容两个人。需要的前置知识是动态内存,如果不太了解,可以看看这篇博客动态内存
1.修改data数组
上面的静态版本是直接开辟了一个100人的数组,很明显已经固定。所以应当将这100个人改为动态的。
静态版本
动态版本
2.修改初始化
在静态版本的初始化里直接使用的memset指定具体的字节数来初始化。在动态版本里不知道具体的大小,很明显不能这么暴力。下面使用calloc对data进行开辟空间的同时完成初始化。
静态版本
动态版本
3.修改增加函数
在静态版本中,如果一直增加联系人到通讯录满后直接给提升:通讯录已满,不能再加入。在动态版本里需要修改,当放满通讯录后自动扩容。
静态版本
动态版本
4.完善通讯录
上面的修改大致功能没有问题了,但还有个缺陷是在开辟空间时我们在堆区上开辟的,所以在程序结束时应当主动free,避免内存泄漏。
在test.c里
在contact.h里
在contact.c里
演示
5.源代码
test.c
#include"contact.h" void menu() { printf("********************************\n"); printf("****** 1.增加 2.删除 ******\n"); printf("****** 3.查找 4.修改 ******\n"); printf("****** 5.展示 6.排序 ******\n"); printf("*******0.退出 ******\n"); printf("********************************\n"); } int main() { int input = 0; //创建通讯录 Contact con;//该结构体包含100个人的信息和已填充人的个数 //初始化通讯录 InitContact(&con);//结构体传参 do { menu(); printf("请选择:"); scanf("%d", &input); switch (input) { case 1: AddContact(&con); break; case 2: Dlete(&con); break; case 3: Search(&con); break; case 4: Modify(&con); break; case 5: ShowContact(&con); break; case 6: Order(&con); break; case 0: Destroy(&con); printf("退出通讯录。\n"); break; default: printf("输入无效,请重新输入。\n"); break; } } while (input); return 0; }
contact.h
#include #include #include #include #define DEFAULT_SZ 3 //初始大小 #define INC_SZ 2 //每次扩容 //人的信息 typedef struct PeoInfo { char name[20]; int age; char sex[5]; char addr[30]; char tele[12]; }PeoInfo; //动态版本 typedef struct Contact { PeoInfo *data;//指向存放人信息的空间 int sz;//当前已经放的信息个数 int capacity;//当前通讯录的最大容量 }Contact; //声明初始化函数 void InitContact(Contact* pc); //声明销毁函数 void Destroy(Contact* pc); //声明增加联系人函数 void AddContact(Contact*pc); //声明显示联系人函数 void ShowContact(const Contact*pc); //声明查找函数 void Search(const Contact*pc);//查找依然不会改变,所以加上const //声明删除函数 void Dlete(Contact*pc); //声明修改函数 void Modify(Contact*pc); //声明排序函数 void Order(Contact*pc);
contact.c
#include"contact.h" //初始化函数的实现,动态版本 void InitContact(Contact* pc) { pc->sz = 0; PeoInfo* ptr = (PeoInfo*)calloc(DEFAULT_SZ,sizeof(PeoInfo));//开辟初始空间 if (ptr == NULL) { printf("%s", strerror(errno)); return; }//判断是否空间开辟成功 pc->data = ptr; pc->capacity = DEFAULT_SZ;//初始容量为3 } //销毁 void Destroy(Contact* pc) { free(pc->data);//由于整个data都是在堆区上开辟的,所以直接free pc->data = NULL; pc->capacity = 0; pc->sz = 0; pc = NULL; } void check_capacity(Contact* pc) { if (pc->sz == pc->capacity)//如果容量已满 { //增加容量 PeoInfo* ptr =(PeoInfo*)realloc(pc->data, (pc->capacity + INC_SZ) * sizeof(PeoInfo));//调整空间大小 if (ptr == NULL) { printf("%s", strerror(errno)); } pc->data = ptr;//把新空间的起始位置传给data pc->capacity += INC_SZ;//最大容量加INC_SZ printf("增容成功\n"); } } //增加联系人 void AddContact(Contact* pc) { assert(pc); check_capacity(pc);//检查容量,判断是否需要增容 //开始添加信息 printf("请输入名字:"); scanf("%s", pc->data[pc->sz].name); printf("请输入年龄:"); scanf("%d", &(pc->data[pc->sz].age)); printf("请输入性别:"); scanf("%s", pc->data[pc->sz].sex); printf("请输入地址:"); scanf("%s", pc->data[pc->sz].addr); printf("请输入电话:"); scanf("%s", pc->data[pc->sz].tele); pc->sz++;//别忘了添加完一个人后向后走一步 } //显示联系人 void ShowContact(const Contact* pc)//因为显示不会改变元素,所以最好加上const { assert(pc); printf("%-20s\t%-4s\t%-5s\t%-20s\t%-12s\n", "名字", "年龄", "性别", "地址", "电话");//提示 for (int i = 0; i sz; i++) { printf("%-20s\t%-4d\t%-5s\t%-20s\t%-12s\n", pc->data[i].name, pc->data[i].age, pc->data[i].sex, pc->data[i].addr, pc->data[i].tele); } } //找到特定联系人的位置 int FindByName(const Contact* pc, char name[])//两个参数,一个是通讯录里存的名字,一个是你要查找的名字 { for (int i = 0; i sz; i++) { if (strcmp(pc->data[i].name, name) == 0) { return i;//找到返回下标 } } return -1;//没找到,返回-1 } //查找 void Search(const Contact* pc) { assert(pc); char name[20] = { 0 }; printf("请输入要查找的名字:"); scanf("%s", name); int pos = FindByName(pc, name); if (pos == -1) { printf("查无此人。\n"); return; } //找到了,打印信息 printf("%-20s\t%-4d\t%-5s\t%-20s\t%-12s\n", pc->data[pos].name, pc->data[pos].age, pc->data[pos].sex, pc->data[pos].addr, pc->data[pos].tele); } //删除 void Dlete(Contact* pc) { assert(pc); char name[20] = { 0 }; printf("请输入要删除的名字:"); scanf("%s", name); int dle = FindByName(pc, name);//找到位置 if (dle == -1) { printf("查无此人。\n"); return; } for (int i = dle; i sz-1; i++)//从后往前覆盖,同时-1避免越界 { pc->data[i] = pc->data[i + 1]; } pc->sz--;//删除完成后别忘了个数-1 printf("删除成功\n"); } //修改 void Modify(Contact* pc) { assert(pc); char name[20] = { 0 }; printf("请输入要修改的名字:"); scanf("%s", name); int ret = FindByName(pc, name); if (-1 == ret) { printf("查无此人\n"); return; } printf("请输入名字:"); scanf("%s", pc->data[ret].name); printf("请输入年龄:"); scanf("%d", &(pc->data[ret].age)); printf("请输入性别:"); scanf("%s", pc->data[ret].sex); printf("请输入地址:"); scanf("%s", pc->data[ret].addr); printf("请输入电话:"); scanf("%s", pc->data[ret].tele); printf("修改成功\n"); } //排序 int cmp(const void* e1, const void* e2) { return strcmp(((PeoInfo*)e1)->name, ((PeoInfo*)e2)->name); } void Order(Contact* pc) { assert(pc); qsort(pc->data, pc->sz, sizeof(pc->data[0]), cmp); printf("排序成功\n"); }
三.文件版本
我们可以发现前面两个版本在运行结束后通讯录里的信息就自动删除了,很明显是不符合我们的预期的。这是因为上面的数据都储存在内存中,而内存在一次程序运行结束后会重置,所以我们需要将其写在硬盘上(文件里),才能保证信息的存储。(需要的前置知识是文件操作,如果不太熟悉可以看看这篇博客文件操作)
1.退出前保存
test.c里
contact.h里
contact.c里
注意文件可以建在任意位置,但如果不是在当前路径下,在fopen时记得将该文件的绝对路径写上(也就是几盘,第几文件夹…)
2.进入后读取
原来初始化通讯录时是直接初始化为0,现在初始化时将文件里的信息加载到通讯录里。
3.源代码
test.c
#include"contact.h" void menu() { printf("********************************\n"); printf("****** 1.增加 2.删除 ******\n"); printf("****** 3.查找 4.修改 ******\n"); printf("****** 5.展示 6.排序 ******\n"); printf("*******0.退出 ******\n"); printf("********************************\n"); } int main() { int input = 0; //创建通讯录 Contact con;//该结构体包含100个人的信息和已填充人的个数 //初始化通讯录 InitContact(&con);//结构体传参 do { menu(); printf("请选择:"); scanf("%d", &input); switch (input) { case 1: AddContact(&con); break; case 2: Dlete(&con); break; case 3: Search(&con); break; case 4: Modify(&con); break; case 5: ShowContact(&con); break; case 6: Order(&con); break; case 0: SaveContact(&con); Destroy(&con); printf("退出通讯录。\n"); break; default: printf("输入无效,请重新输入。\n"); break; } } while (input); return 0; }
contact.h
#include #include #include #include #define DEFAULT_SZ 3 //初始大小 #define INC_SZ 2 //每次扩容 //人的信息 typedef struct PeoInfo { char name[20]; int age; char sex[5]; char addr[30]; char tele[12]; }PeoInfo; //动态版本 typedef struct Contact { PeoInfo *data;//指向存放人信息的空间 int sz;//当前已经放的信息个数 int capacity;//当前通讯录的最大容量 }Contact; //声明初始化函数 void InitContact(Contact* pc); //声明销毁函数 void Destroy(Contact* pc); //声明增加联系人函数 void AddContact(Contact*pc); //声明显示联系人函数 void ShowContact(const Contact*pc); //声明查找函数 void Search(const Contact*pc);//查找依然不会改变,所以加上const //声明删除函数 void Dlete(Contact*pc); //声明修改函数 void Modify(Contact*pc); //声明排序函数 void Order(Contact*pc); //声明保存函数 void SaveContact(Contact*pc); //声明加载通讯录函数 void LoadContact(Contact*pc);
contact.c
#include"contact.h" //初始化函数的实现 void InitContact(Contact* pc) { pc->sz = 0; PeoInfo* ptr = (PeoInfo*)calloc(DEFAULT_SZ,sizeof(PeoInfo));//开辟初始空间 if (ptr == NULL) { printf("%s", strerror(errno)); return; }//判断是否空间开辟成功 pc->data = ptr; pc->capacity = DEFAULT_SZ;//初始容量为3 //加载数据到通讯录 LoadContact(pc); } //销毁 void Destroy(Contact* pc) { free(pc->data);//由于整个data都是在堆区上开辟的,所以直接free pc->data = NULL; pc->capacity = 0; pc->sz = 0; pc = NULL; } void check_capacity(Contact* pc) { if (pc->sz == pc->capacity)//如果容量已满 { //增加容量 PeoInfo* ptr =(PeoInfo*)realloc(pc->data, (pc->capacity + INC_SZ) * sizeof(PeoInfo));//调整空间大小 if (ptr == NULL) { printf("%s", strerror(errno)); } pc->data = ptr;//把新空间的起始位置传给data pc->capacity += INC_SZ;//最大容量加INC_SZ printf("增容成功\n"); } } //增加联系人 void AddContact(Contact* pc) { assert(pc); check_capacity(pc);//检查容量,判断是否需要增容 //开始添加信息 printf("请输入名字:"); scanf("%s", pc->data[pc->sz].name); printf("请输入年龄:"); scanf("%d", &(pc->data[pc->sz].age)); printf("请输入性别:"); scanf("%s", pc->data[pc->sz].sex); printf("请输入地址:"); scanf("%s", pc->data[pc->sz].addr); printf("请输入电话:"); scanf("%s", pc->data[pc->sz].tele); pc->sz++;//别忘了添加完一个人后向后走一步 } //显示联系人 void ShowContact(const Contact* pc)//因为显示不会改变元素,所以最好加上const { assert(pc); printf("%-20s\t%-4s\t%-5s\t%-20s\t%-12s\n", "名字", "年龄", "性别", "地址", "电话");//提示 for (int i = 0; i sz; i++) { printf("%-20s\t%-4d\t%-5s\t%-20s\t%-12s\n", pc->data[i].name, pc->data[i].age, pc->data[i].sex, pc->data[i].addr, pc->data[i].tele); } } //找到特定联系人的位置 int FindByName(const Contact* pc, char name[])//两个参数,一个是通讯录里存的名字,一个是你要查找的名字 { for (int i = 0; i sz; i++) { if (strcmp(pc->data[i].name, name) == 0) { return i;//找到返回下标 } } return -1;//没找到,返回-1 } //查找 void Search(const Contact* pc) { assert(pc); char name[20] = { 0 }; printf("请输入要查找的名字:"); scanf("%s", name); int pos = FindByName(pc, name); if (pos == -1) { printf("查无此人。\n"); return; } //找到了,打印信息 printf("%-20s\t%-4d\t%-5s\t%-20s\t%-12s\n", pc->data[pos].name, pc->data[pos].age, pc->data[pos].sex, pc->data[pos].addr, pc->data[pos].tele); } //删除 void Dlete(Contact* pc) { assert(pc); char name[20] = { 0 }; printf("请输入要删除的名字:"); scanf("%s", name); int dle = FindByName(pc, name);//找到位置 if (dle == -1) { printf("查无此人。\n"); return; } for (int i = dle; i sz-1; i++)//从后往前覆盖,同时-1避免越界 { pc->data[i] = pc->data[i + 1]; } pc->sz--;//删除完成后别忘了个数-1 printf("删除成功\n"); } //修改 void Modify(Contact* pc) { assert(pc); char name[20] = { 0 }; printf("请输入要修改的名字:"); scanf("%s", name); int ret = FindByName(pc, name); if (-1 == ret) { printf("查无此人\n"); return; } printf("请输入名字:"); scanf("%s", pc->data[ret].name); printf("请输入年龄:"); scanf("%d", &(pc->data[ret].age)); printf("请输入性别:"); scanf("%s", pc->data[ret].sex); printf("请输入地址:"); scanf("%s", pc->data[ret].addr); printf("请输入电话:"); scanf("%s", pc->data[ret].tele); printf("修改成功\n"); } //排序 int cmp(const void* e1, const void* e2) { return strcmp(((PeoInfo*)e1)->name, ((PeoInfo*)e2)->name); } void Order(Contact* pc) { assert(pc); qsort(pc->data, pc->sz, sizeof(pc->data[0]), cmp); printf("排序成功\n"); } //保存 void SaveContact(Contact* pc) { //第一步打开文件 FILE* pf = fopen("contact.txt", "wb"); if (pf == NULL) { perror("fopen:"); } else { //写数据,一个人一个人的写 //data数组里一个元素就是一个结构体,也就是一个人 for (int i = 0; i sz; i++) { fwrite(pc->data + i, sizeof(PeoInfo), 1, pf); } fclose(pf); pf = NULL; printf("保存成功\n"); } } //加载通讯录 void LoadContact(Contact* pc) { //打开文件 FILE*pf=fopen("contact.txt", "rb"); if (pf == NULL) { perror("LoadCContact:"); } else { //读取文件 PeoInfo tmp = { 0 }; int i = 0; while (fread(&tmp, sizeof(PeoInfo), 1, pf))//一次读一个人,如果读取完毕返回0 { //写数据 //由于在退出时销毁了通讯录,所以回到初始大小,如果存储的数据打于初始容量,需要扩容 check_capacity(pc);//检查是否需要扩容,如果需要就扩容 pc->data[i] = tmp; pc->sz++; i++; } fclose(pf); pf = NULL; } }