C语言进阶|【C语言进阶16——通讯录(基础版、动态内存版、文件管理版)】


通讯录三种方式实现(基础版、动态内存版、文件管理版)

  • 前言
  • 1、通讯录是什么?
  • 2、程序框架—基本版
    • 2.1 主函数
    • 2.2 函数test
    • 2.3 函数menu
    • 2.4 初始化通讯录—InitContact
    • 2.5 添加个人信息—addPer
    • 2.6 删除指定联系人—delPer
    • 2.7 查找联系人—searPer
    • 2.8 修改联系人信息—modiPer
    • 2.9 排序联系人信息—sortPer
    • 2.10 打印联系人信息—prinPer
  • 3、头文件 .h
  • 4、通讯录运行
  • 5、动态内存版本
    • 5.1 添加个人信息—addPer 修改1
    • 5.2 初始化通讯录—InitContact 修改1
  • 6、文件操作版本
    • 6.1 保存联系人的信息——savePer
    • 6.2 初始化通讯录—InitContact 修改2
  • 7、三种实现方式的完整代码
  • 总结

前言 本文将实现一个通讯录,对前面所学的指针、字符串库函数、结构体、动态内存、文件操作等内容进行巩固和复习,通讯录有三个版本的实现方式:
  • 基本版
  • 动态内存版
  • 文件操作版
1、通讯录是什么? 百度百科:
C语言进阶|【C语言进阶16——通讯录(基础版、动态内存版、文件管理版)】
文章图片

通讯录主要包括以下6个主要功能:
  1. 添加联系人
  2. 删除联系人
  3. 搜索联系人
  4. 修改联系人
  5. 排序联系人
  6. 打印联系人
2、程序框架—基本版 程序整体的框架可以搬用之前学习【C语言基础6——数组(2)三子棋】、【C语言基础7——数组(3)扫雷】用的框架,这种框架也可以当作一种通用的形式,加以运用。
2.1 主函数
int main()//显得简洁 { test(); return 0; }

