数据结构 - 线性表第四篇:C 语言通讯录优化升级全记录(踩坑 + 思考)
前言基础版通讯录虽然跑通了但每次用都挺折磨输入汉字死循环、修改时要全部重输、只能按姓名查找... 这篇博客会记录我按优先级一步步优化的全过程包括每一个功能的需求来源、遇到的问题、解决方案和代码修改所有优化都是我当时实际遇到痛点后才做的。(5/25晚测试时发现修改名字出错为保证文章连续性本文代码不再修改请您谅解修改过程请转至C 语言通讯录补坑篇终版遗留 Bug 修复解决修改姓名输入错乱问题-CSDN博客)一、优化优先级排序我把问题按严重程度排了个序先解决最致命的最高优先级输入汉字死循环直接导致程序无法使用次高优先级修改时必须全部重输体验最差功能扩展支持按电话 / 地址查找最常用的功能缺失细节优化性别输入简化手动输入男女太麻烦终极修复查找功能的连环坑输入名字报错、明明存在却找不到bug 修复自定义修改无效代码健壮性消除所有编译警告和逻辑漏洞二、第一优先级彻底解决输入汉字死循环这是我当时最头疼的问题查了资料才搞懂原理。2.1 问题根源当用scanf(%d, input)读取数字时如果输入数字scanf 读取成功返回 1数字从缓冲区移除如果输入汉字 / 字母scanf 读取失败返回 0汉字永远留在缓冲区下一次循环scanf 又会读到同一个汉字再次失败→无限循环2.2 解决方案缓冲区清理函数写一个通用函数清空输入缓冲区里的所有脏数据// Contacts.c 顶部新增 void ClearBuff() { int c; // 读到换行或EOF为止避免缓冲区为空时阻塞 while ((c getchar()) ! \n c ! EOF); }2.3 在所有 scanf 失败的地方调用// 主菜单 if (scanf(%d, input) ! 1) { ClearBuff(); // 清空脏数据 printf(输入错误请输入数字\n); continue; } // 后面所有的scanf(%d)都加上这个判断效果输入汉字 / 字母后只会提示一次错误然后重新等待输入再也不会死循环了三、次高优先级修改时回车跳过不修改当时修改联系人的体验极差只想改个电话却要把姓名、性别、年龄全部重输一遍。我想实现不想改哪一项直接按回车就保留原来的值。3.1 问题分析scanf(%s)会自动跳过空格和换行无法读取空输入。所以必须用fgets它可以读取一行包括空行。3.2 实现 InputSkip 工具函数#define MAX_INPUT 200 void InputSkip(char* buf, int len, char* oldVal) { // 先清空之前的缓冲区因为上一个scanf会留下换行 ClearBuff(); char temp[MAX_INPUT]; // 读取一行输入 fgets(temp, len, stdin); // 去掉fgets读进来的换行符 temp[strcspn(temp, \n)] \0; // 如果输入为空直接回车保留原来的值 if (strlen(temp) 0) { strncpy(buf, oldVal, len - 1); buf[len - 1] \0; // 强制加终止符防止越界 } // 否则用新值覆盖 else { strncpy(buf, temp, len - 1); buf[len - 1] \0; } }3.3 修改 ModDat 函数void ModDat(contact* Con) { printf(提示不需要修改的项直接回车即可\n); char name[NAME_Max]; printf(请输入要修改的联系人姓名); scanf(%s, name); int index FinNa(Con, name); if (index 0) { printf(联系人不存在\n); return; } printf(新姓名); InputSkip(Con-a[index].name, NAME_Max, Con-a[index].name); printf(新性别); char buf[20] {0}; InputSkip(buf, 20, Con-a[index].Gender); strcpy(Con-a[index].Gender, buf); printf(新年龄); InputSkip(Con-a[index].age, AGE_Max, Con-a[index].age); printf(新电话); InputSkip(Con-a[index].Tel, TEL_Max, Con-a[index].Tel); printf(新住址); InputSkip(Con-a[index].Address, ADDRESS_Max, Con-a[index].Address); printf(修改成功\n); }效果修改时哪一项不想改直接按回车就行体验提升了 10 倍四、功能扩展多条件查找默认姓名 二级菜单原来只能按姓名查找有时候忘记姓名只记得电话根本找不到人。我想实现默认直接按姓名查找最常用不用选菜单输入 1 弹出二级菜单支持按电话 / 地址查找支持退出查找不用强制输入内容4.1 新增按电话和地址查找函数// 按电话查找 int FinTel(contact* Con, char Tell[]) { for (int i 0; i Con-size; i) { if (strcmp(Con-a[i].Tel, Tell) 0) { printf(找到联系人下标%d\n, i); return i; } } printf(未找到联系人\n); return -1; } // 按住址查找 int FinAdd(contact* Con, char Add[]) { for (int i 0; i Con-size; i) { if (strcmp(Con-a[i].Address, Add) 0) { printf(找到联系人下标%d\n, i); return i; } } printf(未找到联系人\n); return -1; }这里代码明显冗余三个查找函数除了比较的字段不同其他完全一样。但当时作为新手先实现功能再说重构是后面的事。4.2 第一版 Menu2有 bug后面会重写int Menu2(contact* Con) { int choose 0; int index -1; char search[100] {0}; // 初始化消除字符串无终止符警告 printf(默认按姓名查找输入1切换更多方式); if (scanf(%d, choose) ! 1) { ClearBuff(); printf(输入错误\n); return -1; } // 输入1弹出二级菜单 if (choose 1) { printf(1.姓名 2.电话 3.住址 0.退出\n); printf(请选择); if (scanf(%d, choose) ! 1) { ClearBuff(); printf(输入错误\n); return -1; } switch (choose) { case 1: printf(请输入姓名); scanf(%s, search); index FinNa(Con, search); break; case 2: printf(请输入电话); scanf(%s, search); index FinTel(Con, search); break; case 3: printf(请输入住址); scanf(%s, search); index FinAdd(Con, search); break; case 0: printf(退出查找\n); return -2; // 用-2标记用户主动退出 default: printf(选项错误\n); return -1; } } // 不输入1默认按姓名查找 else { printf(请输入姓名); scanf(%s, search); index FinNa(Con, search); } return index; }4.3 关键逻辑修复区分用户退出和未找到一开始我用-1同时表示 “未找到” 和 “用户退出”导致用户退出查找时程序会提示 “联系人不存在”逻辑错误。解决用-2标记用户主动退出-1标记未找到调用处分别判断// 删除函数 void DelCon(contact* Con) { int index Menu2(Con); if (index -2) { printf(已取消删除\n); return; } if (index 0) { printf(联系人不存在\n); return; } SLDeleteSpecify(Con, index); printf(删除成功\n); } // 查找和修改函数同理都加上这两个判断五、细节优化性别输入智能转换原来要手动输入 “男”“女”容易输错成 “男男”“女女”。我想实现输入0→自动变成 “女”输入1→自动变成 “男”输入其他文字→原样保存支持 “保密”“中性” 等自定义5.1 添加联系人优化void AddCon(contact* Con) { UseDat data; printf(请输入姓名); scanf(%s, data.name); char buf[20] {0}; // 初始化消除警告 printf(请输入性别(0女 1男 也可自定义)); scanf(%s, buf); if (strcmp(buf, 0) 0) strcpy(data.Gender, 女); else if (strcmp(buf, 1) 0) strcpy(data.Gender, 男); else strcpy(data.Gender, buf); // 后面不变 }5.2 修改联系人优化// ModDat函数中性别部分 char buf[20] {0}; printf(新性别(0女 1男 也可自定义)); InputSkip(buf, 20, Con-a[index].Gender); if (strcmp(buf, 0) 0) strcpy(Con-a[index].Gender, 女); else if (strcmp(buf, 1) 0) strcpy(Con-a[index].Gender, 男); else if(strlen(buf) 0) // 空输入保留原值 strcpy(Con-a[index].Gender, buf);效果输入 0 或 1 就能快速选择性别也支持自定义非常方便。六、终极踩坑查找功能的连环崩溃本以为到这里就大功告成了结果查找功能又出了两个致命 bug整整调试了一天才解决。6.1 第一个坑输入名字直接报错现象提示 “默认按姓名查找输入 1 切换方式”我直接输入 “王一”结果提示 “输入错误联系人不存在”原因我写的 Menu2 逻辑完全反了代码里先调用scanf(%d, choose)要求必须输入数字一输中文就会读取失败直接返回 - 1。排查过程我在代码里加了很多 printf 调试才发现原来我写的 “默认按姓名查找” 根本是骗人的必须先输数字不能直接输名字。解决方案重写 Menu2 逻辑先读取字符串判断是不是 “1” 再切换方式// 重写后的Menu2第一版修复 int Menu2(contact* Con) { int choose 0; char search[100] { 0 }; printf(默认按姓名查找直接输入名字输入1切换方式); if (scanf(%d, choose) 1) { ClearBuff(); if (choose 1) { printf(1.姓名 2.电话 3.住址 0.退出\n); printf(请选择); if (scanf(%d, choose) ! 1) { ClearBuff(); printf(输入错误\n); return -1; } ClearBuff(); switch (choose) { case 1: printf(请输入姓名); fgets(search, sizeof(search), stdin); search[strcspn(search, \n)] 0; index FinNa(Con, search); break; // 其他case同理 } return index; } } // 输入了名字直接查找 ClearBuff(); printf(请输入姓名); fgets(search, sizeof(search), stdin); search[strcspn(search, \n)] 0; return FinNa(Con, search); }6.2 第二个坑明明存在却提示 Not found现象添加了联系人 “王一”查找时输入 “王一”结果提示 “Not found”原因fgets会把回车一起读进去我查的是 “王一 \n”但存的是 “王一”strcmp直接判定不相等排查过程我用 printf 打印了输入的字符串和存储的字符串发现输入的字符串后面多了一个看不见的换行符。解决方案在所有查找函数开头加一行代码去掉字符串最后的换行符// 姓名查找 int FinNa(contact* Con, char name[]) { // 必须加这一行去掉换行符 name[strcspn(name, \n)] 0; for (int i 0; i Con-size; i) { if (strcmp(Con-a[i].name, name) 0) { printf(Found it,subscript %d\n, i); return i; } } printf(Not found\n); return -1; } // 电话查找和住址查找同理都加这一行 int FinTel(contact* Con, char Tell[]) { Tell[strcspn(Tell, \n)] 0; // 后面不变 } int FinAdd(contact* Con, char Add[]) { Add[strcspn(Add, \n)] 0; // 后面不变 }6.3 最终完美版 Menu2结合上面两个修复我写出了最终版的 Menu2彻底解决了所有查找问题int Menu2(contact* Con) { int choose 0; char search[100] { 0 }; printf(默认按姓名查找直接输入名字输入1切换方式); ClearBuff(); fgets(search, sizeof(search), stdin); search[strcspn(search, \n)] \0; // 如果输入的是1进入切换菜单 if (strcmp(search, 1) 0) { printf(1.姓名 2.电话 3.住址 0.退出\n); printf(请选择); scanf(%d, choose); ClearBuff(); switch (choose) { case 1: printf(请输入姓名); fgets(search, sizeof(search), stdin); search[strcspn(search, \n)] \0; return FinNa(Con, search); case 2: printf(请输入电话); fgets(search, sizeof(search), stdin); search[strcspn(search, \n)] \0; return FinTel(Con, search); case 3: printf(请输入住址); fgets(search, sizeof(search), stdin); search[strcspn(search, \n)] \0; return FinAdd(Con, search); case 0: printf(退出查找\n); return -2; default: printf(选项错误\n); return -1; } } // 否则直接按姓名查找 return FinNa(Con, search); }七、修复性别自定义修改无效现象修改联系人时性别输入 “保密”结果展示时还是原来的性别。原因我在修改函数里自定义性别没有真正赋值到结构体里原来的代码里InputSkip读取了输入到buf但只有输入 0 或 1 的时候才会赋值输入其他内容的时候什么都没做。解决方案完善性别修改的逻辑空输入保留原值自定义内容正确赋值// 修复后的性别修改逻辑 char buf[20] { 0 }; printf(请输入新性别(0女 1男 直接回车保留旧值)); InputSkip(buf, sizeof(buf), Con-a[index].Gender); // 空输入直接回车→ 不修改保留原来的值 if (strlen(buf) 0) { // 什么都不做 } // 输入 0 → 女 else if (strcmp(buf, 0) 0) { strcpy(Con-a[index].Gender, 女); } // 输入 1 → 男 else if (strcmp(buf, 1) 0) { strcpy(Con-a[index].Gender, 男); } // 输入其他 → 直接保存输入内容 else { strcpy(Con-a[index].Gender, buf); }八、最后一步消除所有编译警告8.1 警告 1“可能没有为字符串添加零终止符”原因局部 char 数组未初始化里面是随机垃圾值编译器担心没有\0。解决所有 char 数组定义时都加 {0}初始化// 错误 char buf[20]; char search[100]; // 正确 char buf[20] {0}; char search[100] {0};8.2 警告 2“函数必须返回值”原因一开始写 Menu2 时有些分支没有 return。解决确保所有路径都有返回值上面的 Menu2 已经修复。8.3 警告 3“SLTYPE_PRINT_FMT 未使用 / 格式错误”原因SeqList.h 里定义了#define SLTYPE_PRINT_FMT %d但 SLType 是结构体无法用 % d 打印。解决把这个宏改成空字符串// SeqList.h #define SLTYPE_PRINT_FMT 九、最终效果和学习总结经过整整一周的调试和优化我的通讯录终于变成了一个真正好用的工具1. 输入汉字不会死循环2.修改时回车跳过不修改3.支持按姓名 / 电话 / 地址查找4.性别输入智能转换0 女 1 男 自定义5. 查找功能 100% 正常不会出现找不到的情况6. 性别自定义修改正常7.数据持久化正常8. 增删改查全功能正常。9.1 整个过程的收获对 C 语言字符串处理的理解加深了终于搞懂了换行符、字符串终止符的重要性以及 scanf 和 fgets 的区别对输入输出缓冲区的理解彻底通透了之前一直觉得缓冲区很神秘现在终于知道为什么会出现输入吞掉、无限循环的问题学会了调试技巧从只会重启程序到会用 printf 加断点调试一步步定位问题理解了用户体验的重要性一个功能能不能用是一回事好不好用是另一回事很多细节优化能极大提升体验接受了代码的不完美新手先实现功能再考虑优化和重构不要一开始就追求完美9.2 后续可以继续优化的方向把年龄改成 int 类型支持按年龄排序增加手机号格式校验11 位数字增加重复联系人校验支持批量删除和导入导出重构三个查找函数消除代码冗余十、最终完整可运行代码分文件1. SeqList.h顺序表头文件#pragma once #define _CRT_SECURE_NO_WARNINGS #includestdlib.h #includestdio.h #includeassert.h #includeContact.h typedef struct UserData SLType; #define SLTYPE_PRINT_FMT typedef struct SeqList { SLType* a; int size;//有效数据个数 int capacity;//储存空间 }SL; //初始化和销毁 void SLInit(SL* ps); void Destroy(SL* ps); void Print(SL* ps); //扩容 void SLExp(SL* ps); //头部操作 void PushFront(SL* ps, SLType x);//头部插入 void SLPopFront(SL* ps);//头部删除 //尾部操作 void PushBack(SL* ps, SLType x);//尾部插入 void SLPopBack(SL* ps);//尾部删除 //在指定位置之前插入/删除数据 void PushSpecify(SL* ps,int pos, SLType x);//插入 void SLDeleteSpecify(SL* ps,int pos);//删除 //修改指定位置内容 void Modifyspecified(SL* ps, int pos, SLType x);2. SeqList.c顺序表实现#includeSeqList.h void SLInit(SL* ps) { assert(ps); ps-a NULL; ps-capacity 0; ps-size 0; } void Destroy(SL* ps) { assert(ps); if (ps-a) { free(ps-a); ps-a NULL; ps-capacity 0; ps-size 0;//有效数据个数 } } void SLExp(SL* ps) { assert(ps); if (ps-capacity ps-size) { int Newspace ps-capacity 0 ? 4 : 2 * ps-capacity; SLType* Newps (SLType*)realloc(ps-a, Newspace * sizeof(SLType)); if (Newps NULL) { perror(Failed to apply for space); exit(1); } else { ps-a Newps; ps-capacity Newspace; } } } void PushBack(SL* ps, SLType x)//尾部插入 { assert(ps); SLExp(ps);//判断是否扩容 ps-a[ps-size] x; } void PushFront(SL* ps, SLType x)//头部插入 { assert(ps); SLExp(ps);//判断是否扩容 for (int i ps-size; i 0; i--) { ps-a[i] ps-a[i - 1]; } ps-a[0] x; ps-size; } void SLPopFront(SL* ps)//头部删除 { assert(ps); assert(ps-size);//数据不能为空 for (int i 0; i ps-size-1; i) { ps-a[i] ps-a[i 1]; } ps-size--; } void SLPopBack(SL* ps)//尾部删除 { assert(ps); assert(ps-size);//数据不能为空 ps-size--; } //在指定位置之前插入/删除数据 void PushSpecify(SL* ps,int pos, SLType x)//插入 { assert(ps); assert(pos 0 pos ps-size); SLExp(ps);//判断是否扩容 for (int i ps-size; i pos; i--) { ps-a[i] ps-a[i - 1]; } ps-a[pos] x; ps-size; } void SLDeleteSpecify(SL* ps,int pos)//删除 { assert(ps); assert(pos 0 pos ps-size); assert(ps-size);//数据不能为空 for (int i pos; i ps-size - 1; i) { ps-a[i] ps-a[i 1]; } ps-size--; } void Modifyspecified(SL* ps, int pos, SLType x)//修改指定位置内容 { assert(ps); assert(pos 0 pos ps-size); ps-a[pos] x; } void Print(SL* ps)//打印 { assert(ps); for (int i 0; i ps-size; i) { printf(SLTYPE_PRINT_FMT , ps-a[i]); } printf(\n); }3. Contact.h通讯录头文件#pragma once #define NAME_Max 100//姓名 #define Gender_Max 4//性别 #define AGE_Max 4//年龄 #define TEL_Max 15//电话 #define ADDRESS_Max 100//住址 #define _CRT_SECURE_NO_WARNINGS typedef struct UserData { char name[NAME_Max];//姓名 char Gender[Gender_Max];//性别 char age[AGE_Max];//年龄 char Tel[TEL_Max];//电话 char Address[ADDRESS_Max];//住址 }UseDat; void ClearBuff(void); // 清空直到换行 typedef struct SeqList contact; //初始化通讯录 void InitAdd(contact* Con); //添加联系人 void AddCon(contact* Con); //删除联系人 void DelCon(contact* Con); //展示通讯录 void ShoCon(contact* Con); //查找联系人 void FinCon(contact* Con); //修改联系人数据 void ModDat(contact* Con); //销毁通讯录数据 void DesDat(contact* Con);4. Contacts.c通讯录核心实现#define _CRT_SECURE_NO_WARNINGS #includeSeqList.h #includeContact.h #include string.h #define MAX_INPUT 200 void ClearBuff() { int c; while ((c getchar()) ! \n c ! EOF); } void InputSkip(char* buf, int len, char* oldVal) { int c; while ((c getchar()) ! \n c ! EOF); char temp[MAX_INPUT]; int read_len (len MAX_INPUT) ? len : MAX_INPUT; fgets(temp, read_len, stdin); temp[strcspn(temp, \n)] \0; if (strlen(temp) 0) { strncpy(buf, oldVal, len - 1); // 只拷贝len-1个字符 buf[len - 1] \0; // 强制终止符避免越界 } else { strncpy(buf, temp, len - 1); buf[len - 1] \0; } } //查找函数 int FinNa(contact* Con, char name[])//查找姓名 { name[strcspn(name, \n)] \0; for (int i 0; i Con-size; i) { if (strcmp(Con-a[i].name, name) 0) { printf(Found it,subscript %d\n, i); return i; } } printf(Not found\n); return -1; // 没找到返回-1 } int FinTel(contact* Con, char Tell[])//查找电话 { Tell[strcspn(Tell, \n)] \0; for (int i 0; i Con-size; i) { if (strcmp(Con-a[i].Tel, Tell) 0) { printf(Found it,subscript %d\n, i); return i; } } printf(Not found\n); return -1; // 没找到返回-1 } int FinAdd(contact* Con, char Add[])//查找姓名 { Add[strcspn(Add, \n)] \0; for (int i 0; i Con-size; i) { if (strcmp(Con-a[i].Address, Add) 0) { printf(Found it,subscript %d\n, i); return i; } } printf(Not found\n); return -1; // 没找到返回-1 } // 查找菜单默认按姓名输入1弹出二级选择 int Menu2(contact* Con) { int choose 0; char search[100] { 0 }; printf(默认按姓名查找输入1切换方式); // 直接读取姓名不再判断数字 ClearBuff(); fgets(search, sizeof(search), stdin); search[strcspn(search, \n)] \0; // 如果输入的是 1进入切换菜单 if (strcmp(search, 1) 0) { printf(1.姓名 2.电话 3.住址 0.退出\n); printf(请选择); scanf(%d, choose); ClearBuff(); switch (choose) { case 1: printf(请输入姓名); fgets(search, sizeof(search), stdin); search[strcspn(search, \n)] \0; return FinNa(Con, search); case 2: printf(请输入电话); fgets(search, sizeof(search), stdin); search[strcspn(search, \n)] \0; return FinTel(Con, search); case 3: printf(请输入住址); fgets(search, sizeof(search), stdin); search[strcspn(search, \n)] \0; return FinAdd(Con, search); case 0: return -2; default: return -1; } } // 否则直接按姓名查找 return FinNa(Con, search); } void LoadCon(contact* Con) { // 尝试打开文件不存在就直接返回 FILE* pf fopen(contact.txt, rb); if (pf NULL) { printf(没有找到历史数据文件创建新的通讯录\n); return; } UseDat data; while (fread(data, sizeof(UseDat), 1, pf) 1) { PushBack(Con, data); } printf(江湖四海联络册...加载完成\n); fclose(pf); } //初始化通讯录 void InitAdd(contact* Con) { SLInit(Con); LoadCon(Con); } //添加联系人 void AddCon(contact* Con) { UseDat data; //写入文件数据 printf(请输入姓名); scanf(%s, data.name);/*scanf(%s, data.Gender); */ //printf(请输入性别); char buf[20] { 0 }; printf(请输入性别(0女 1男 也可自定义输入));// 性别0女 1男其他自定义 scanf(%s, buf); if (strcmp(buf, 0) 0) { strcpy(data.Gender, 女); } else if (strcmp(buf, 1) 0) { strcpy(data.Gender, 男); } else { strcpy(data.Gender, buf);// 输入不是0/1保存自己输入的内容 } printf(请输入年龄); scanf(%s, data.age); printf(请输入电话); scanf(%s, data.Tel); printf(请输入住址); scanf(%s, data.Address); PushBack(Con, data); printf(添加成功\n); } //删除联系人 void DelCon(contact* Con) { int index Menu2(Con); // 调用查找菜单获取下标 if (index -2) { printf(已取消删除\n); return; } if (index 0) { printf(联系人不存在删除失败\n); return; } // 3. 找到执行删除 SLDeleteSpecify(Con, index); printf(删除成功\n); } //展示通讯录 void ShoCon(contact* Con) { printf(%-10s %-10s %-10s %-15s %-20s\n, 姓名, 性别, 年龄, 电话, 住址); for (int i 0; i Con-size; i) { printf(%-10s %-10s %-10s %-15s %-20s\n, Con-a[i].name, Con-a[i].Gender, Con-a[i].age, Con-a[i].Tel, Con-a[i].Address); } printf(展示完成\n); } //查找联系人 void FinCon(contact* Con) { int index Menu2(Con); if (index -2) { printf(已取消查找\n); return; } if (index 0) { printf(联系人不存在\n); return; } // 打印找到的联系人 printf(%-10s %-10s %-10s %-15s %-20s\n, Con-a[index].name, Con-a[index].Gender, Con-a[index].age, Con-a[index].Tel, Con-a[index].Address); printf(打印完成\n); } //修改联系人数据 void ModDat(contact* Con) { printf(如果不需要修改某一项直接回车即可。\n); int index Menu2(Con); if (index -2) { printf(已取消修改\n); return; } if (index 0) { printf(联系人不存在\n); return; } printf(请输入新的姓名); InputSkip(Con-a[index].name, NAME_Max, Con-a[index].name);/*scanf(%s, Con-a[index].name);*/ /*printf(请输入新的性别); scanf(%s, Con-a[index].Gender);*/ // 性别0女 1男其他自定义 char buf[20] { 0 }; printf(请输入新性别(0女 1男 也可自定义输入)); InputSkip(buf, 20, Con-a[index].Gender);/*scanf(%s, buf);*/ if (strcmp(buf, 0) 0) { strcpy(Con-a[index].Gender, 女); } else if (strcmp(buf, 1) 0) { strcpy(Con-a[index].Gender, 男); } else { strcpy(Con-a[index].Gender, buf); } printf(请输入年龄); InputSkip(Con-a[index].age, AGE_Max, Con-a[index].age);/*scanf(%s, data.age); */ printf(请输入电话); InputSkip(Con-a[index].Tel, TEL_Max, Con-a[index].Tel);/*scanf(%s, data.Tel);*/ printf(请输入新的住址); InputSkip(Con-a[index].Address, ADDRESS_Max, Con-a[index].Address);/*scanf(%s, Con-a[index].Address);*/ printf(修改成功\n); } void SaveCon(contact* Con) { FILE* pf fopen(contact.txt, wb); if (pf NULL) { perror(Failed to open file); return; } //将通讯录数据写入文件 for (int i 0; i Con-size; i) { fwrite(Con-a[i], sizeof(UseDat), 1, pf); } printf(数据保存完成\n); fclose(pf); } //销毁通讯录数据 void DesDat(contact* Con) { SaveCon(Con); Destroy(Con); }5. test.c主函数 / 菜单#define _CRT_SECURE_NO_WARNINGS #include SeqList.h #includeContact.h void menu() { //初始化通讯录 contact Con; InitAdd(Con); int input 0; do{ printf(欢迎使用通讯录系统\n); printf(* * * * 1.添加联系人 * * * * 2.删除联系人 * * * *\n); printf(* * * * 3.展示通讯录 * * * * 4.查找联系人 * * * *\n); printf(* * * * 5.修改联系人数据 * * 6.退出系统 * * * * *\n); printf(请输入操作选项); if (scanf(%d, input) ! 1) { ClearBuff(); // 清空脏数据 printf(输入错误请输入数字\n); continue; // 重新循环 } switch (input) { case 1: AddCon(Con); break; case 2: DelCon(Con); break; case 3: ShoCon(Con); break; case 4: FinCon(Con); break; case 5: ModDat(Con); break; case 6: printf(退出系统\n); break; default: printf(输入错误请重新输入\n); break; } } while (input ! 6); DesDat(Con); } int main() { menu(); // 调用菜单函数 return 0; }十一、升级版通讯录运行演示 功能讲解对比第一篇基础版本篇做了大量实用升级结合运行截图给你逐点演示讲解。核心新增升级点查找支持姓名 / 电话 / 住址 3 种方式不再只能按姓名操作性别快捷输入0女、1男也可以自定义文字修改信息时直接回车可跳过不修改项不用重复输入增加输入容错非法字符不会导致程序崩溃增删改查复用同一套查找逻辑代码复用性更强保留持久化存储启动自动加载、退出自动保分步运行演示1. 程序启动运行后自动读取本地contact.txt历史文件加载完毕弹出主菜单新增性别快捷输入功能输入1进入添加性别支持两种模式输入1→ 自动保存为男输入0→ 自动保存为女直接输入文字如 “女”支持自定义示例添加联系人【张三】3. 删除联系人序号 2核心升级支持按【电话、住址】删除不止姓名默认按姓名查找输入1切换查找方式选择2按电话输入对应号码即可删除。示例按电话删除张三4. 多条件查找联系人序号 4核心升级3 种查找方式自由切换默认姓名查找输入1切换支持姓名、电话、住址精准匹配。示例添加【李四】性别直接自定义输入女按电话查找5. 修改联系人序号 5新增回车跳过功能不想修改的信息直接回车程序自动保留原有数据性别同样支持快捷输入。6. 异常输入容错主菜单输入非数字时程序自动清空缓冲区提示输入错误不会崩溃卡死。7. 退出自动保存序号 6输入6退出自动将所有联系人数据写入contact.txt下次启动自动加载数据永久保存。整体总结本篇在基础版上重点优化了查找灵活性和使用体验解决了只能按姓名操作的局限增加了人性化快捷输入、容错处理代码结构更规范实用性大幅提升。本篇代码Git链接 Luminous/Luminousbegin十二、写在最后这个通讯录项目是我第一个真正意义上的 C 语言项目从最开始的几百行基础代码到后来一步步优化成现在的样子我踩了几乎所有新手会踩的坑。现在回头看当时的代码有很多问题冗余严重、命名不规范、逻辑不够严谨... 但正是这些不完美让我对 C 语言的理解有了质的提升。对于初学者来说不要怕写 bug每一个 bug 都是最好的老师。当你把所有 bug 都修完你会发现自己已经不知不觉成长了很多。