目录
1.前言
承接上文,本篇是通讯录三部曲的最后一个部分,前面写了作为一个通讯录的基本部分增删查改和显示,但由于存在很多不足,所以上一篇进行了扩容部分的优化,那么这次要进行的是结束程序后的保存信息以及再一次运行程序时加载上一次保留信息部分。对一个简单的通讯录进行完善。
2.加载信息的必要性
2.1 为什么使用文件?
假设你在通讯录上花了很多时间录入了许多信息,如果没有⽂件对其进行保存,我们写的程序的数据是存储在内存中,程序退出,内存回收,数据丢失 了,再次运行程序,是看不到上次程序的数据的,这是不是有点尴尬?如果要将数据进⾏持久化的保存,我们可以使用文件。所以我们需要对它进行优化,使得所录入的信息能够保存下来。并且下次再一次打开能够看到,这样才是比较正常的。
2.2 什么是文件?
磁盘(硬盘)上的文件是文件,一般分为程序文件和数据文件,而我们这次所讲的是数据文件,数据文件是我们写程序时的读写的数据,比如程序运行时需要从内存中读取的数据,或输出内容的文件。
3.头文件信息修改
因为需要结束程序前保存信息以及加载联系人到下一次打开,所以需要写两个函数:保存和加载。
//保存通讯录
void preservecon(contact *pc);
//加载联系人到通讯录
void loadingcon(contact* pc);
4.测试部分的修改
保存的函数从哪里开始运行?逻辑上时我们写完联系人的信息,然后选择退出后,程序结束,那么我们要在申请的空间被销毁前保存当下信息到本地,然后再销毁就可以。

由图可知应该在0,退出选项。
case 0:
preservecon(&con);
Destroycon(&con);
printf("退出通讯录\n");
break;
5.保存信息&加载信息
5.1 前情提示
我们保存信息是保存在本地文件的,从内存写数据到文件里,是二进制形式写入,也可以叫输出,只写用"wb",如果没有,就新建一个文件,在这里用到的是文件指针变量FILE*来指向文件,建立指针与文件的联系,ANSIC规定使用fopen 函数来打开文件,fclose来关闭文件。
fopen的使用规则是:用一个打开模式去打开一个文件名,两种情况,要么不存在,自动新建一个是该文件名的文件,要么打开失败。那么此时就最好返回一个perror来打印错误信息,以便快速找到问题所在。

由于这个是文件管理函数,所以要切记的是,输出函数也就是写入,输入函数也就是读取。这个千万不要搞混,可以自己用自己方法去记住。
5.2 保存信息功能的具体实现
要将我们在内存中写的数据保存在文件,首先要打开一个文件,用"wb"只写的方式去写。都是三大步骤:
- 文件打开(fopen)
- 文件操作,输出/输入
- 关闭文件(fclose)
5.2.1 fwrite的了解与使用
写数据用,用的是fwrite,看英文知意思,参数意思是把count个大小为size字节的元素从要写入的元素数组写入到FILE*指向的对象。我们一个元素一个元素写。