2.2 函数test
void test() { int input = 0; //创建通讯录 contact con; //初始化通讯录 InitContact(&con); //传结构体地址 do { menu(); printf("请选择操作: ==> "); scanf("%d", &input); switch (input) { case add://1、添加联系人 addPer(&con); break; case del://2、删除联系人 delPer(&con); break; case search://3、搜索联系人 searPer(&con); break; case modify://4、修改联系人 modiPer(&con); break; case sort://5、排序联系人 sortPer(&con); break; case print://6、打印联系人 prinPer(&con); break; case exit: break; default: break; } } while (input); //选零就退出循环了 }

2.3 函数menu
void menu() { printf("\n"); printf("******************************\n"); printf("****1.addPer 2.delPer******\n"); printf("****3.searPer 4.modiPer*****\n"); printf("****5.sortPer 6.prinPer*****\n"); printf("****0.exit ******************\n"); printf("******************************\n"); printf("\n"); }

2.4 初始化通讯录—InitContact
//初始化通讯录 void InitContact(contact* pc) { assert(pc); pc->sz = 0; //信息个数初始化为0 //通过字符串库函数,初始化结构体数组data, //pc->data是数组首元素地址,后面是整个结构体数组大小,整个都初始化为0 memset(pc->data, 0, sizeof(pc->data)); //字符串库函数 }

2.5 添加个人信息—addPer
//1、添加个人信息 void addPer(contact* pc) { assert(pc); if (pc->sz==MAX) { printf("通讯录已经满了,无法添加新的联系人!\n"); return; } //个数没满的话,开始录入个人信息 printf("请输入名字 ==> "); scanf("%s", pc->data[pc->sz].name); //结构体其他的数组名都是地址 printf("请输入性别 ==> "); scanf("%s", pc->data[pc->sz].sex); printf("请输入年龄 ==> "); scanf("%d", &(pc->data[pc->sz].age)); //年龄不是地址,这里必须取地址 printf("请输入号码 ==> "); scanf("%s", pc->data[pc->sz].phone); printf("请输入住址 ==> "); scanf("%s", pc->data[pc->sz].addr); pc->sz++; //每当录入一个人的信息时,人数就增加1 printf("个人信息添加成功!\n"); }

2.6 删除指定联系人—delPer
//在通讯录中查找联系人,找到返回下标 int findPer(const contact* pc, char name[]) { assert(pc); //遍历整个data数组,查找名字 for (int i = 0; i < pc->sz; i++) {//字符串库函数,字符串比较,相同,则函数返回0 if (0==strcmp(pc->data[i].name,name)) {//找到信息人的名字时,返回下标 return i; } } return -1; //遍历都找不到,就返回-1 } //2、删除指定联系人 void delPer(contact* pc) { assert(pc); //检查信息个数 if (pc->sz == 0) { printf("通讯录已经空了,无法删除联系人!\n"); return; } //如果还有,则删除指定联系人 //1、指定联系人 char name[NAME_MAX] = {0}; //名字的字符串 printf("请输入需要删除的联系人的姓名 ==> "); scanf("%s", name); //2、开始查找这个联系人 int res = findPer(pc, name); //传首字符的地址 if (res==-1) { printf("要删除的联系人不存在!\n"); return; //直接返回,后面的语句不需要执行了 } //3、找到后删除 for (int j = res; j < pc->sz-1; j++) {//删除就是后面的信息往前移动,覆盖前面一个信息的内容 pc->data[j] = pc->data[j + 1]; } pc->sz--; //总的信息个数减少1个 printf("指定联系人删除成功!\n"); }

2.7 查找联系人—searPer
//3、查找个人信息 void searPer(contact* pc) { assert(pc); //1、指定联系人 char name[NAME_MAX] = { 0 }; printf("请输入需要查找的联系人的姓名 ==> "); scanf("%s", name); //2、开始查找这个联系人 int res = findPer(pc, name); //找到这个人的信息,就返回下标 if (res == -1)//没找到就,打印出来 { printf("要删除的联系人不存在!\n"); return; //直接返回,后面的语句不需要执行了 } printf("%-20s %-5s %-5s %-12s %-30s\n", "姓名", "性别", "年龄", "号码", "住址"); printf("%-20s %-5s %-5d %-12s %-30s\n", pc->data[res].name, pc->data[res].sex, pc->data[res].age, pc->data[res].phone, pc->data[res].addr); }

2.8 修改联系人信息—modiPer
//4、修改联系人信息 void modiPer(contact* pc) { assert(pc); //1、指定联系人 char name[NAME_MAX] = { 0 }; printf("请输入需要查找的联系人的姓名 ==> "); scanf("%s", name); //2、开始查找这个联系人 int res = findPer(pc, name); //找到这个人的信息,就返回下标 if (res == -1)//没找到就,打印出来 { printf("要删除的联系人不存在!\n"); return; //直接返回,后面的语句不需要执行了 } printf("已经查找到联系人的姓名,请重新输入个人信息 ==> \n"); //查找到后,开始修改,重新录入个人信息 printf("请输入名字 ==> "); scanf("%s", pc->data[res].name); //结构体其他的数组名都是地址 printf("请输入性别 ==> "); scanf("%s", pc->data[res].sex); printf("请输入年龄 ==> "); scanf("%d", &(pc->data[res].age)); //年龄不是地址,这里必须取地址 printf("请输入号码 ==> "); scanf("%s", pc->data[res].phone); printf("请输入住址 ==> "); scanf("%s", pc->data[res].addr); printf("联系人个人信息修改成功!\n"); }

2.9 排序联系人信息—sortPer
//对结构体数组里的结构体成员的姓名进行排序 int cmp_data_name(const void* p1, const void* p2) { return strcmp(((struct perInfo*)p1)->name, ((struct perInfo*)p2)->name); }//5、对联系人个人信息排序 void sortPer(contact* pc) { assert(pc); //检查信息个数 ,首先判断通讯录联系人有几人 if (pc->sz == 0) { printf("通讯录是空的,无法排序!\n"); return; } if (pc->sz == 1)//直接将信息打印出来 { printf("%-20s %-5s %-5d %-12s %-30s\n", pc->data[0].name, pc->data[0].sex, pc->data[0].age, pc->data[0].phone, pc->data[0].addr); return; } //信息的个数是2个及以上,开始排序 //按姓名进行升序排序 //结构体数组的地址联系人信息的个数每一个人信息的大小 函数地址 qsort(pc->data, pc->sz, sizeof(pc->data[0]), cmp_data_name); //打印出来排序后的结果 printf("对姓名排序后的结果:\n"); printf("%-20s %-5s %-5s %-12s %-30s\n", "姓名", "性别", "年龄", "号码", "住址"); for (int i = 0; i < pc->sz; i++) {//%-20s 是左对齐 printf("%-20s %-5s %-5d %-12s %-30s\n", pc->data[i].name, pc->data[i].sex, pc->data[i].age, pc->data[i].phone, pc->data[i].addr); } }

2.10 打印联系人信息—prinPer
//6、打印个人信息 void prinPer(const contact* pc) { assert(pc); printf("通讯录现有的所有联系人的信息 ==> \n"); //在屏幕上打印一行提示信息: 姓名 性别 年龄 号码 住址 printf("%-20s %-5s %-5s %-12s %-30s\n", "姓名", "性别", "年龄", "号码", "住址"); for (int i = 0; i < pc->sz; i++) {//%-20s 是左对齐 printf("%-20s %-5s %-5d %-12s %-30s\n", pc->data[i].name, pc->data[i].sex, pc->data[i].age, pc->data[i].phone, pc->data[i].addr); } }

3、头文件 .h
#include #include #include//类型的声明,宏定义、结构体,枚举 #define MAX 1000 //定义数组中元素的个数//个人信息,所需的最大上限 #define NAME_MAX 20 //名字 #define SEX_MAX 5 //性别 #define PHONE_MAX 12 //手机号码 #define ADDR_MAX 30//住址enum option//操作选择 { exit,//默认值从0开始 add, del, search, modify, sort, print }; //结构体类型,每个人信息:姓名、性别、年龄、号码、住址 typedef struct perInfo { char name[NAME_MAX]; char sex[SEX_MAX]; int age; char phone[PHONE_MAX]; char addr[ADDR_MAX]; }perInfo; //结构体类型,结构体数组都是人的信息、录入信息人的个数 typedef struct contact { perInfo data[MAX]; //结构体类型的数组,1000个人的信息,每个元素是一个结构体类型 int sz; //记录此时通讯录的已有信息个数 }contact; //函数的声明//初始化通讯录 void InitContact(contact* pc); //1、添加个人信息 void addPer(contact* pc); //2、删除个人信息 void delPer(contact* pc); //3、查找个人信息 void searPer(contact* pc); //4、修改联系人信息 void modiPer(contact* pc); //5、对联系人个人信息排序 void sortPer(contact* pc); //6、打印个人信息 void prinPer(const contact* pc);

4、通讯录运行 运行结果见下图,基本实现了预期功能:
C语言进阶|【C语言进阶16——通讯录(基础版、动态内存版、文件管理版)】
文章图片

5、动态内存版本 在基本版的通讯录中,信息个数固定式1000的,这里有点问题:
  • 有时存放的信息个数少,1000就有点浪费内存了
  • 当存放的信息个数超过1000时,没法增加信息存储容量
在此基础上引入动态内存版本,让存储信息的容量随着真实存放的个数,实时改变。
5.1 添加个人信息—addPer 修改1
//动态内存添加 void checkCapacity(contact* pc) { assert(pc); //当目前存放的信息个数 与 当前能够存放的最大容量相同时,进行扩容 if (pc->sz == pc->capacity) {//每次增加2个 存放信息 perInfo* tmp = (perInfo*)realloc(pc->data, (pc->capacity + 2) * sizeof(perInfo)); if (tmp == NULL) { perror("checkCapacity:realloc"); return; } pc->data = https://www.it610.com/article/tmp; pc->capacity += 2; printf("扩容成功\n"); } } //释放动态内存 void freePer(contact* pc) { free(pc->data); pc->data = https://www.it610.com/article/NULL; pc->capacity = 0; pc->sz = 0; printf("销毁成功\n"); } void addPer(contact* pc) { assert(pc); //基本版 /*if (pc->sz==MAX) { printf("通讯录已经满了,无法添加新的联系人!\n"); return; }*/ //动态增容 checkCapacity(pc); //个数没满的话,开始录入个人信息 printf("请输入名字 ==> "); scanf("%s", pc->data[pc->sz].name); //结构体其他的数组名都是地址 printf("请输入性别 ==> "); scanf("%s", pc->data[pc->sz].sex); printf("请输入年龄 ==> "); scanf("%d", &(pc->data[pc->sz].age)); //年龄不是地址,这里必须取地址 printf("请输入号码 ==> "); scanf("%s", pc->data[pc->sz].phone); printf("请输入住址 ==> "); scanf("%s", pc->data[pc->sz].addr); pc->sz++; //每当录入一个人的信息时,人数就增加1 printf("个人信息添加成功!\n"); }

5.2 初始化通讯录—InitContact 修改1
void InitContact(contact* pc) { assert(pc); pc->sz = 0; //信息个数初始化为0 //一开始,能存储的信息个数定为 3 pc->capacity = INIT_SZ; pc->data = https://www.it610.com/article/(perInfo*)malloc(pc->capacity * sizeof(perInfo)); if (pc->data =https://www.it610.com/article/= NULL) { perror("InitContact:malloc"); return; } //通过字符串库函数,初始化结构体数组data, //pc->data是数组首元素地址,后面是整个结构体数组大小,整个都初始化为0 memset(pc->data, 0, sizeof(pc->data)); //字符串库函数 }

6、文件操作版本 【C语言进阶|【C语言进阶16——通讯录(基础版、动态内存版、文件管理版)】】基础版、动态内存版在程序结束时,输入的个人信息也销毁了。因此使用文件操作将个人信息存储起来,当下一次调用程序时,再将文件加载进来。
6.1 保存联系人的信息——savePer
void savePer(contact* pc) { //打开文件 FILE* pf = fopen("contact.dat", "wb"); if (pf == NULL) { perror("savePer:"); return; } //写文件 for (int i = 0; i < pc->sz; i++) {//输出, 从程序向文件 输出 fwrite(pc->data + i, sizeof(perInfo), 1, pf); } //关闭文件 fclose(pf); pf = NULL; }

6.2 初始化通讯录—InitContact 修改2
//初始化通讯录 void InitContact(contact* pc) { assert(pc); pc->sz = 0; //信息个数初始化为0 //一开始,能存储的信息个数定为 3 pc->capacity = INIT_SZ; pc->data = https://www.it610.com/article/(perInfo*)malloc(pc->capacity * sizeof(perInfo)); if (pc->data =https://www.it610.com/article/= NULL) { perror("InitContact:malloc"); return; } //通过字符串库函数,初始化结构体数组data, //pc->data是数组首元素地址,后面是整个结构体数组大小,整个都初始化为0 memset(pc->data, 0, sizeof(pc->data)); //字符串库函数 //加载信息到通讯录中 loadPer(pc); }//将文件导入到 程序里 void loadPer(contact* pc) { //打开文件 FILE* pf = fopen("contact.dat", "rb"); if (pf == NULL) { perror("loadPer:"); return; } //读文件从文件中读数据 到 程序中 perInfo tmp = { 0 }; //输入从文件 读到 程序 while (fread(&tmp,sizeof(perInfo),1,pf)) { checkCapacity(pc); //扩容 pc->data[pc->sz] = tmp; //将信息一个个 读取 到程序中 pc->sz++; //信息个数+1 } //关闭文件 fclose(pf); pf = NULL; }

7、三种实现方式的完整代码 通讯录三种方式实现(基础版、动态内存版、文件管理版)的完整代码放在 gitee 中:
通讯录三种实现方式完整代码
总结 通讯录三种版本的代码,使用了结构体传地址作为参数、指针、字符串库函数、结构体、动态内存、文件操作等已经学到的知识。整体框架和三子棋、扫雷是雷同的,在局部添加新的代码。随着学习的深入,学到新的知识将会更新代码。
下一篇开始学习程序编译相关的知识点。

    推荐阅读