C语言学习笔记20260714-分文件通讯录完整实现(程序退出不丢失通讯录)
🎯 学习目标
本次学习的核心目标是实现通讯录数据的持久化存储。在传统的内存操作程序中,数据仅存在于运行时的内存空间,一旦程序退出,所有数据随之丢失。通过引入文件操作,我们需要达成以下效果:
- 数据保留:程序退出后,通讯录数据完整保存在本地文件中。
- 自动加载:下次启动程序时,自动从文件读取历史数据,无需手动重新录入。
- 实时同步:任何增、删、改操作完成后,立即同步更新文件,确保数据一致性。
💡 核心思路
实现持久化并非复杂的算法问题,而是对数据流向的控制。整体设计遵循“启动加载,变动保存”的原则:
- 二进制读写:使用
fread和fwrite进行二进制操作。相比文本格式化读写,二进制方式无需处理字符串解析、空格换行等格式问题,直接按内存结构读写,效率高且不易出错。 - 启动加载 (
LoadContact):在main函数初始化通讯录后,立即尝试打开数据文件。若文件存在,读取人数和人员数组;若文件不存在(首次运行),则保持空通讯录状态,不报错。 - 变动保存 (
SaveContact):在AddContact、DelContact、ModifyContact、SortContact等修改内存数据的函数末尾,统一调用保存函数。将当前人数和人员数组覆盖写入文件。
🔍 关键代码解析
1. 数据加载:LoadContact
该函数负责在程序启动时“恢复现场”。
voidLoadContact(Contact*pc){FILE*pf=fopen("contact.dat","rb");// 以二进制读方式打开if(pf==NULL){// 文件不存在不是错误,可能是第一次运行,初始化为空即可return;}// 1. 读取当前联系人数量fread(&(pc->count),sizeof(int),1,pf);// 2. 根据数量读取具体的人员数据块fread(pc->data,sizeof(PeoInfo),pc->count,pf);fclose(pf);printf(">> 已加载 %d 条历史联系人记录。\n",pc->count);}- 容错处理:
fopen返回NULL时直接return,避免首次运行因无文件而崩溃。 - 分步读取:先读
count,再读data。这保证了读取的数据量与实际存储量一致,避免读取垃圾数据。
2. 数据保存:SaveContact
该函数负责将内存快照“固化”到磁盘。
voidSaveContact(constContact*pc){FILE*pf=fopen("contact.dat","wb");// 以二进制写方式打开if(pf==NULL){printf(">> 错误:无法保存文件!\n");return;}// 1. 写入当前联系人数量fwrite(&(pc->count),sizeof(int),1,pf);// 2. 写入所有人员数据块fwrite(pc->data,sizeof(PeoInfo),pc->count,pf);fclose(pf);}- 覆盖写入:使用
"wb"模式,每次保存都会清空旧文件并写入最新全量数据。对于百条级别的数据,这种方式简单可靠,无需处理文件中间插入/删除的复杂逻辑。 - 结构体整块写入:
fwrite(pc->data, sizeof(PeoInfo), pc->count, pf)直接将结构体数组的内存镜像写入文件,这是二进制存储的精髓。
💻 完整代码展示
1. 头文件:contact.h
定义数据结构与函数接口,新增文件操作声明。
#pragmaonce#ifndef__CONTACT_H__#define__CONTACT_H__#include<stdio.h>#include<string.h>#include<stdlib.h>#defineMAX_PEOPLE100#defineNAME_LEN20#defineSEX_LEN6#defineTEL_LEN15#defineADDR_LEN30// 单人信息结构体typedefstructPeoInfo{charname[NAME_LEN];intage;charsex[SEX_LEN];chartel[TEL_LEN];charaddr[ADDR_LEN];}PeoInfo;// 通讯录结构体typedefstructContact{PeoInfo data[MAX_PEOPLE];intcount;// 当前有效联系人个数}Contact;// 初始化通讯录voidInitContact(Contact*pc);// 加载通讯录(从文件)voidLoadContact(Contact*pc);// 保存通讯录(到文件)voidSaveContact(constContact*pc);// 1. 新增联系人voidAddContact(Contact*pc);// 2. 显示所有联系人voidShowContact(constContact*pc);// 3. 查找联系人(按姓名,返回下标,无则-1)intFindByName(constContact*pc,charname[]);// 4. 删除指定联系人voidDelContact(Contact*pc);// 5. 修改联系人信息voidModifyContact(Contact*pc);// 6. 搜索联系人并打印voidSearchContact(constContact*pc);// 7. 按姓名排序联系人voidSortContact(Contact*pc);#endif2. 功能实现:contact.c
包含文件读写逻辑及业务功能,在修改操作后自动调用保存。
#define_CRT_SECURE_NO_WARNINGS#include"contact.h"// 初始化通讯录voidInitContact(Contact*pc){pc->count=0;memset(pc->data,0,sizeof(pc->data));}// 从文件加载数据voidLoadContact(Contact*pc){FILE*pf=fopen("contact.dat","rb");if(pf==NULL){return;}fread(&(pc->count),sizeof(int),1,pf);fread(pc->data,sizeof(PeoInfo),pc->count,pf);fclose(pf);printf(">> 已加载 %d 条历史联系人记录。\n",pc->count);}// 保存数据到文件voidSaveContact(constContact*pc){FILE*pf=fopen("contact.dat","wb");if(pf==NULL){printf(">> 错误:无法保存文件!\n");return;}fwrite(&(pc->count),sizeof(int),1,pf);fwrite(pc->data,sizeof(PeoInfo),pc->count,pf);fclose(pf);}// 新增联系人voidAddContact(Contact*pc){if(pc->count>=MAX_PEOPLE){printf("通讯录已满,无法添加!\n");return;}printf("请输入姓名:");scanf("%s",pc->data[pc->count].name);printf("请输入年龄:");scanf("%d",&pc->data[pc->count].age);printf("请输入性别:");scanf("%s",pc->data[pc->count].sex);printf("请输入电话:");scanf("%s",pc->data[pc->count].tel);printf("请输入地址:");scanf("%s",pc->data[pc->count].addr);pc->count++;SaveContact(pc);// 数据变动,立即保存printf("添加成功!\n");}// 显示全部联系人voidShowContact(constContact*pc){if(pc->count==0){printf("通讯录为空!\n");return;}printf("%-10s %-5s %-6s %-15s %-20s\n","姓名","年龄","性别","电话","地址");for(inti=0;i<pc->count;i++){printf("%-10s %-5d %-6s %-15s %-20s\n",pc->data[i].name,pc->data[i].age,pc->data[i].sex,pc->data[i].tel,pc->data[i].addr);}}// 根据姓名查找,返回下标,找不到返回-1intFindByName(constContact*pc,charname[]){for(inti=0;i<pc->count;i++){if(strcmp(pc->data[i].name,name)==0){returni;}}return-1;}// 删除联系人voidDelContact(Contact*pc){if(pc->count==0){printf("通讯录为空,无需删除!\n");return;}charname[NAME_LEN];printf("请输入要删除的姓名:");scanf("%s",name);intpos=FindByName(pc,name);if(pos==-1){printf("未找到该联系人!\n");return;}// 后续数据前移覆盖for(inti=pos;i<pc->count-1;i++){pc->data[i]=pc->data[i+1];}pc->count--;SaveContact(pc);// 数据变动,立即保存printf("删除成功!\n");}// 修改联系人voidModifyContact(Contact*pc){if(pc->count==0){printf("通讯录为空!\n");return;}charname[NAME_LEN];printf("请输入要修改的姓名:");scanf("%s",name);intpos=FindByName(pc,name);if(pos==-1){printf("未找到该联系人!\n");return;}printf("请输入新姓名:");scanf("%s",pc->data[pos].name);printf("请输入新年龄:");scanf("%d",&pc->data[pos].age);printf("请输入新性别:");scanf("%s",pc->data[pos].sex);printf("请输入新电话:");scanf("%s",pc->data[pos].tel);printf("请输入新地址:");scanf("%s",pc->data[pos].addr);SaveContact(pc);// 数据变动,立即保存printf("修改完成!\n");}// 查找并打印联系人voidSearchContact(constContact*pc){if(pc->count==0){printf("通讯录为空!\n");return;}charname[NAME_LEN];printf("请输入要查找的姓名:");scanf("%s",name);intpos=FindByName(pc,name);if(pos==-1){printf("未找到该联系人!\n");return;}printf("查询结果:\n");printf("%-10s %-5s %-6s %-15s %-20s\n","姓名","年龄","性别","电话","地址");printf("%-10s %-5d %-6s %-15s %-20s\n",pc->data[pos].name,pc->data[pos].age,pc->data[pos].sex,pc->data[pos].tel,pc->data[pos].addr);}// 按姓名升序排序(冒泡排序)voidSortContact(Contact*pc){if(pc->count<=1){printf("联系人数量不足,无需排序!\n");return;}for(inti=0;i<pc->count-1;i++){for(intj=0;j<pc->count-1-i;j++){if(strcmp(pc->data[j].name,pc->data[j+1].name)>0){PeoInfo temp=pc->data[j];pc->data[j]=pc->data[j+1];pc->data[j+1]=temp;}}}SaveContact(pc);// 数据变动,立即保存printf("排序完成!\n");}3. 主程序:stu20260629.c
程序入口,负责加载数据与菜单交互。
#define_CRT_SECURE_NO_WARNINGS#include"contact.h"voidMenu(){printf("====================通讯录====================\n");printf("1. 新增联系人 2. 删除联系人\n");printf("3. 查找联系人 4. 修改联系人\n");printf("5. 联系人排序 6. 显示全部\n");printf("0. 退出程序\n");printf("===============================================\n");printf("请输入功能序号:");}intmain(){Contact con;InitContact(&con);// 程序启动时加载文件数据LoadContact(&con);intinput=0;do{Menu();scanf("%d",&input);switch(input){case1:AddContact(&con);break;case2:DelContact(&con);break;case3:SearchContact(&con);break;case4:ModifyContact(&con);break;case5:SortContact(&con);break;case6:ShowContact(&con);break;case0:printf("程序退出,再见!\n");break;default:printf("输入错误,请重新选择!\n");break;}printf("\n");}while(input!=0);return0;}