前言接上篇让我们正式进入实战项目基于完整版动态顺序表手写实现简易通讯录支持联系人增删改查、查找、修改、保存等功能把顺序表知识真正落地应用。这期间我踩了不少坑编译报错连环炸、运行直接崩溃、输入汉字无限死循环、数据显示乱码...这篇博客会还原我的真实开发过程包括每一步的错误想法、踩的坑、排查过程和最终解决方案所有代码都是我当时一行行写的保留了新手特有的代码冗余和逻辑漏洞。一、先回顾我之前写的通用顺序表代码通讯录的底层完全复用了我之前写的动态顺序表这也是后来代码冗余的根源 —— 顺序表是通用设计但通讯录只用到了其中几个函数。1.1 顺序表头文件SeqList.h#pragma once #define _CRT_SECURE_NO_WARNINGS #includestdlib.h #includestdio.h #includeassert.h #includeContact.h // 当时不知道怎么解耦先留空后面让通讯录定义 typedef int SLType; // 错误的宏定义当时以为所有数据都能用%d打印 #define SLTYPE_PRINT_FMT %d typedef struct SeqList { SLType* a; // 动态数组 int size; // 有效数据个数 int capacity; // 总容量 }SL; // 当时写了所有通用函数其实通讯录只用到了5个 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); // 没用 int FindSpecify(SL* ps, SLType x); // 没用结构体不能用比较 void PushSpecify(SL* ps,int pos, SLType x); // 没用 void SLDeleteSpecify(SL* ps,int pos); // 有用 void Modifyspecified(SL* ps, int pos, SLType x); // 没用1.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(扩容失败); exit(1); } ps-a Newps; ps-capacity Newspace; } } void PushBack(SL* ps, SLType x) { assert(ps); SLExp(ps); ps-a[ps-size] x; } void SLDeleteSpecify(SL* ps,int pos) { assert(ps); assert(pos 0 pos ps-size); assert(ps-size 0); for (int i pos; i ps-size - 1; i) { ps-a[i] ps-a[i 1]; } ps-size--; } // 后面还有一堆没用的函数全部注释掉了代码冗余严重二、第一步把顺序表改成通讯录的底层当时的想法很简单把顺序表的SLType从int改成联系人结构体就行。结果第一步就踩了头文件循环包含的坑。2.1 错误的尝试循环包含我直接在SeqList.h里包含Contact.h然后把SLType改成struct UserData// SeqList.h 错误写法 #includeContact.h typedef struct UserData SLType;然后Contact.h里又包含SeqList.h// Contact.h 错误写法 #includeSeqList.h typedef struct UserData { char name[100]; // ...其他字段 }UseDat; typedef struct SeqList contact;编译直接报错struct SeqList 重定义。2.2 解决方案前向声明经过检查我发现头文件互相包含了应该用前向声明// SeqList.h 正确写法 #pragma once #define _CRT_SECURE_NO_WARNINGS #includestdlib.h #includestdio.h #includeassert.h // 前向声明UserData不用包含Contact.h typedef struct UserData SLType; typedef struct SeqList { SLType* a; int size; int capacity; }SL; // 只保留通讯录用到的函数声明 void SLInit(SL* ps); void Destroy(SL* ps); void SLExp(SL* ps); void PushBack(SL* ps, SLType x); void SLDeleteSpecify(SL* ps,int pos);然后Contact.h正常包含SeqList.h// Contact.h 正确写法 #pragma once #define _CRT_SECURE_NO_WARNINGS #includeSeqList.h #define NAME_Max 100 #define Gender_Max 4 #define AGE_Max 4 #define TEL_Max 15 #define ADDRESS_Max 100 typedef struct UserData { char name[NAME_Max]; char Gender[Gender_Max]; char age[AGE_Max]; // 当时图省事直接用char数组存年龄后面踩了大坑 char Tel[TEL_Max]; // 电话也用char数组因为有11位int存不下 char Address[ADDRESS_Max]; }UseDat; 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);终于解决了循环包含的问题编译通过了第一步。三、第二步实现通讯录基础功能我当时的思路是先把增删改查的架子搭起来能跑通就行不管代码好不好看。3.1 初始化和文件加载当时想做数据持久化程序启动时自动加载之前的联系人void LoadCon(contact* Con) { FILE* pf fopen(contact.txt, rb); if (pf NULL) { // 当时写的是exit(1)第一次运行直接崩溃 perror(打开文件失败); exit(1); } 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); }3.2 添加联系人void AddCon(contact* Con) { UseDat data; printf(请输入姓名); scanf(%s, data.name); printf(请输入性别); scanf(%s, data.Gender); printf(请输入年龄); // 当时犯了低级错误age是char数组用了%d格式符 scanf(%d, data.age); printf(请输入电话); // 电话也是char数组同样用了%d scanf(%d, data.Tel); printf(请输入住址); scanf(%s, data.Address); PushBack(Con, data); printf(添加成功\n); }3.3 展示通讯录void ShoCon(contact* Con) { printf(%-10s %-10s %-10s %-15s %-20s\n, 姓名, 性别, 年龄, 电话, 住址); for (int i 0; i Con-size; i) { // 同样的错误age和Tel用了%d打印 printf(%-10s %-10s %-10d %-15d %-20s\n, Con-a[i].name, Con-a[i].Gender, Con-a[i].age, Con-a[i].Tel, Con-a[i].Address); } printf(展示完成\n); }3.4 删除和修改联系人// 按姓名查找 int FinNa(contact* Con, char name[]) { for (int i 0; i Con-size; i) { if (strcmp(Con-a[i].name, name) 0) { return i; } } return -1; } void DelCon(contact* Con) { char name[NAME_Max]; printf(请输入要删除的联系人姓名); scanf(%s, name); int index FinNa(Con, name); if (index 0) { printf(联系人不存在\n); return; } SLDeleteSpecify(Con, index); printf(删除成功\n); } void ModDat(contact* Con) { char name[NAME_Max]; printf(请输入要修改的联系人姓名); scanf(%s, name); int index FinNa(Con, name); if (index 0) { printf(联系人不存在\n); return; } // 当时必须全部重输不能跳过体验极差 printf(请输入新的姓名); scanf(%s, Con-a[index].name); printf(请输入新的性别); scanf(%s, Con-a[index].Gender); printf(请输入新的年龄); scanf(%d, Con-a[index].age); printf(请输入新的电话); scanf(%d, Con-a[index].Tel); printf(请输入新的住址); scanf(%s, Con-a[index].Address); printf(修改成功\n); }3.5 退出时保存数据void SaveCon(contact* Con) { FILE* pf fopen(contact.txt, wb); if (pf NULL) { perror(保存失败); 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); }四、主菜单和第一次运行连环报错我写完主菜单信心满满地点击运行结果迎来了连环暴击。4.1 错误 1无法解析的外部符号 main报错信息无法解析的外部符号 main函数 int __cdecl invoke_main(void) 中引用了该符号原因光顾着写通讯录函数忘记写main函数了C 程序必须有main作为入口。解决加上主菜单和 main 函数// 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.删除 3.展示 4.查找 5.修改 6.退出\n); printf(请输入操作); scanf(%d, input); 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; }4.2 错误 2运行直接崩溃现象点击运行直接弹出 “程序已停止工作”控制台显示打开文件失败: No such file or directory原因第一次运行时contact.txt不存在LoadCon函数里直接调用exit(1)终止了程序。解决修改LoadCon文件不存在时直接返回不崩溃void LoadCon(contact* Con) { FILE* pf fopen(contact.txt, rb); if (pf NULL) { printf(没有找到历史数据创建新通讯录\n); return; // 不exit继续运行 } // 后面不变 }4.3 错误 3输入汉字直接无限循环现象主菜单输入汉字控制台无限刷屏 “输入错误”原因scanf(%d, input)读取汉字失败汉字留在输入缓冲区下一次循环又读到同样的脏数据。临时解决当时还不会写缓冲区清理函数只能重启程序后面优化篇会详细解决。4.4 错误 4年龄和电话显示乱码现象添加联系人后展示时年龄和电话显示一堆乱码。原因age和Tel是char数组但我用了%d格式符输入输出类型不匹配导致数据错乱。解决把所有%d改成%s// 添加联系人 printf(请输入年龄); scanf(%s, data.age); // %d→%s printf(请输入电话); scanf(%s, data.Tel); // %d→%s // 展示通讯录 printf(%-10s %-10s %-10s %-15s %-20s\n, Con-a[i].name, Con-a[i].Gender, Con-a[i].age, // %d→%s Con-a[i].Tel, // %d→%s Con-a[i].Address);五、基础版终于跑通了但问题一大堆经过一下午的调试基础版终于能跑了1.可以添加联系人2. 可以展示联系人3. 可以删除和修改4.退出时自动保存数据。但是体验差到极致1. 输入汉字直接死循环2.修改时必须全部重输不能跳过3. 只能按姓名查找不能按电话 / 地址4. 性别必须手动输入 “男”“女”容易输错5.没有任何输入校验输入超长会崩溃6.代码冗余严重顺序表里一堆没用的函数。六、通讯录系统运行演示6.1 程序启动运行程序后首先检测本地contact.txt文件首次运行无历史数据提示创建新通讯录随后弹出功能菜单6.2 功能分步演示1. 添加联系人序号 1输入1依次填写联系人信息姓名、性别、年龄、电话、住址回车后提示添加成功。示例添加联系人【张三】2. 删除联系人序号 2输入2输入要删除的姓名即可精准删除对应联系人。示例删除联系人【张三】3. 展示所有联系人序号 3输入3系统格式化打印通讯录内全部联系人信息无联系人时提示为空。示例查看联系人【李四】4. 修改联系人序号 5输入5输入待修改的原姓名依次填写新信息即可更新联系人全部内容。示例将【李四】修改为【王五】修改后再次执行展示功能可看到信息已更新5. 退出系统并保存序号 6输入6程序自动将当前通讯录数据写入本地contact.txt文件持久化保存下次启动可自动加载历史数据。6.3、核心功能总结动态扩容联系人数量无上限顺序表自动扩容持久化存储退出自动保存、启动自动加载精准操作按姓名实现增删改查操作简单直观数据隔离5 个文件分层编写结构清晰、易维护。七、基础版学习总结头文件循环包含是新手高频坑一定要用前向声明解决不要互相包含scanf 格式符必须和类型严格匹配char 数组用 % sint 用 % d否则必乱码文件操作要考虑不存在的情况不要直接 exit要优雅降级输入缓冲区问题是 C 语言的噩梦新手一定要提前了解通用代码复用会带来冗余顺序表的通用函数很多在通讯录里没用但先实现功能再优化。本篇Gitee链接为Luminous/Luminousbegin下一篇博客我会记录如何一步步把这个 “能用但难用” 的基础版优化成一个真正好用的通讯录系统。
数据结构 - 线性表第三篇:基于顺序表实现 C 语言通讯录(基础功能篇)
发布时间:2026/7/14 7:01:40
前言接上篇让我们正式进入实战项目基于完整版动态顺序表手写实现简易通讯录支持联系人增删改查、查找、修改、保存等功能把顺序表知识真正落地应用。这期间我踩了不少坑编译报错连环炸、运行直接崩溃、输入汉字无限死循环、数据显示乱码...这篇博客会还原我的真实开发过程包括每一步的错误想法、踩的坑、排查过程和最终解决方案所有代码都是我当时一行行写的保留了新手特有的代码冗余和逻辑漏洞。一、先回顾我之前写的通用顺序表代码通讯录的底层完全复用了我之前写的动态顺序表这也是后来代码冗余的根源 —— 顺序表是通用设计但通讯录只用到了其中几个函数。1.1 顺序表头文件SeqList.h#pragma once #define _CRT_SECURE_NO_WARNINGS #includestdlib.h #includestdio.h #includeassert.h #includeContact.h // 当时不知道怎么解耦先留空后面让通讯录定义 typedef int SLType; // 错误的宏定义当时以为所有数据都能用%d打印 #define SLTYPE_PRINT_FMT %d typedef struct SeqList { SLType* a; // 动态数组 int size; // 有效数据个数 int capacity; // 总容量 }SL; // 当时写了所有通用函数其实通讯录只用到了5个 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); // 没用 int FindSpecify(SL* ps, SLType x); // 没用结构体不能用比较 void PushSpecify(SL* ps,int pos, SLType x); // 没用 void SLDeleteSpecify(SL* ps,int pos); // 有用 void Modifyspecified(SL* ps, int pos, SLType x); // 没用1.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(扩容失败); exit(1); } ps-a Newps; ps-capacity Newspace; } } void PushBack(SL* ps, SLType x) { assert(ps); SLExp(ps); ps-a[ps-size] x; } void SLDeleteSpecify(SL* ps,int pos) { assert(ps); assert(pos 0 pos ps-size); assert(ps-size 0); for (int i pos; i ps-size - 1; i) { ps-a[i] ps-a[i 1]; } ps-size--; } // 后面还有一堆没用的函数全部注释掉了代码冗余严重二、第一步把顺序表改成通讯录的底层当时的想法很简单把顺序表的SLType从int改成联系人结构体就行。结果第一步就踩了头文件循环包含的坑。2.1 错误的尝试循环包含我直接在SeqList.h里包含Contact.h然后把SLType改成struct UserData// SeqList.h 错误写法 #includeContact.h typedef struct UserData SLType;然后Contact.h里又包含SeqList.h// Contact.h 错误写法 #includeSeqList.h typedef struct UserData { char name[100]; // ...其他字段 }UseDat; typedef struct SeqList contact;编译直接报错struct SeqList 重定义。2.2 解决方案前向声明经过检查我发现头文件互相包含了应该用前向声明// SeqList.h 正确写法 #pragma once #define _CRT_SECURE_NO_WARNINGS #includestdlib.h #includestdio.h #includeassert.h // 前向声明UserData不用包含Contact.h typedef struct UserData SLType; typedef struct SeqList { SLType* a; int size; int capacity; }SL; // 只保留通讯录用到的函数声明 void SLInit(SL* ps); void Destroy(SL* ps); void SLExp(SL* ps); void PushBack(SL* ps, SLType x); void SLDeleteSpecify(SL* ps,int pos);然后Contact.h正常包含SeqList.h// Contact.h 正确写法 #pragma once #define _CRT_SECURE_NO_WARNINGS #includeSeqList.h #define NAME_Max 100 #define Gender_Max 4 #define AGE_Max 4 #define TEL_Max 15 #define ADDRESS_Max 100 typedef struct UserData { char name[NAME_Max]; char Gender[Gender_Max]; char age[AGE_Max]; // 当时图省事直接用char数组存年龄后面踩了大坑 char Tel[TEL_Max]; // 电话也用char数组因为有11位int存不下 char Address[ADDRESS_Max]; }UseDat; 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);终于解决了循环包含的问题编译通过了第一步。三、第二步实现通讯录基础功能我当时的思路是先把增删改查的架子搭起来能跑通就行不管代码好不好看。3.1 初始化和文件加载当时想做数据持久化程序启动时自动加载之前的联系人void LoadCon(contact* Con) { FILE* pf fopen(contact.txt, rb); if (pf NULL) { // 当时写的是exit(1)第一次运行直接崩溃 perror(打开文件失败); exit(1); } 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); }3.2 添加联系人void AddCon(contact* Con) { UseDat data; printf(请输入姓名); scanf(%s, data.name); printf(请输入性别); scanf(%s, data.Gender); printf(请输入年龄); // 当时犯了低级错误age是char数组用了%d格式符 scanf(%d, data.age); printf(请输入电话); // 电话也是char数组同样用了%d scanf(%d, data.Tel); printf(请输入住址); scanf(%s, data.Address); PushBack(Con, data); printf(添加成功\n); }3.3 展示通讯录void ShoCon(contact* Con) { printf(%-10s %-10s %-10s %-15s %-20s\n, 姓名, 性别, 年龄, 电话, 住址); for (int i 0; i Con-size; i) { // 同样的错误age和Tel用了%d打印 printf(%-10s %-10s %-10d %-15d %-20s\n, Con-a[i].name, Con-a[i].Gender, Con-a[i].age, Con-a[i].Tel, Con-a[i].Address); } printf(展示完成\n); }3.4 删除和修改联系人// 按姓名查找 int FinNa(contact* Con, char name[]) { for (int i 0; i Con-size; i) { if (strcmp(Con-a[i].name, name) 0) { return i; } } return -1; } void DelCon(contact* Con) { char name[NAME_Max]; printf(请输入要删除的联系人姓名); scanf(%s, name); int index FinNa(Con, name); if (index 0) { printf(联系人不存在\n); return; } SLDeleteSpecify(Con, index); printf(删除成功\n); } void ModDat(contact* Con) { char name[NAME_Max]; printf(请输入要修改的联系人姓名); scanf(%s, name); int index FinNa(Con, name); if (index 0) { printf(联系人不存在\n); return; } // 当时必须全部重输不能跳过体验极差 printf(请输入新的姓名); scanf(%s, Con-a[index].name); printf(请输入新的性别); scanf(%s, Con-a[index].Gender); printf(请输入新的年龄); scanf(%d, Con-a[index].age); printf(请输入新的电话); scanf(%d, Con-a[index].Tel); printf(请输入新的住址); scanf(%s, Con-a[index].Address); printf(修改成功\n); }3.5 退出时保存数据void SaveCon(contact* Con) { FILE* pf fopen(contact.txt, wb); if (pf NULL) { perror(保存失败); 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); }四、主菜单和第一次运行连环报错我写完主菜单信心满满地点击运行结果迎来了连环暴击。4.1 错误 1无法解析的外部符号 main报错信息无法解析的外部符号 main函数 int __cdecl invoke_main(void) 中引用了该符号原因光顾着写通讯录函数忘记写main函数了C 程序必须有main作为入口。解决加上主菜单和 main 函数// 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.删除 3.展示 4.查找 5.修改 6.退出\n); printf(请输入操作); scanf(%d, input); 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; }4.2 错误 2运行直接崩溃现象点击运行直接弹出 “程序已停止工作”控制台显示打开文件失败: No such file or directory原因第一次运行时contact.txt不存在LoadCon函数里直接调用exit(1)终止了程序。解决修改LoadCon文件不存在时直接返回不崩溃void LoadCon(contact* Con) { FILE* pf fopen(contact.txt, rb); if (pf NULL) { printf(没有找到历史数据创建新通讯录\n); return; // 不exit继续运行 } // 后面不变 }4.3 错误 3输入汉字直接无限循环现象主菜单输入汉字控制台无限刷屏 “输入错误”原因scanf(%d, input)读取汉字失败汉字留在输入缓冲区下一次循环又读到同样的脏数据。临时解决当时还不会写缓冲区清理函数只能重启程序后面优化篇会详细解决。4.4 错误 4年龄和电话显示乱码现象添加联系人后展示时年龄和电话显示一堆乱码。原因age和Tel是char数组但我用了%d格式符输入输出类型不匹配导致数据错乱。解决把所有%d改成%s// 添加联系人 printf(请输入年龄); scanf(%s, data.age); // %d→%s printf(请输入电话); scanf(%s, data.Tel); // %d→%s // 展示通讯录 printf(%-10s %-10s %-10s %-15s %-20s\n, Con-a[i].name, Con-a[i].Gender, Con-a[i].age, // %d→%s Con-a[i].Tel, // %d→%s Con-a[i].Address);五、基础版终于跑通了但问题一大堆经过一下午的调试基础版终于能跑了1.可以添加联系人2. 可以展示联系人3. 可以删除和修改4.退出时自动保存数据。但是体验差到极致1. 输入汉字直接死循环2.修改时必须全部重输不能跳过3. 只能按姓名查找不能按电话 / 地址4. 性别必须手动输入 “男”“女”容易输错5.没有任何输入校验输入超长会崩溃6.代码冗余严重顺序表里一堆没用的函数。六、通讯录系统运行演示6.1 程序启动运行程序后首先检测本地contact.txt文件首次运行无历史数据提示创建新通讯录随后弹出功能菜单6.2 功能分步演示1. 添加联系人序号 1输入1依次填写联系人信息姓名、性别、年龄、电话、住址回车后提示添加成功。示例添加联系人【张三】2. 删除联系人序号 2输入2输入要删除的姓名即可精准删除对应联系人。示例删除联系人【张三】3. 展示所有联系人序号 3输入3系统格式化打印通讯录内全部联系人信息无联系人时提示为空。示例查看联系人【李四】4. 修改联系人序号 5输入5输入待修改的原姓名依次填写新信息即可更新联系人全部内容。示例将【李四】修改为【王五】修改后再次执行展示功能可看到信息已更新5. 退出系统并保存序号 6输入6程序自动将当前通讯录数据写入本地contact.txt文件持久化保存下次启动可自动加载历史数据。6.3、核心功能总结动态扩容联系人数量无上限顺序表自动扩容持久化存储退出自动保存、启动自动加载精准操作按姓名实现增删改查操作简单直观数据隔离5 个文件分层编写结构清晰、易维护。七、基础版学习总结头文件循环包含是新手高频坑一定要用前向声明解决不要互相包含scanf 格式符必须和类型严格匹配char 数组用 % sint 用 % d否则必乱码文件操作要考虑不存在的情况不要直接 exit要优雅降级输入缓冲区问题是 C 语言的噩梦新手一定要提前了解通用代码复用会带来冗余顺序表的通用函数很多在通讯录里没用但先实现功能再优化。本篇Gitee链接为Luminous/Luminousbegin下一篇博客我会记录如何一步步把这个 “能用但难用” 的基础版优化成一个真正好用的通讯录系统。