当前位置: 首页 > article >正文

C 语言通讯录(终版)|新手踩坑全总结 + 最终可运行代码博客简介

系列回顾本系列三篇完整闭环第一篇基础版从零实现增删查改 文件存储踩遍新手所有坑格式符乱码、文件闪退、输入死循环第二篇优化版修复核心 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 语言通讯录(终版)|新手踩坑全总结 + 最终可运行代码博客简介

系列回顾 本系列三篇完整闭环: 第一篇(基础版):从零实现增删查改 文件存储,踩遍新手所有坑(格式符乱码、文件闪退、输入死循环);第二篇(优化版)&#xff1…...

天勤策略钉钉告警:交易信号与异常通知怎么分流

前言 策略上云之后,我和同事最怕两件事:一是真出事了没人知道,二是没事也被钉钉刷屏刷到麻木。最早接推送时图省事,在 wait_update 里每次 last_price 变就发一条,一个活跃品种白天能几百条,两天之后群里全…...

LEFT JOIN 中 ON 与 WHERE 过滤的差异

在 MySQL 数据库开发中,LEFT JOIN(左外连接)是一个最常被误用的语法。许多开发者往往习惯性地将所有过滤条件一股脑地往 ON 后面塞,或者为了排版好看将条件全部扔到 WREHRE 里面。 这种模糊的逻辑在普通内连接(INNER J…...

宇树go2机械狗远程操控联网问题

用手机“Unitree Go”app的wifi模式,让狗和电脑连接同一个wifi,使其处于同一个局域网下。要求wifi名和密码无中文。然后在本地电脑powershell输入ipconfig查询本机局域网网段,确认机械狗同一网段 IP 地址。终端执行命令:ssh unitr…...

如何找到最适合你的私有化IM?

跳出公有云的舒适区,决心搭建私有化IM,并不是一件能一蹴而就的事。市面上打着私有化旗号的软件鱼龙混杂,有的安装环境要求极高,有的功能华而不实。如何在复杂的选型迷雾中,找到最适合组织基因的那一款?你可…...

一幅精细绝伦的[城市或地点]微缩模型

提示词: 一幅精细绝伦的[城市或地点]微缩模型,无缝搭建于质朴的木桌之上,仿佛整个场景都是实时手工制作而成。场景囊括了[城市或地点]最具标志性的地标、建筑、街道、交通、文化元素和氛围,周围环绕着密 地址:https://…...

淮南家长必看:淮南哪里学少儿编程靠谱?原来这样选才不踩坑。

说实话,很多淮南家长送孩子学编程,心里是没底的。因为编程不像钢琴、画画,能当场弹一首或画一张给你看。孩子到底学了啥、学得怎么样,家长往往两眼一抹黑。今天我不推荐任何一家机构,只跟你分享三个普通人一眼就能看懂…...

2026年最佳手机阅读器推荐:付费也值得的精品选择

在数字时代,阅读方式正在发生深刻变革。随着电子书、在线文章和多媒体内容的兴起,人们越来越倾向于通过智能手机进行阅读。然而,并非所有的阅读器都能提供优质的阅读体验。今天,我们将聚焦于一款即便付费也绝对物超所值的手机阅读…...

DDD 中的代码组织:按技术层分 vs 按领域模块分,哪种才是正解?

前言 在实践领域驱动设计(DDD)时,你可能见过两种截然不同的代码组织方式:一种是传统的按技术层划分文件夹,另一种是按业务模块划分文件夹。两种写法的人都声称自己在做 DDD,那到底哪种更合理?本…...

从CDP“3A”到千亿美元目标:联想集团的创新路径与AI原生转型

在全球产业链加速重构、人工智能技术范式快速迭代的背景下,中国企业的创新能力正成为各界关注的焦点。当被问及“哪些中国企业创新做得不错”时,有一家科技企业凭借其在绿色低碳、供应链协同以及混合式人工智能领域的系统性突破,给出了具有说…...

油雾净化设备哪家技术更专业

在机械加工、五金锻造、热处理等工业生产场景中,机床切削、乳化液喷淋、高温加工会持续产生大量工业油雾。悬浮在车间内的油雾不仅会腐蚀生产设备、污染生产环境,还会刺激人体呼吸道,危害操作人员身体健康,同时超标排放还会违反环…...

解密Palantir系列一:1. 决策的三元闭环

解密Palantir系列一:1. 决策的三元闭环 第一性问题企业真正缺的是更多数据,还是让数据变成正确行动的闭环?很多人第一次理解 Palantir,会把它归类成“大数据公司”“AI 公司”“可视化工具”或“咨询公司”。这些说法都只碰到了一…...

如何做好费用率数据分析?巧用费用率研判企业盈利现状

企业经营发展过程中,盈利水平高低直接决定长远发展实力,而费用率数据是看透企业真实盈利水平最直观、最核心的指标。很多经营者在日常管理中,往往只看重账面营收的增长,却忽略了费用率数据的深层分析与解读,最终出现营…...

(QBuffer配合 QDataStream)二进制序列化

QByteArray arr; QBuffer buf(&arr); buf.open(QIODevice::WriteOnly); QDataStream out(&buf); out << QString(“hello”) << 123; // 序列化 // 反序列化 buf.seek(0); QDataStream in(&buf); QString s; int n; in >> s >> n;...

VMware虚拟机安装及配置

密码 # 设置 root 用户密码 sudo passwd root修改国内镜像源 在 Ubuntu 24.04 之前&#xff0c;Ubuntu 的软件源配置文件路径为 /etc/apt/sources.list&#xff1b;从 Ubuntu 24.04 开始&#xff0c;Ubuntu 的软件源配置文件变更为 DEB822 格式&#xff0c;路径为 /etc/apt/so…...

专业做绝对值编码器的服务商

在工业自动化领域&#xff0c;绝对值编码器是不可或缺的关键组件。它能够直接输出轴或直线运动的“绝对位置”&#xff0c;断电后位置信息不会丢失&#xff0c;每次上电都能立刻知道当前的精确坐标&#xff0c;这使得其在各种精密应用中具有无可替代的优势。本文将通过具体数据…...

《从 0 实现 SGLang》第 1 篇 · LLM 推理引擎到底在做什么

千行代码&#xff0c;一步步搭出一个现代 LLM 推理引擎&#xff0c;吃透大模型推理的每一项关键技术。 本阶段目标 — 最简推理实现 用最朴素的方式把端到端推理跑通&#xff1a;先搭起整体框架&#xff0c;再逐个模块替换为完整实现。整个阶段共 5 篇短文&#xff1a; 序号…...

2026年必看:六款热门AI编程工具横评,Trae与Cursor怎么选

2026年必看&#xff1a;六款热门AI编程工具横评&#xff0c;Trae与Cursor怎么选AI编程工具正从辅助插件进化为全流程开发核心&#xff0c;2026年市场进入智能体协作新阶段。本文精选6款主流AI编程工具&#xff0c;从核心功能、协作模式、适配场景等维度深度解析&#xff0c;帮开…...

第一学期结果

关注 1.从安涛老师前三期视频中了解了方向2.从b站了解了555的内部结构3.仿真。4.低通滤波器的基本原理&#xff1a;一、核心定义只允许低频信号顺利通过&#xff0c;阻挡、衰减高频信号的电路。 你电路里作用&#xff1a;滤掉方波里的高频谐波&#xff0c;留下低频基波&#xf…...

2026.5.21【MIPI D-PHY】一、D-PHY 简介

一、简介 MIPI&#xff1a;全称移动行业处理器接口&#xff08;Mobile Industry Processor Interface&#xff09;。MIPI是由MIPI联盟发起的为移动应用处理器制定的开放标准。 MIPI可分为物理层和逻辑层两大部分。 MIPI按照物理层&#xff08;Physical Standard&#xff09;划分…...

由一次构建 OpenEuler 22.03 dnf源所了解到的

零、说在前面今天在安装 Milvus 的时候&#xff0c;因为部分插件下载过慢&#xff0c;需要重建国内 yum/dnf 源&#xff0c;按照常规的方式重建后报出了一些奇怪的报错。通过这些报错让我了解到了 OpenEuler 22.03 的不同版本在构建 yum/dnf 源的时候是存在区别的。因此将我的处…...

Delft3D建模、水动力模拟方法及地表水环境影响评价:岸线绘制与导入、非结构化计算网格生成、水下地形数据处理等前处理操作;水动力与污染物对流扩散模拟的参数设置、边界条件设定及模型率定验证

查看原文>>>https://mp.weixin.qq.com/s/_CiPDK_oXaAGxVfu2qk6ew 前言 本文以地表水数值模拟软件Delft3D 4.03.00操作为主要内容&#xff0c;强调地表水水动力建模、基础资料的获取、边界条件设定、模型率定和验证、数据分析和处理等关键环节。通过对案例模型的实操…...

Token聚合平台 vs 传统云 vs AI原生云,AI推理应用怎么选?

在大模型能力深度融入生产环境的当下&#xff0c;后端 AI 架构的选择往往决定了应用的生死。从早期的“调用一个接口”到如今复杂的智能体&#xff08;Agent&#xff09;工作流&#xff0c;开发团队在底座选型上面临着两条截然不同的演进路径&#xff1a;一条是追求便利与极致轻…...

windows VS2026 编译32位 onnxRuntime

打开命令行终端&#xff0c;执行以下命令克隆官方仓库并初始化子模块&#xff08;--recursive 参数非常重要&#xff0c;否则会因为缺少依赖导致编译失败&#xff09;&#xff1a;git clone --recursive https://github.com/microsoft/onnxruntime.git进入目录&#xff1a;cd o…...

影刀RPA 从0到1:自动化系统架构收敛与工程化演进总结

影刀RPA 从0到1&#xff1a;自动化系统架构收敛与工程化演进总结 作者&#xff1a;林焱 写到这里。 这个系列其实已经慢慢进入后半段了。 前面聊了很多内容。 包括&#xff1a; 浏览器池 节点集群 Redis 队列 调度系统 容灾恢复 日志监控 性能治理 很多人刚开始接…...

2026年想做美缝施工?专业靠谱的美缝施工究竟哪家好?

在装修领域&#xff0c;美缝施工虽看似是小工程&#xff0c;却对家居整体美观度和实用性影响重大。然而&#xff0c;美缝行业乱象丛生&#xff0c;让众多业主在选择美缝施工团队时犯了难。2026年若想做美缝施工&#xff0c;怎样才能选到专业靠谱的团队呢&#xff1f;下面为大家…...

从低空协议劫持实战看 MAVLink 二进制审计在飞控发布环节的必要性

攻防实测复盘&#xff1a;协议劫持漏洞成因解析无人机接管攻击的本质不是高危漏洞&#xff0c;而是协议与生俱来的默认信任逻辑。近期多项低空攻防实测中&#xff0c;攻击者依托通用射频采集设备&#xff0c;即可持续捕获空口无线交互数据&#xff0c;实现对飞行设备的非正常控…...

谷歌AI掌门竟是死敌大股东!“DeepMind黑手党”四年卷走140亿美元

谷歌AI掌门竟是死敌大股东&#xff0c;“DeepMind黑手党”四年卷走140亿美元&#xff01;就在刚刚&#xff0c;全球科技圈爆出惊人消息——谷歌AI最高掌门人、DeepMind创始人、诺贝尔奖得主Demis Hassabis&#xff0c;被挖出是其最大死敌、超级独角兽Anthropic的早期隐秘金主&a…...

GPT5.5每次推理只激活部分参数MoE路由策略完整拆解

做多模型架构对比测试时用了cc.877ai.cn这个AI模型聚合平台&#xff0c;一站接入多个模型方便对比不同架构策略在实际任务中的表现差异。GPT-5.5是OpenAI首个从零完整重训的基础模型。大多数人关注"变强了多少"但更值得关注的是"怎么变强的"。MoE路由策略是…...

SpaceX披露IPO招股书:400亿美元数据中心交易、600亿美元收购Cursor,轨道AI计算挑战待解

拿下Anthropic算力大单&#xff1a;每月12.5亿美元&#xff0c;连付3年&#xff0c;双方均可叫停今年5月&#xff0c;SpaceX与Anthropic就访问COLOSSUS和COLOSSUS II两大大型数据中心的算力访问达成了云服务协议。根据协议&#xff0c;Anthropic同意在2029年5月之前每月向Space…...