本项目基于C 语言 单向链表实现通讯录管理系统采用分层架构设计支持人员信息增删改查、文件持久化存储配套完整的调试与 BUG 排查方案是 Linux C 开发入门的经典工程实践项目。完整代码在最下面。一、项目总览与架构设计1.1 核心需求分析通讯录系统的核心功能围绕联系人信息管理展开基础需求如下增添加新联系人支持姓名、手机号、性别、年龄等信息录入删按指定条件姓名 / 手机号删除联系人改修改已有联系人的信息查按姓名 / 手机号查询并展示联系人详情遍历格式化展示所有联系人列表持久化程序退出时保存数据到文件启动时自动加载数据交互控制台菜单支持用户选择操作1.2 分层架构设计项目采用经典的三层架构实现数据、逻辑、接口解耦提升代码可维护性与可调试性。层级职责特点数据层链表层定义数据结构实现链表基础操作增删节点、遍历、销毁只负责数据存储与组织不包含任何业务规则接口层API 层封装链表底层操作向上提供统一的函数接口屏蔽指针细节业务层无需直接操作链表节点业务逻辑层处理业务规则如数据合法性校验、重复判断、异常分支处理核心业务规则都在这一层实现交互层UI 层菜单打印、用户输入接收、结果展示只负责交互不包含核心逻辑架构设计的核心优势分层解耦修改链表实现不影响业务逻辑修改业务规则不改动底层数据结构便于调试问题可快速定位到对应层级可扩展性强后续新增功能只需在对应层级补充代码二、底层链表实现2.1 核心结构体定义1联系人信息结构体存储单个联系人的所有属性作为链表节点的数据域。typedef struct Contact { char name[32]; // 姓名 char phone[20]; // 手机号 char gender[8]; // 性别 int age; // 年龄 }Contact;2链表节点结构体单向链表节点包含数据域和指针域。// 链表节点结构体 typedef struct Node { Contact data; // 数据域联系人信息 struct Node* next; // 指针域指向后继节点 } Node;3链表管理结构体封装链表头指针与节点数量简化操作。// 通讯录链表管理结构体 typedef struct AddressBook { Node* head; int count; } AddressBook;2.2 链表基础操作函数数据层仅实现最基础的链表原子操作不包含业务判断init_book()初始化通讯录创建空链表头指针置 NULL计数清 0create_node(Contact data)申请内存创建新节点返回节点指针insert_head(Node **head, Node *new_node)头插法插入节点insert_tail(Node **head, Node *new_node)尾插法插入节点通讯录推荐使用保持添加顺序free_node(Node *node)释放单个节点内存destroy_book(Node **head)销毁整个链表释放所有节点内存防止内存泄漏关键注意点每次malloc后必须判断返回值是否为 NULL处理内存分配失败的情况尾节点的next必须初始化为NULL避免遍历出现野指针程序退出前必须遍历释放所有节点杜绝内存泄漏三、接口层设计与实现3.1 接口层的作用与设计原则接口层是业务逻辑与底层链表之间的桥梁核心作用是封装底层细节提供标准化调用入口。设计原则入参校验所有接口先判断参数合法性如指针非空、字符串长度合法状态返回函数返回状态码0 成功-1 失败-2 不存在等便于上层处理屏蔽指针业务层无需操作Node*只通过接口函数传递数据3.2 核心接口函数定义// 添加联系人传入联系人数据返回状态码 int api_add_contact(AddressBook *book, Contact *data); // 删除联系人按姓名删除返回状态码 int api_delete_contact(AddressBook *book, const char *name); // 查询联系人按姓名查找返回找到的节点数据通过出参带回 int api_search_contact(AddressBook *book, const char *name, Contact *result); // 修改联系人按姓名查找并更新信息 int api_modify_contact(AddressBook *book, const char *name, Contact *new_data); // 遍历所有联系人回调函数方式/直接打印 int api_list_all(AddressBook *book); // 获取联系人总数 int api_get_count(AddressBook *book);四、业务逻辑层分析与实现4.1 业务层职责业务层是系统的核心负责所有业务规则判断调用接口层完成数据操作。数据合法性校验业务规则判断如姓名是否重复异常场景处理多步操作封装4.2 核心业务逻辑实现1添加联系人业务逻辑校验输入数据姓名不能为空、手机号长度合法、年龄在合理范围0~150查重判断调用查询接口检查姓名 / 手机号是否已存在调用接口层api_add_contact完成插入返回执行结果给交互层2删除联系人业务逻辑校验删除条件姓名非空判断链表是否为空空链表直接返回 “无数据”调用接口层执行删除根据返回值反馈 “删除成功 / 联系人不存在”3修改联系人业务逻辑先查询目标联系人是否存在校验新数据的合法性调用接口层执行修改4查询与遍历业务逻辑查询支持精确查询也可扩展模糊查询遍历格式化输出对齐列宽提升可读性五、人员操作功能调试与运行5.1 编译与调试环境编译命令gcc -g contact.c main.c -o contact_book-g参数生成调试信息支持 gdb 调试调试工具Linux 下使用gdbWindows 下可使用 VS、Dev-C 等 IDE 调试5.2 增改查功能调试要点添加功能调试断点位置create_node、insert_tail函数入口检查项节点内存是否分配成功、数据是否正确拷贝、next 指针是否置 NULL、头指针是否正确更新常见问题字符串赋值用了导致地址赋值错误必须用strcpy/strncpy查询功能调试断点位置遍历循环处、字符串比较处检查项循环是否遍历所有节点、字符串比较是否使用strcmp不能用常见问题遍历提前终止、未处理空链表情况修改功能调试检查项找到节点后数据是否正确覆盖、未找到节点是否返回对应错误码5.3 基础功能测试用例测试场景预期结果正常添加 1 条联系人添加成功总数 1添加姓名重复的联系人提示已存在添加失败空链表查询 / 删除提示无数据不崩溃修改不存在的联系人提示联系人不存在输入非法数据空姓名、负数年龄校验不通过提示重新输入六、删除功能调试与 BUG 解决方案链表删除是最高频出 BUG 的环节必须分场景处理。6.1 链表删除的三种场景删除头节点特殊处理将链表头指针指向第二个节点删除中间节点找到前驱节点prev执行prev-next cur-next删除尾节点前驱节点的next置为NULL6.2 常见 BUG 与排查方法写完代码可以查一下自己出现了哪些错误。常见 BUG现象原因解决方案段错误Segmentation Fault删除操作时程序崩溃空链表直接操作、访问 NULL 指针的 next函数入口先判空所有指针访问前校验非空删除头节点后数据丢失删除第一个人后链表无法遍历未更新头指针头指针变成野指针删除头节点时执行*head cur-next删除后遍历死循环执行删除后遍历停不下来删除尾节点时前驱节点 next 未置 NULL尾节点删除后手动将prev-next NULL明明存在却删不掉输入正确姓名但提示不存在字符串比较用了比较的是地址而非内容使用strcmp(name, cur-data.name) 0判断相等内存泄漏多次增删后内存占用升高删除节点只改指针未 free 节点内存移除节点后必须执行free(cur)释放内存删错节点删除的不是目标联系人遍历查找逻辑错误、指针偏移出错gdb 逐步打印节点姓名跟踪指针走向BUG 排查通用思路分段打印在关键节点打印指针地址、节点数据缩小问题范围gdb 单步执行跟踪每一步指针变化定位出错的代码行边界优先优先测试空链表、单节点链表、尾节点等边界场景七、文件持久化保存与加载7.1 接口层文件读写接口1保存文件接口遍历链表将每个节点的数据写入文件推荐二进制存储读写效率高、结构体直接读写。// 接口层保存通讯录到文件 int api_save_to_file(AddressBook *book, const char *filename) { FILE *fp fopen(filename, wb); // 二进制写模式 if(fp NULL) return -1; Node* cur book-head; while(cur ! NULL) { fwrite(cur-data, sizeof(Contact), fp); // 写入单条联系人数据 cur cur-next; } fclose(fp); return 0; }2加载文件接口从文件循环读取数据创建节点插入链表重建内存中的数据结构。// 接口层从文件加载通讯录 int api_load_from_file(AddressBook *book, const char *filename) { FILE* fp fopen(filename, rb); // 二进制读模式 if(fp NULL) return -1; // 文件不存在时返回初始化空链表 Contact tmp; while(fread(tmp, sizeof(Contact), 1, fp) 1) { api_add_contact(book, tmp); // 调用已有添加接口插入链表 } fclose(fp); return 0; }7.2 业务层持久化逻辑封装启动自动加载程序初始化时自动调用加载函数读取本地文件退出自动保存用户选择 “退出” 选项时自动执行保存操作手动保存菜单提供 “保存” 选项支持随时手动存档7.3 边界与异常处理文件不存在首次运行无数据文件时正常初始化空通讯录不报错文件为空读取 0 条数据返回空链表读写权限不足fopen失败时给出明确提示数据损坏可扩展校验逻辑异常文件提示数据损坏八、项目整体联调与运行8.1 完整运行流程程序启动 ↓ 初始化通讯录结构体 ↓ 加载本地数据文件 → 无文件则创建空通讯录 ↓ 打印主菜单等待用户输入 ↓ 用户选择功能 → 执行业务逻辑 → 展示结果 ↓ 循环菜单直到用户选择退出 ↓ 自动保存数据到文件 ↓ 释放链表所有内存 → 程序退出8.2 全场景测试用例首次运行测试无数据文件程序正常启动添加数据后退出再打开数据不丢失边界数据测试0 个联系人、1 个联系人、大量联系人100 条异常输入测试菜单输入字母、超长字符串输入程序不崩溃异常退出测试强制关闭程序后下次启动保留最后一次保存的数据重复操作测试反复增删改查链表结构正常无内存泄漏8.3 内存与健壮性优化内存泄漏检测使用valgrind工具检测命令valgrind --leak-checkfull ./contact_book要求程序正常退出后所有堆内存都被释放输入健壮性限制输入字符串长度使用fgets替代scanf避免缓冲区溢出错误提示友好所有失败操作给出明确原因不输出无意义的报错完整代码address_book.c#include stdio.h #include stdlib.h #include string.h /************************* 宏定义 *************************/ #define NAME_LEN 32 #define PHONE_LEN 20 #define GENDER_LEN 8 #define DATA_FILE contact.dat /************************* 数据结构定义 *************************/ // 联系人信息结构体数据域 typedef struct { char name[NAME_LEN]; char phone[PHONE_LEN]; char gender[GENDER_LEN]; int age; } Contact; // 链表节点结构体 typedef struct Node { Contact data; // 数据域 struct Node* next; // 指针域指向后继节点 } Node; // 通讯录管理结构体封装头指针与数量 typedef struct { Node* head; // 链表头指针 int count; // 当前联系人总数 } AddressBook; /************************* 数据层链表基础操作 *************************/ /** * brief 初始化空通讯录 */ void init_book(AddressBook *book) { book-head NULL; book-count 0; } /** * brief 创建新的链表节点 * param data 联系人数据 * return 新节点指针失败返回NULL */ Node* create_node(Contact *data) { Node *new_node (Node*)malloc(sizeof(Node)); if(new_node NULL) { printf(内存分配失败\n); return NULL; } // 拷贝数据初始化指针 memcpy(new_node-data, data, sizeof(Contact)); new_node-next NULL; return new_node; } /** * brief 头插法插入节点 * return 0成功 -1失败 */ int insert_head(AddressBook *book, Node *new_node) { if(book NULL || new_node NULL) return -1; // 空链表新节点直接作为头节点 if(book-head NULL) { book-head new_node; } else { new_node-next book-head-next; book-head-next new_node; } book-count ; return 0; } /** * brief 销毁整个链表释放所有内存防止内存泄漏 */ void destroy_book(AddressBook *book) { if(book NULL) return ; Node *cur book-head; while(cur ! NULL) { Node *tmp cur-next; free(cur); cur tmp; } book-head NULL; book-count 0; } /************************* 接口层功能封装 *************************/ /** * brief 添加联系人接口 * return 0成功 -1失败 */ int api_add_contact(AddressBook *book, Contact *data) { if(book NULL || data NULL) return -1; Node *new_node create_node(data); if(new_node NULL) return -1; return insert_head(book, new_node); } /** * brief 按姓名删除联系人接口 * return 0成功 -1参数/空链表错误 -2联系人不存在 */ int api_delete_contact(AddressBook *book, const char *name) { if(book NULL || name NULL || book-head NULL) return -1; Node *prev NULL; Node *cur book-head; // 遍历查找目标联系人 while(cur ! NULL) { if (strcmp(cur-data.name, name) 0) { break; } prev cur; cur cur-next; } if(cur NULL) return -2; // 未找到 // 执行删除分场景处理 if(prev NULL) { // 场景1删除头节点 book-head cur-next; } else { // 场景2删除中间/尾节点 prev-next cur-next; } free(cur); book-count--; return 0; } /** * brief 按姓名查询联系人 * param result 出参带回找到的联系人数据 * return 0成功 -1未找到 */ int api_search_contact(AddressBook *book, const char *name, Contact *result) { if(book NULL || name NULL || result NULL) return -1; Node *cur book-head; while(cur ! NULL) { if(strcmp(cur-data.name, name) 0) { memcpy(result, cur-data, sizeof(Contact)); return 0; } cur cur-next; } return -1; } /** * brief 修改联系人信息 * return 0成功 -1失败 */ int api_modify_contact(AddressBook *book, const char *name, Contact *new_data) { if (book NULL || name NULL || new_data NULL) return -1; Node *cur book-head; while(cur ! NULL) { if(strcmp(cur-data.name, name) 0) { // 保留原姓名更新其他字段 strncpy(cur-data.phone, new_data-phone, PHONE_LEN - 1); strncpy(cur-data.gender, new_data-gender, GENDER_LEN - 1); return 0; } cur cur-next; } return -1; } /** * brief 遍历并打印所有联系人 */ void api_list_all(AddressBook *book) { if (book NULL || book-head NULL) { printf(通讯录为空暂无联系人\n); return ; } printf(\n 联系人列表共%d条\n, book-count); printf(%-12s %-15s %-6s %-4s\n, 姓名, 手机号, 性别, 年龄); printf(----------------------------------------\n); Node *cur book-head; while(cur ! NULL) { printf(%-12s %-15s %-6s %-4d\n, cur-data.name, cur-data.phone, cur-data.gender, cur-data.age); cur cur-next; } printf(\n); } /** * brief 保存通讯录到文件 * return 0成功 -1失败 */ int api_save_to_file(AddressBook *book, const char *filename) { if (book NULL || filename NULL) return -1; FILE *fp fopen(filename, wb); // 二进制写模式 if(fp NULL) { perror(保存文件失败); return -1; } Node *cur book-head; while(cur ! NULL) { fwrite(cur-data, sizeof(Contact), 1, fp); cur cur-next; } fclose(fp); return 0; } /** * brief 从文件加载通讯录 * return 0成功 -1文件不存在/读取失败 */ int api_load_from_file(AddressBook *book, const char *filename) { if (book NULL || filename NULL) return -1; FILE *fp fopen(filename, rb); if (fp NULL) { // 文件不存在属于正常情况首次运行不报错 return -1; } Contact temp; while(fread(temp, sizeof(Contact), 1, fp) 1) { api_add_contact(book, temp); } fclose(fp); return 0; } /************************* 业务逻辑层 *************************/ /** * brief 校验联系人数据合法性 * return 1合法 0非法 */ int biz_validate_contact(Contact *data) { if (data NULL) return 0; if (strlen(data-name) 0) return 0; // 姓名不能为空 if (data-age 0 || data-age 150) return 0; // 年龄范围校验 if (strlen(data-phone) 0) return 0; // 手机号不能为空 return 1; } /** * brief 添加联系人业务逻辑查重 校验 调用接口 * return 0成功 -1数据非法 -2姓名重复 */ int biz_add_contact(AddressBook *book, Contact *data) { // 1. 数据合法性校验 if (!biz_validate_contact(data)) { return -1; } // 2. 姓名查重 Contact tmp; if (api_search_contact(book, data-name, tmp) 0) { return -2; } // 3. 调用接口层执行添加 return api_add_contact(book, data); } /************************* 交互层菜单与工具函数 *************************/ // 清空输入缓冲区避免scanf残留换行影响后续输入 void clear_input_buffer() { int c; while((c getchar())! \n c ! EOF); } // 打印主菜单 void print_menu() { printf(\n 通讯录管理系统 \n); printf(1. 添加联系人\n); printf(2. 删除联系人\n); printf(3. 修改联系人\n); printf(4. 查询联系人\n); printf(5. 查看所有联系人\n); printf(6. 保存数据到文件\n); printf(0. 退出系统\n); printf(\n); printf(请输入选项); } // 录入联系人信息 void input_contact(Contact *data) { printf(请输入姓名); scanf(%s, data-name); printf(请输入手机号); scanf(%s, data-phone); printf(请输入性别); scanf(%s, data-gender); printf(请输入年龄); scanf(%d, data-age); clear_input_buffer(); } /************************* 主函数程序入口 *************************/ int main() { AddressBook book; init_book(book); // 启动自动加载本地数据 api_load_from_file(book, DATA_FILE); printf(系统启动完成已加载%d条联系人数据\n, book.count); int choice -1; char name_buf[NAME_LEN]; Contact tmp_data; while(1) { print_menu(); scanf(%d, choice); clear_input_buffer(); switch(choice) { case 1: // 添加联系人 printf(\n--- 添加联系人 ---\n); input_contact(tmp_data); int ret biz_add_contact(book, tmp_data); if (ret 0) { printf(添加成功\n); } else if (ret -1) { printf(添加失败数据不合法\n); } else if (ret -2) { printf(添加失败该姓名已存在\n); } break; case 2: // 删除联系人 printf(\n--- 删除联系人 ---\n); printf(请输入要删除的姓名); scanf(%s, name_buf); clear_input_buffer(); ret api_delete_contact(book, name_buf); if (ret 0) { printf(删除成功\n); } else if (ret -2) { printf(删除失败联系人不存在\n); } else { printf(删除失败\n); } break; case 3: // 修改联系人 printf(\n--- 修改联系人 ---\n); printf(请输入要修改的姓名); scanf(%s, name_buf); clear_input_buffer(); if (api_search_contact(book, name_buf, tmp_data) ! 0) { printf(联系人不存在\n); break; } printf(请输入新的信息姓名不可修改\n); printf(新手机号); scanf(%s, tmp_data.phone); printf(新性别); scanf(%s, tmp_data.gender); printf(新年龄); scanf(%d, tmp_data.age); clear_input_buffer(); if (api_modify_contact(book, name_buf, tmp_data) 0) { printf(修改成功\n); } else { printf(修改失败\n); } break; case 4: // 查询联系人 printf(\n--- 查询联系人 ---\n); printf(请输入要查询的姓名); scanf(%s, name_buf); clear_input_buffer(); if (api_search_contact(book, name_buf, tmp_data) 0) { printf(\n查询结果\n); printf(姓名%s\n, tmp_data.name); printf(手机号%s\n, tmp_data.phone); printf(性别%s\n, tmp_data.gender); printf(年龄%d\n, tmp_data.age); } else { printf(未找到联系人\n); } break; case 5: // 查看所有 api_list_all(book); break; case 6: // 手动保存 if (api_save_to_file(book, DATA_FILE) 0) { printf(数据已保存到 %s\n, DATA_FILE); } break; case 0: // 退出 printf(\n正在保存数据...\n); api_save_to_file(book, DATA_FILE); destroy_book(book); // 释放内存 printf(系统已退出欢迎下次使用\n); return 0; default: printf(输入无效请输入0-6之间的数字\n); break; } } return 0; }