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

news2026/5/22 6:47:59
系列回顾本系列三篇完整闭环第一篇基础版从零实现增删查改 文件存储踩遍新手所有坑格式符乱码、文件闪退、输入死循环第二篇优化版修复核心 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 语言小项目的全流程。非常适合大一同学跟着敲、复盘、积累项目经验。下面接着进行数据结构的学习大家一起加油鸭

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2633963.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…