5.2.2 功能实现
void preservecon(contact* pc)
{
FILE*pf = fopen("contact.txt","wb");
if (pf==NULL)
{
perror("preserve");
return;
}
//写数据
int i = 0;
for ( i = 0; i < pc->sz; i++)
{
fwrite(&(pc->data[i]), sizeof(PIO), 1, pf);
}
fclose(pf);
pf = NULL;
printf("保存成功");
运行正常的情况下,会在本地出现一个对应的文件,类型是txt。
至于为什么是类似乱码,就是因为fwrite是以二进制形式写入的。
5.3 加载信息的具体实现
既然已经保存了文本信息,按照测试部分的逻辑就已经退出了。那么下一次打开就是要加载原有的信息了。而加载信息功能应该是写在初始化函数中的,因为我们一运行程序,不论是增删查改,如果原先有数据,就要展示出来。
void init(contact* pc)
{
assert(pc);
pc->sz = 0;
//开辟空间
PIO *ptr = (PIO*)calloc(3,sizeof(PIO));
if (ptr==NULL)
{
perror("calloc");
return;
}
pc->data = ptr;
pc->capacity = 3;
//加载上次的信息到程序,可以查看
loadingcon(pc);
}
从文件中读数据显示到屏幕这个输出设备上,也就是输入数据。要用“rb”模式只读模式,它读的也是二进制数据。
5.3.1 fread的了解与使用
读数据,用的是fread,它的参数和fwrite是一样的。 fread是从流里读/输入数据,fwirte是写/输出到流里。
关于这两个函数可以打开C library - C++ Reference 这个网址去查看更多详解和举例。
5.3.2 功能实现
void loadingcon(contact* pc)
{
//判断是否为空
assert(pc);
//读取数据
FILE* pf = fopen("contact.txt", "rb");
if (pf == NULL)
{
perror("loadingcon");
return;
}
PIO tmp = { 0 };
int i = 0;
while (fread(&tmp, sizeof(PIO), 1, pf))
{
checkcapacity(pc);
pc->data[i] = tmp;
pc->sz++;
i++;
}
fclose(pf);
pf = NULL;
}
注意在这个代码块中,是要建立一个临时的元素数组用来存放读取的数据。当然还有检查空间的必要性,因为初始化的时候空间比较小,只有3个元素的大小。不够就需要先扩大空间再放数据进去。
6.完整源码展示
6.1 头文件部分
为了方便后期维护,数组的大小我都用了define去定义
#pragma once
#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<stdlib.h>
#define DataMAX 100
#define NameMax 10
#define SexMax 5
#define AddsMax 20
#define TeleMax 20
人的信息创建
typedef struct people
{
char name[NameMax];
int age;
char sex[SexMax];
char adds[AddsMax];
char tele[TeleMax];
}PIO;
//通讯录信息
typedef struct contact
{
PIO *data;
int sz;//大小
int capacity;//初始容量
}contact;
//初始化通讯录
void init(contact* pc);
//增加联系人
void Addcon(contact* pc);
//删除联系人
void Delecon(contact* pc);
//查找联系人
void Searchcon(contact* pc);
//修改联系人
void Modifycon(contact* pc);
//显示目前联系人
void Showcon(const contact* pc);
//销毁通讯录
void Destroycon(contact* pc);
//保存通讯录
void preservecon(contact *pc);
//加载联系人到通讯录
void loadingcon(contact* pc);
6.2 测试文件部分
#define _CRT_SECURE_NO_WARNINGS 1
#include "contact_book.h"
void menu()
{
printf("-------1.Add---------\n");
printf("-------2.Delete------\n");
printf("-------3.Search------\n");
printf("-------4.Show--------\n");
printf("-------5.Modify------\n");
printf("-------0.Exit--------\n");
}
int main()
{
int input = 0;
//创建通讯录
contact con;
//初始化
init(&con);
do
{
menu();
printf("请选择通讯录功能:\n");
scanf("%d", &input);
switch (input)
{
case 1:
Addcon(&con);
break;
case 2:
Delecon(&con);
break;
case 3:
Searchcon(&con);
break;
case 4:
Showcon(&con);
break;
case 5:
Modifycon(&con);
break;
case 0:
preservecon(&con);
Destroycon(&con);
printf("退出通讯录\n");
break;
default:
printf("无该功能,请重新输入\n");
break;
}
} while (input);
return 0;
}
6.3 具体函数部分
#define _CRT_SECURE_NO_WARNINGS 1
#include "contact_book.h"
//初始化通讯录
// 静态版本
void init(contact* pc)
{
//下标为sz的设置为0
pc->sz = 0;
//data里面的信息全部初始化为0
memset(pc->data,0,sizeof(pc->data));
}
//动态版本
void init(contact* pc)
{
assert(pc);
pc->sz = 0;
//开辟空间
PIO *ptr = (PIO*)calloc(3,sizeof(PIO));
if (ptr==NULL)
{
perror("calloc");
return;
}
pc->data = ptr;
pc->capacity = 3;
//加载上次的信息到程序,可以查看
loadingcon(pc);
}
//增加联系人
//静态版本
void Addcon(contact* pc)
{
assert(pc);
if (pc->sz==100)
{
printf("通讯录已满");
return ;
}
//添加联系人信息
else
{
printf("请输入名字:\n");
scanf("%s",pc->data[pc->sz].name);
printf("请输入年龄:\n");
scanf("%d", &(pc->data[pc->sz].age));
printf("请输入性别:\n");
scanf("%s", pc->data[pc->sz].sex);
printf("请输入电话:\n");
scanf("%s", pc->data[pc->sz].tele);
printf("请输入地址:\n");
scanf("%s", pc->data[pc->sz].adds);
}
pc->sz++;
}
void checkcapacity(contact *pc)
{
if (pc->sz==pc->capacity)
{
//增容
PIO*ptr=(PIO*)realloc(pc->data, sizeof(PIO) * (pc->capacity + 5));
if (NULL==ptr)
{
perror("realloc");
return;
}
pc->data = ptr;
pc->capacity += 5;
printf("扩容成功\n");
}
}
void loadingcon(contact* pc)
{
//判断是否为空
assert(pc);
//读取数据
FILE* pf = fopen("contact.txt", "rb");
if (pf == NULL)
{
perror("loadingcon");
return;
}
PIO tmp = { 0 };
int i = 0;
while (fread(&tmp, sizeof(PIO), 1, pf))
{
checkcapacity(pc);
pc->data[i] = tmp;
pc->sz++;
i++;
}
fclose(pf);
pf = NULL;
}
//动态添加联系人版本
void Addcon(contact* pc)
{
assert(pc);
checkcapacity(pc);
printf("请输入名字:\n");
scanf("%s", pc->data[pc->sz].name);
printf("请输入年龄:\n");
scanf("%d", &(pc->data[pc->sz].age));
printf("请输入性别:\n");
scanf("%s", pc->data[pc->sz].sex);
printf("请输入电话:\n");
scanf("%s", pc->data[pc->sz].tele);
printf("请输入地址:\n");
scanf("%s", pc->data[pc->sz].adds);
pc->sz++;
}
int FindName( const contact *pc,char name[])
{
int i = 0;
for (i = 0; i < pc->sz; i++)
{
if (strcmp(pc->data[i].name, name) == 0)
{
return i;
}
}
return -1;
}
//删除联系人
void Delecon(contact* pc)
{
assert(pc);
if (pc->sz==0)
{
printf("通讯录为空,无法删除\n");
}
//删除
else
{
//要删除的人是
printf("想要删除的姓名:>\n");
char name[NameMax] = {0};
scanf("%s",name);
int ret = FindName(pc,name);
//把要删除的人姓名的下标找到,并替换
int i = 0;
if (-1 == ret)
{
printf("要删除的人不存在\n");
return;
}
else
{
memmove(&(pc->data[ret]), &(pc->data[ret + 1]),(pc->sz-ret)*sizeof(pc->data[0]));
pc->sz--;
printf("删除成功\n");
}
}
}
//查找联系人
void Searchcon(contact* pc)
{
assert(pc);
printf("请输入你想查找的人:\n");
char name[NameMax] = {0};
scanf("%s",name);
int ret = FindName(pc, name);
if (ret ==-1)
{
printf("要查找的人不存在\n");
return ;
}
else
{
printf("%-10s\t%-4s\t%-5s\t%-20s\t%-12s\n","姓名","年龄","性别","地址","电话号码");
printf("%-10s\t%-4d\t%-5s\t%-20s\t%-12s\n", pc->data[ret].name,
pc->data[ret].age,
pc->data[ret].sex,
pc->data[ret].adds,
pc->data[ret].tele);
}
}
//修改联系人
void Modifycon(contact* pc)
{
assert(pc);
char name[NameMax] = {0};
printf("请输入你要修改的联系人\n");
scanf("%s", name);
int ret = FindName(pc, name);
if (-1==ret)
{
printf("要修改的人不存在\n");
return;
}
else
{
printf("请输入名字:\n");
scanf("%s", pc->data[ret].name);
printf("请输入年龄:\n");
scanf("%d", &(pc->data[ret].age));
printf("请输入性别:\n");
scanf("%s", pc->data[ret].sex);
printf("请输入电话:\n");
scanf("%s", pc->data[ret].tele);
printf("请输入地址:\n");
scanf("%s", pc->data[ret].adds);
}
}
int compare(const void* a, const void* b)
{
return strcmp(((PIO*)a)->name, ((PIO*)b)->name);
}
//显示目前联系人信息
void Showcon(const contact* pc)
{
assert(pc);
qsort(pc->data,pc->sz,sizeof(PIO),compare);
int i = 0;
printf("%-10s\t%-4s\t%-5s\t%-20s\t%-12s\n", "姓名", "年龄", "性别", "地址", "电话号码");
for ( i = 0; i <pc->sz; i++)
{
printf("%-10s\t%-4d\t%-5s\t%-20s\t%-12s\n",pc->data[i].name, pc->data[i].age,
pc->data[i].sex,
pc->data[i].adds,
pc->data[i].tele);
}
}
//销毁通讯录
void Destroycon(contact* pc)
{
free(pc->data);
pc->data = NULL;
pc->sz = 0;
pc->capacity = 0;
}
//写入数据到文件
void preservecon(contact* pc)
{
FILE*pf = fopen("contact.txt","wb");
if (pf==NULL)
{
perror("preserve");
return;
}
//写数据
int i = 0;
for ( i = 0; i < pc->sz; i++)
{
fwrite(&(pc->data[i]), sizeof(PIO), 1, pf);
}
fclose(pf);
pf = NULL;
printf("保存成功");
}
7.总结
到这里通讯录的三部曲就基本全部写完了。基础部分和一定需要优化的构架都完善了,还有更为高级的内容,暂时能力不足,等能力提升了再逐步开发增加功能。感谢观看!