news 2026/7/14 7:01:23

数据结构 - 线性表第四篇:C 语言通讯录优化升级全记录(踩坑 + 思考)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
数据结构 - 线性表第四篇:C 语言通讯录优化升级全记录(踩坑 + 思考)

前言

基础版通讯录虽然跑通了,但每次用都挺折磨:输入汉字死循环、修改时要全部重输、只能按姓名查找... 这篇博客会记录我按优先级一步步优化的全过程,包括每一个功能的需求来源、遇到的问题、解决方案和代码修改,所有优化都是我当时实际遇到痛点后才做的。

(5/25晚测试时发现修改名字出错,为保证文章连续性,本文代码不再修改,请您谅解!修改过程请转至C 语言通讯录补坑篇:终版遗留 Bug 修复,解决修改姓名输入错乱问题-CSDN博客)

一、优化优先级排序

我把问题按严重程度排了个序,先解决最致命的:

  1. 最高优先级:输入汉字死循环(直接导致程序无法使用)
  2. 次高优先级:修改时必须全部重输(体验最差)
  3. 功能扩展:支持按电话 / 地址查找(最常用的功能缺失)
  4. 细节优化:性别输入简化(手动输入男女太麻烦)
  5. 终极修复:查找功能的连环坑(输入名字报错、明明存在却找不到)
  6. bug 修复:自定义修改无效
  7. 代码健壮性:消除所有编译警告和逻辑漏洞

二、第一优先级:彻底解决输入汉字死循环

这是我当时最头疼的问题,查了资料才搞懂原理。

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 整个过程的收获

  1. 对 C 语言字符串处理的理解加深了:终于搞懂了换行符、字符串终止符的重要性,以及 scanf 和 fgets 的区别
  2. 对输入输出缓冲区的理解彻底通透了:之前一直觉得缓冲区很神秘,现在终于知道为什么会出现输入吞掉、无限循环的问题
  3. 学会了调试技巧:从只会重启程序,到会用 printf 加断点调试,一步步定位问题
  4. 理解了用户体验的重要性:一个功能能不能用是一回事,好不好用是另一回事,很多细节优化能极大提升体验
  5. 接受了代码的不完美:新手先实现功能,再考虑优化和重构,不要一开始就追求完美

9.2 后续可以继续优化的方向

  1. 把年龄改成 int 类型,支持按年龄排序
  2. 增加手机号格式校验(11 位数字)
  3. 增加重复联系人校验
  4. 支持批量删除和导入导出
  5. 重构三个查找函数,消除代码冗余

十、最终完整可运行代码(分文件)

1. SeqList.h(顺序表头文件)

#pragma once #define _CRT_SECURE_NO_WARNINGS #include<stdlib.h> #include<stdio.h> #include<assert.h> #include"Contact.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(顺序表实现)

#include"SeqList.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 #include"SeqList.h" #include"Contact.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" #include"Contact.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; }

十一、升级版通讯录运行演示 & 功能讲解

对比第一篇基础版,本篇做了大量实用升级,结合运行截图,给你逐点演示讲解。

核心新增升级点

  1. 查找支持姓名 / 电话 / 住址 3 种方式,不再只能按姓名操作
  2. 性别快捷输入:0=女、1=男,也可以自定义文字
  3. 修改信息时,直接回车可跳过不修改项,不用重复输入
  4. 增加输入容错,非法字符不会导致程序崩溃
  5. 增删改查复用同一套查找逻辑,代码复用性更强
  6. 保留持久化存储,启动自动加载、退出自动保

分步运行演示

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 都修完,你会发现自己已经不知不觉成长了很多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/14 6:58:22

Qt学习(开发环境Visual Studio)5.工程结构介绍

本文将介绍软件开发过程中的工程框架&#xff0c;作者对本模块并非精通&#xff0c;所写所作及代码均来自老师傅的传授&#xff0c;加上自己的理解。Doc&#xff1a;文档&#xff0c;主要包含软件的说明书、界面设计、算法开发要求等内容Src&#xff1a;码源&#xff0c;开发过…

作者头像 李华
网站建设 2026/7/14 6:55:05

2026年4月最新:全网深度测评10款专业写小说软件,到底哪个最好用?

现在的网文圈真的太卷了。如果是全职写小说的&#xff0c;光靠自己敲键盘&#xff0c;手速根本拼不过别人。很多人之前用通用AI写连载&#xff0c;结果写了几十万字后&#xff0c;AI把设定全忘了&#xff0c;出来的文字还满是不可否认、总而言之这种浓浓的机器味。 为了在保证…

作者头像 李华
网站建设 2026/7/14 6:54:57

Unity程序化生成实战:ProceduralToolkit核心功能与地下城构建

1. 项目概述&#xff1a;为什么你需要ProceduralToolkit&#xff1f;如果你在Unity里做过地形、建筑、植被或者任何需要大量、规则或随机内容生成的项目&#xff0c;那你一定体会过手动摆放的痛苦。一个关卡&#xff0c;成百上千棵树&#xff1b;一座城市&#xff0c;无数栋形态…

作者头像 李华
网站建设 2026/7/14 6:54:45

1行代码加速Pandas:Modin分布式原理与生产实践

1. 项目概述&#xff1a;当一行代码就能让Pandas快出一个数量级&#xff0c;你还在手写Dask或Spark吗&#xff1f;“1 Line of Code to Make Your Pandas 80% More Efficient: Modin Pandas”——这个标题不是营销噱头&#xff0c;而是我去年在处理某省医保结算日志&#xff08…

作者头像 李华
网站建设 2026/7/14 6:51:55

C++构建电力能耗数据分析系统:高性能实时处理架构与工程实践

1. 项目概述&#xff1a;当电力数据遇上C最近几年&#xff0c;无论是大型工厂、商业楼宇还是数据中心&#xff0c;能耗管理都成了一个绕不开的话题。电费账单上的数字越来越醒目&#xff0c;背后的数据也越来越庞大。我接手过不少相关的项目&#xff0c;发现很多团队在处理海量…

作者头像 李华
网站建设 2026/7/14 6:51:32

基于STM32与Proteus的PT100测温仿真系统设计与避坑指南

1. 系统概述与核心组件选择PT100铂电阻温度传感器因其出色的线性度、稳定性和宽测温范围&#xff08;-200℃~850℃&#xff09;&#xff0c;成为工业测温领域的黄金标准。本系统采用STM32F103R6作为主控芯片&#xff0c;配合Proteus仿真软件构建完整的虚拟测温环境。实测表明&a…

作者头像 李华