系列回顾本系列三篇完整闭环第一篇基础版从零实现增删查改 文件存储踩遍新手所有坑格式符乱码、文件闪退、输入死循环第二篇优化版修复核心 bug新增多条件查找、回车跳过修改等进阶功能本篇终版对底层代码做规范重构 全 bug 修复同时加入逐功能详细讲解。一、工程结构说明标准 C 语言项目分文件写法把不同功能的代码拆到不同文件里方便维护和复用文件作用包含内容contact.h头文件所有宏定义、结构体声明、函数声明相当于项目的 说明书contact.c功能实现文件所有通讯录功能的具体代码增删查改、文件操作等main.c主程序入口菜单界面 主循环只负责调用功能函数二、完整代码 逐功能讲解1. contact.h头文件项目声明// 防止头文件重复包含新手必加否则会报重定义错误 #ifndef __CONTACT_H__ #define __CONTACT_H__ // 引入需要的头文件 #include stdio.h // 输入输出 #include stdlib.h // 内存管理、exit函数 #include string.h // 字符串操作strcmp、strcpy等 // 宏定义统一管理常量后期修改只改这里 #define MAX_NAME 20 // 姓名最大长度 #define MAX_SEX 6 // 性别最大长度 #define MAX_TEL 12 // 手机号最大长度11位终止符 #define MAX_ADDR 30 // 地址最大长度 #define MAX_SL 1000 // 通讯录最大容量 // 联系人信息结构体存储一个人的所有信息 typedef struct UserData { char name[MAX_NAME]; // 姓名 char sex[MAX_SEX]; // 性别 int age; // 年龄修复第一篇char数组存年龄的bug char tel[MAX_TEL]; // 手机号 char addr[MAX_ADDR]; // 地址 } UserData; // 通讯录结构体本质是一个顺序表数组有效元素个数 typedef struct Contact { UserData data[MAX_SL]; // 存储所有联系人的数组 int size; // 当前通讯录中有效联系人的个数 } Contact; // 函数声明所有功能函数都在这里声明其他文件才能调用 // 基础功能 void InitContact(Contact* con); // 初始化通讯录 void AddContact(Contact* con); // 添加联系人 void ShowContact(Contact* con); // 展示所有联系人 void DelContact(Contact* con); // 删除联系人 // 新增功能第二篇实现 void SearchContact(Contact* con); // 多条件查找姓名/电话/地址 void ModifyContact(Contact* con); // 修改联系人支持回车跳过 void ClearAllContact(Contact* con);// 清空所有联系人 void SortContact(Contact* con); // 按姓名排序 // 文件持久化功能 void SaveContact(Contact* con); // 保存数据到文件 void LoadContact(Contact* con); // 从文件加载数据 // 工具函数解决输入bug void ClearBuff(); // 清空输入缓冲区解决死循环 void InputSkip(char* dest, int maxLen); // 回车跳过输入不用全部重输 #endif // !__CONTACT_H__2. contact.c功能实现核心代码#include contact.h /************************** 工具函数解决输入bug **************************/ // 清空输入缓冲区解决第一篇输入汉字死循环的核心bug // 原理把缓冲区里残留的换行、汉字等脏数据全部读走直到遇到换行符 void ClearBuff() { while (getchar() ! \n); } // 新增功能回车跳过输入第二篇核心优化 // 作用修改联系人时直接回车就保留原来的值不用全部重输 // 参数dest-要赋值的目标数组maxLen-数组最大长度 void InputSkip(char* dest, int maxLen) { char tmp[100] { 0 }; // 临时数组存储输入 fgets(tmp, maxLen, stdin); // 读取一行输入包括空行 // 去掉fgets自动读入的换行符 if (tmp[strlen(tmp) - 1] \n) tmp[strlen(tmp) - 1] \0; // 如果输入不为空不是直接回车才覆盖原来的值 if (strlen(tmp) 0) strcpy(dest, tmp); } // 内部工具函数按姓名查找联系人复用避免重复代码 // 返回值找到返回下标没找到返回-1 static int FindByName(Contact* con, char* name) { for (int i 0; i con-size; i) { if (strcmp(con-data[i].name, name) 0) return i; } return -1; } /************************** 基础功能实现 **************************/ // 初始化通讯录程序启动时调用加载历史数据 void InitContact(Contact* con) { con-size 0; // 初始有效个数为0 LoadContact(con); // 从文件加载历史数据修复第一篇首次运行闪退bug } // 添加联系人 void AddContact(Contact* con) { // 先判断通讯录是否已满 if (con-size MAX_SL) { printf(通讯录已满\n); return; } // 输入姓名支持带空格的名字修复第一篇scanf不能输空格的bug printf(请输入姓名); ClearBuff(); // 先清空缓冲区残留的换行 fgets(con-data[con-size].name, MAX_NAME, stdin); // 去掉fgets读入的换行符 if (con-data[con-size].name[strlen(con-data[con-size].name) - 1] \n) con-data[con-size].name[strlen(con-data[con-size].name) - 1] \0; // 新增功能重名校验防止添加重复联系人 if (FindByName(con, con-data[con-size].name) ! -1) { printf(该联系人已存在\n); return; } // 输入其他信息 printf(请输入性别); scanf(%s, con-data[con-size].sex); printf(请输入年龄); scanf(%d, con-data[con-size].age); // 用int存年龄修复格式符乱码bug printf(请输入手机号); scanf(%s, con-data[con-size].tel); printf(请输入地址); scanf(%s, con-data[con-size].addr); con-size; // 有效个数加1 printf(添加成功\n); SaveContact(con); // 添加后自动保存到文件 } // 展示所有联系人 void ShowContact(Contact* con) { if (con-size 0) { printf(通讯录暂无联系人\n); return; } // 打印表头 printf(%-10s %-6s %-4s %-12s %-20s\n, 姓名, 性别, 年龄, 手机号, 地址); // 遍历打印所有联系人 for (int i 0; i con-size; i) { printf(%-10s %-6s %-4d %-12s %-20s\n, con-data[i].name, con-data[i].sex, con-data[i].age, con-data[i].tel, con-data[i].addr); } } // 删除联系人 void DelContact(Contact* con) { char name[MAX_NAME]; printf(请输入要删除的姓名); scanf(%s, name); // 先查找联系人是否存在 int pos FindByName(con, name); if (pos -1) { printf(未找到联系人\n); return; } // 顺序表删除逻辑后面的元素往前覆盖 for (int i pos; i con-size - 1; i) con-data[i] con-data[i 1]; con-size--; // 有效个数减1 printf(删除成功\n); SaveContact(con); // 删除后自动保存 } /************************** 新增功能实现第二篇 **************************/ // 新增功能多条件查找支持按姓名/手机号/地址查找 void SearchContact(Contact* con) { int choose 0; printf(1.按姓名查找 2.按手机号查找 3.按地址查找\n请选择查找方式); scanf(%d, choose); if (choose 1) { // 按姓名查找 char name[MAX_NAME]; printf(请输入查找姓名); scanf(%s, name); int pos FindByName(con, name); if (pos ! -1) { printf(%-10s %-6s %-4d %-12s %-20s\n, con-data[pos].name, con-data[pos].sex, con-data[pos].age, con-data[pos].tel, con-data[pos].addr); } else printf(未找到联系人\n); } else if (choose 2) { // 按手机号查找 char tel[MAX_TEL]; printf(请输入查找手机号); scanf(%s, tel); for (int i 0; i con-size; i) { if (strcmp(con-data[i].tel, tel) 0) { printf(%-10s %-6s %-4d %-12s %-20s\n, con-data[i].name, con-data[i].sex, con-data[i].age, con-data[i].tel, con-data[i].addr); return; } } printf(未找到联系人\n); } else if (choose 3) { // 按地址查找 char addr[MAX_ADDR]; printf(请输入查找地址); scanf(%s, addr); for (int i 0; i con-size; i) { if (strcmp(con-data[i].addr, addr) 0) { printf(%-10s %-6s %-4d %-12s %-20s\n, con-data[i].name, con-data[i].sex, con-data[i].age, con-data[i].tel, con-data[i].addr); return; } } printf(未找到联系人\n); } else { printf(输入错误\n); } } // 新增功能修改联系人支持回车跳过不修改 void ModifyContact(Contact* con) { char name[MAX_NAME]; printf(请输入要修改的姓名); scanf(%s, name); int pos FindByName(con, name); if (pos -1) { printf(未找到联系人\n); return; } printf(修改信息直接回车不修改该项\n); // 调用InputSkip函数实现回车跳过 printf(新姓名); ClearBuff(); InputSkip(con-data[pos].name, MAX_NAME); printf(新性别); InputSkip(con-data[pos].sex, MAX_SEX); // 年龄单独处理int类型不能直接用InputSkip printf(新年龄); char tmp[10] { 0 }; fgets(tmp, 10, stdin); if (strlen(tmp) 1) // 输入不为空才修改 con-data[pos].age atoi(tmp); // 字符串转int printf(新手机号); InputSkip(con-data[pos].tel, MAX_TEL); printf(新地址); InputSkip(con-data[pos].addr, MAX_ADDR); printf(修改成功\n); SaveContact(con); // 修改后自动保存 } // 新增功能清空所有联系人 void ClearAllContact(Contact* con) { con-size 0; // 直接把有效个数设为0即可 SaveContact(con); // 保存空数据到文件 printf(已清空所有联系人\n); } // 新增功能按姓名拼音排序冒泡排序 void SortContact(Contact* con) { for (int i 0; i con-size - 1; i) { for (int j 0; j con-size - i - 1; j) { // strcmp比较字符串大小按拼音升序排列 if (strcmp(con-data[j].name, con-data[j 1].name) 0) { // 交换两个联系人的位置 UserData temp con-data[j]; con-data[j] con-data[j 1]; con-data[j 1] temp; } } } printf(排序完成\n); } /************************** 文件持久化功能 **************************/ // 保存数据到文件二进制方式 void SaveContact(Contact* con) { FILE* pf fopen(contact.dat, wb); // wb二进制写模式 if (pf NULL) { perror(fopen); // 打印错误信息 return; } // 把整个通讯录数组写入文件 fwrite(con-data, sizeof(UserData), con-size, pf); fclose(pf); // 关闭文件 } // 从文件加载历史数据修复第一篇文件不存在闪退bug void LoadContact(Contact* con) { FILE* pf fopen(contact.dat, rb); // rb二进制读模式 if (pf NULL) { // 文件不存在第一次运行直接返回不崩溃 return; } UserData tmp; // 循环读取文件中的每个联系人 while (fread(tmp, sizeof(UserData), 1, pf)) { con-data[con-size] tmp; con-size; } fclose(pf); // 关闭文件 }3. main.c主程序入口#include contact.h void menu() { printf(\n); printf(1.添加联系人 2.删除联系人\n); printf(3.查找联系人 4.修改联系人\n); printf(5.展示联系人 0.退出程序\n); printf(\n); printf(请选择); } int main() { Contact con; // 创建一个通讯录变量 InitContact(con); // 初始化通讯录加载历史数据 int input 0; // 主循环直到用户输入0退出 do { menu(); // 打印菜单 scanf(%d, input); // 读取用户选择 switch (input) { case 1:AddContact(con); break; case 2:DelContact(con); break; case 3:SearchContact(con); break; case 4:ModifyContact(con); break; case 5:ShowContact(con); break; case 0:SaveContact(con); printf(已保存退出程序\n); break; default:printf(输入错误请重新选择\n); ClearBuff(); break; } } while (input ! 0); return 0; }三、所有新增功能汇总新增功能解决的问题实现位置多条件查找只能按姓名查找忘记姓名找不到人SearchContact函数回车跳过修改修改时必须全部重输体验极差InputSkip工具函数重名校验可以添加多个同名联系人AddContact函数中调用FindByName支持空格姓名scanf(%s)遇到空格截断用fgets读取姓名自动保存数据每次操作后手动保存增删改后自动调用SaveContact输入异常处理输入汉字 / 字母死循环ClearBuff工具函数首次运行不闪退文件不存在直接 exit 崩溃LoadContact中文件不存在直接返回四、新手学习总结通过这个完整的通讯录项目你能掌握以下 C 语言核心技能结构体的使用用结构体封装复杂数据顺序表的实现数组 有效元素个数的线性表结构分文件编程C 语言工程的标准组织方式输入输出处理scanf/fgets的区别、缓冲区问题文件操作二进制读写实现数据持久化调试技巧定位并解决常见 bug乱码、闪退、死循环五、放在最后本篇代码GitLuminous/Luminousbegin通讯录系列正式完结三篇博客从零基础写代码、踩坑排查、功能优化、标准工程化完整走完了一个 C 语言小项目的全流程。非常适合大一同学跟着敲、复盘、积累项目经验。下面接着进行数据结构的学习大家一起加油鸭
C 语言通讯录(终版)|新手踩坑全总结 + 最终可运行代码博客简介
发布时间:2026/5/22 6:47:48
系列回顾本系列三篇完整闭环第一篇基础版从零实现增删查改 文件存储踩遍新手所有坑格式符乱码、文件闪退、输入死循环第二篇优化版修复核心 bug新增多条件查找、回车跳过修改等进阶功能本篇终版对底层代码做规范重构 全 bug 修复同时加入逐功能详细讲解。一、工程结构说明标准 C 语言项目分文件写法把不同功能的代码拆到不同文件里方便维护和复用文件作用包含内容contact.h头文件所有宏定义、结构体声明、函数声明相当于项目的 说明书contact.c功能实现文件所有通讯录功能的具体代码增删查改、文件操作等main.c主程序入口菜单界面 主循环只负责调用功能函数二、完整代码 逐功能讲解1. contact.h头文件项目声明// 防止头文件重复包含新手必加否则会报重定义错误 #ifndef __CONTACT_H__ #define __CONTACT_H__ // 引入需要的头文件 #include stdio.h // 输入输出 #include stdlib.h // 内存管理、exit函数 #include string.h // 字符串操作strcmp、strcpy等 // 宏定义统一管理常量后期修改只改这里 #define MAX_NAME 20 // 姓名最大长度 #define MAX_SEX 6 // 性别最大长度 #define MAX_TEL 12 // 手机号最大长度11位终止符 #define MAX_ADDR 30 // 地址最大长度 #define MAX_SL 1000 // 通讯录最大容量 // 联系人信息结构体存储一个人的所有信息 typedef struct UserData { char name[MAX_NAME]; // 姓名 char sex[MAX_SEX]; // 性别 int age; // 年龄修复第一篇char数组存年龄的bug char tel[MAX_TEL]; // 手机号 char addr[MAX_ADDR]; // 地址 } UserData; // 通讯录结构体本质是一个顺序表数组有效元素个数 typedef struct Contact { UserData data[MAX_SL]; // 存储所有联系人的数组 int size; // 当前通讯录中有效联系人的个数 } Contact; // 函数声明所有功能函数都在这里声明其他文件才能调用 // 基础功能 void InitContact(Contact* con); // 初始化通讯录 void AddContact(Contact* con); // 添加联系人 void ShowContact(Contact* con); // 展示所有联系人 void DelContact(Contact* con); // 删除联系人 // 新增功能第二篇实现 void SearchContact(Contact* con); // 多条件查找姓名/电话/地址 void ModifyContact(Contact* con); // 修改联系人支持回车跳过 void ClearAllContact(Contact* con);// 清空所有联系人 void SortContact(Contact* con); // 按姓名排序 // 文件持久化功能 void SaveContact(Contact* con); // 保存数据到文件 void LoadContact(Contact* con); // 从文件加载数据 // 工具函数解决输入bug void ClearBuff(); // 清空输入缓冲区解决死循环 void InputSkip(char* dest, int maxLen); // 回车跳过输入不用全部重输 #endif // !__CONTACT_H__2. contact.c功能实现核心代码#include contact.h /************************** 工具函数解决输入bug **************************/ // 清空输入缓冲区解决第一篇输入汉字死循环的核心bug // 原理把缓冲区里残留的换行、汉字等脏数据全部读走直到遇到换行符 void ClearBuff() { while (getchar() ! \n); } // 新增功能回车跳过输入第二篇核心优化 // 作用修改联系人时直接回车就保留原来的值不用全部重输 // 参数dest-要赋值的目标数组maxLen-数组最大长度 void InputSkip(char* dest, int maxLen) { char tmp[100] { 0 }; // 临时数组存储输入 fgets(tmp, maxLen, stdin); // 读取一行输入包括空行 // 去掉fgets自动读入的换行符 if (tmp[strlen(tmp) - 1] \n) tmp[strlen(tmp) - 1] \0; // 如果输入不为空不是直接回车才覆盖原来的值 if (strlen(tmp) 0) strcpy(dest, tmp); } // 内部工具函数按姓名查找联系人复用避免重复代码 // 返回值找到返回下标没找到返回-1 static int FindByName(Contact* con, char* name) { for (int i 0; i con-size; i) { if (strcmp(con-data[i].name, name) 0) return i; } return -1; } /************************** 基础功能实现 **************************/ // 初始化通讯录程序启动时调用加载历史数据 void InitContact(Contact* con) { con-size 0; // 初始有效个数为0 LoadContact(con); // 从文件加载历史数据修复第一篇首次运行闪退bug } // 添加联系人 void AddContact(Contact* con) { // 先判断通讯录是否已满 if (con-size MAX_SL) { printf(通讯录已满\n); return; } // 输入姓名支持带空格的名字修复第一篇scanf不能输空格的bug printf(请输入姓名); ClearBuff(); // 先清空缓冲区残留的换行 fgets(con-data[con-size].name, MAX_NAME, stdin); // 去掉fgets读入的换行符 if (con-data[con-size].name[strlen(con-data[con-size].name) - 1] \n) con-data[con-size].name[strlen(con-data[con-size].name) - 1] \0; // 新增功能重名校验防止添加重复联系人 if (FindByName(con, con-data[con-size].name) ! -1) { printf(该联系人已存在\n); return; } // 输入其他信息 printf(请输入性别); scanf(%s, con-data[con-size].sex); printf(请输入年龄); scanf(%d, con-data[con-size].age); // 用int存年龄修复格式符乱码bug printf(请输入手机号); scanf(%s, con-data[con-size].tel); printf(请输入地址); scanf(%s, con-data[con-size].addr); con-size; // 有效个数加1 printf(添加成功\n); SaveContact(con); // 添加后自动保存到文件 } // 展示所有联系人 void ShowContact(Contact* con) { if (con-size 0) { printf(通讯录暂无联系人\n); return; } // 打印表头 printf(%-10s %-6s %-4s %-12s %-20s\n, 姓名, 性别, 年龄, 手机号, 地址); // 遍历打印所有联系人 for (int i 0; i con-size; i) { printf(%-10s %-6s %-4d %-12s %-20s\n, con-data[i].name, con-data[i].sex, con-data[i].age, con-data[i].tel, con-data[i].addr); } } // 删除联系人 void DelContact(Contact* con) { char name[MAX_NAME]; printf(请输入要删除的姓名); scanf(%s, name); // 先查找联系人是否存在 int pos FindByName(con, name); if (pos -1) { printf(未找到联系人\n); return; } // 顺序表删除逻辑后面的元素往前覆盖 for (int i pos; i con-size - 1; i) con-data[i] con-data[i 1]; con-size--; // 有效个数减1 printf(删除成功\n); SaveContact(con); // 删除后自动保存 } /************************** 新增功能实现第二篇 **************************/ // 新增功能多条件查找支持按姓名/手机号/地址查找 void SearchContact(Contact* con) { int choose 0; printf(1.按姓名查找 2.按手机号查找 3.按地址查找\n请选择查找方式); scanf(%d, choose); if (choose 1) { // 按姓名查找 char name[MAX_NAME]; printf(请输入查找姓名); scanf(%s, name); int pos FindByName(con, name); if (pos ! -1) { printf(%-10s %-6s %-4d %-12s %-20s\n, con-data[pos].name, con-data[pos].sex, con-data[pos].age, con-data[pos].tel, con-data[pos].addr); } else printf(未找到联系人\n); } else if (choose 2) { // 按手机号查找 char tel[MAX_TEL]; printf(请输入查找手机号); scanf(%s, tel); for (int i 0; i con-size; i) { if (strcmp(con-data[i].tel, tel) 0) { printf(%-10s %-6s %-4d %-12s %-20s\n, con-data[i].name, con-data[i].sex, con-data[i].age, con-data[i].tel, con-data[i].addr); return; } } printf(未找到联系人\n); } else if (choose 3) { // 按地址查找 char addr[MAX_ADDR]; printf(请输入查找地址); scanf(%s, addr); for (int i 0; i con-size; i) { if (strcmp(con-data[i].addr, addr) 0) { printf(%-10s %-6s %-4d %-12s %-20s\n, con-data[i].name, con-data[i].sex, con-data[i].age, con-data[i].tel, con-data[i].addr); return; } } printf(未找到联系人\n); } else { printf(输入错误\n); } } // 新增功能修改联系人支持回车跳过不修改 void ModifyContact(Contact* con) { char name[MAX_NAME]; printf(请输入要修改的姓名); scanf(%s, name); int pos FindByName(con, name); if (pos -1) { printf(未找到联系人\n); return; } printf(修改信息直接回车不修改该项\n); // 调用InputSkip函数实现回车跳过 printf(新姓名); ClearBuff(); InputSkip(con-data[pos].name, MAX_NAME); printf(新性别); InputSkip(con-data[pos].sex, MAX_SEX); // 年龄单独处理int类型不能直接用InputSkip printf(新年龄); char tmp[10] { 0 }; fgets(tmp, 10, stdin); if (strlen(tmp) 1) // 输入不为空才修改 con-data[pos].age atoi(tmp); // 字符串转int printf(新手机号); InputSkip(con-data[pos].tel, MAX_TEL); printf(新地址); InputSkip(con-data[pos].addr, MAX_ADDR); printf(修改成功\n); SaveContact(con); // 修改后自动保存 } // 新增功能清空所有联系人 void ClearAllContact(Contact* con) { con-size 0; // 直接把有效个数设为0即可 SaveContact(con); // 保存空数据到文件 printf(已清空所有联系人\n); } // 新增功能按姓名拼音排序冒泡排序 void SortContact(Contact* con) { for (int i 0; i con-size - 1; i) { for (int j 0; j con-size - i - 1; j) { // strcmp比较字符串大小按拼音升序排列 if (strcmp(con-data[j].name, con-data[j 1].name) 0) { // 交换两个联系人的位置 UserData temp con-data[j]; con-data[j] con-data[j 1]; con-data[j 1] temp; } } } printf(排序完成\n); } /************************** 文件持久化功能 **************************/ // 保存数据到文件二进制方式 void SaveContact(Contact* con) { FILE* pf fopen(contact.dat, wb); // wb二进制写模式 if (pf NULL) { perror(fopen); // 打印错误信息 return; } // 把整个通讯录数组写入文件 fwrite(con-data, sizeof(UserData), con-size, pf); fclose(pf); // 关闭文件 } // 从文件加载历史数据修复第一篇文件不存在闪退bug void LoadContact(Contact* con) { FILE* pf fopen(contact.dat, rb); // rb二进制读模式 if (pf NULL) { // 文件不存在第一次运行直接返回不崩溃 return; } UserData tmp; // 循环读取文件中的每个联系人 while (fread(tmp, sizeof(UserData), 1, pf)) { con-data[con-size] tmp; con-size; } fclose(pf); // 关闭文件 }3. main.c主程序入口#include contact.h void menu() { printf(\n); printf(1.添加联系人 2.删除联系人\n); printf(3.查找联系人 4.修改联系人\n); printf(5.展示联系人 0.退出程序\n); printf(\n); printf(请选择); } int main() { Contact con; // 创建一个通讯录变量 InitContact(con); // 初始化通讯录加载历史数据 int input 0; // 主循环直到用户输入0退出 do { menu(); // 打印菜单 scanf(%d, input); // 读取用户选择 switch (input) { case 1:AddContact(con); break; case 2:DelContact(con); break; case 3:SearchContact(con); break; case 4:ModifyContact(con); break; case 5:ShowContact(con); break; case 0:SaveContact(con); printf(已保存退出程序\n); break; default:printf(输入错误请重新选择\n); ClearBuff(); break; } } while (input ! 0); return 0; }三、所有新增功能汇总新增功能解决的问题实现位置多条件查找只能按姓名查找忘记姓名找不到人SearchContact函数回车跳过修改修改时必须全部重输体验极差InputSkip工具函数重名校验可以添加多个同名联系人AddContact函数中调用FindByName支持空格姓名scanf(%s)遇到空格截断用fgets读取姓名自动保存数据每次操作后手动保存增删改后自动调用SaveContact输入异常处理输入汉字 / 字母死循环ClearBuff工具函数首次运行不闪退文件不存在直接 exit 崩溃LoadContact中文件不存在直接返回四、新手学习总结通过这个完整的通讯录项目你能掌握以下 C 语言核心技能结构体的使用用结构体封装复杂数据顺序表的实现数组 有效元素个数的线性表结构分文件编程C 语言工程的标准组织方式输入输出处理scanf/fgets的区别、缓冲区问题文件操作二进制读写实现数据持久化调试技巧定位并解决常见 bug乱码、闪退、死循环五、放在最后本篇代码GitLuminous/Luminousbegin通讯录系列正式完结三篇博客从零基础写代码、踩坑排查、功能优化、标准工程化完整走完了一个 C 语言小项目的全流程。非常适合大一同学跟着敲、复盘、积累项目经验。下面接着进行数据结构的学习大家一起加油鸭