1.为什么要进行文件操作
在往期,我们使用C语言构造过通讯录,但是无论是一次性开辟好空间的通讯录,还是动态版的通讯录,都无法做到退出通讯录后将信息保存下来。但是通过文件操作,我们可以在退出通讯录时将信息保存到文件中,并且在再次打开时读取信息到程序中,以达到每次打开时能信息的功能。
2.如何保存通讯录
在C语言里,有文件操作函数帮助我们将程序中的信息保存到文件中。为了保存通讯录,我们将会用到fopen,fclose,fwrite以及fread函数。通过这四个函数,我们可以在关闭程序时将信息写入到文件中,并且在打开程序时将文件中的信息读取到程序中,已达到保存通讯录信息的目的。
接下来,我们先对这四个函数进行介绍。
FILE *fopen(const char *filename, const char *mode)
fopen函数可以让我们打开一个文件,其第一个参数为包含文件名的字符串,它可以是文件的名字,比如"data.txt",只写文件名时程序只会在当前目录下查找该文件。也可包含文件的路径,比如""C:\Users\Administrator\Desktop\data.txt",这样就能对其他目录下的文件进行操作了。
第二个参数则是包含文件访问模式的字符串。比如"r"则意为以读取的方式打开文件,若该文件不存在则会打开失败。而"w"则表示以写入的方式打开一个文件,如果没有同名文件则会创建一个空文件,若有同名文件则会清空该文件。
其返回值会返回一个FILE*类型的指针,这个指针指向了被打开的文件,若打开失败则会返回NULL。
int fclose(FILE *stream)
fclose函数可以帮助我们关闭一个已经打开的文件。其参数为要关闭的文件,其返回值在关闭文件成功时会返回0,如果失败则会返回EOF。
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
fwrite函数可以将所指向空间的数据写入到文件中。参数ptr指向了被写入数据的空间。参数size指每个被写入数据的大小。参数nmemb指写入的数据总数,每个数据大小为size。参数stream是要被写入的文件的指针。fwrite函数的返回值为写入的数据总数。
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
fread函数则是将文件内的数据读取到内存空间处,其中ptr指针指向了该内存空间,size参数意为每个数据需要读取的空间大小,nmemb则是读取的数据总数。stream指针指向了需要被读取的文件。fread函数的返回值为读取的数据总数。
3.修改通讯录
在选择退出后,我们创建一个SaveCon函数以保存通讯录,再将通讯录销毁。
case EXIT:
SaveCon(&con);//保存通讯录到文件中
DestroyCon(&con);
printf("退出通讯录。");
break;
SaveCon函数内部实现
void SaveCon(struct contact* con)
{
assert(con);//断言
FILE* pf = fopen("C:\\Users\\Administrator\\Desktop\\data.txt", "wb");//打开文件
if (pf == NULL)
{
perror("SaveContact");
return;
}
fwrite(con->data, sizeof(struct peoinfo), con->size, pf);//写入信息
fclose(pf);//关闭文件
pf = NULL;//指针置为空指针
}
这样,我们就能在关闭通讯录时,将信息自动保存到文件中。
我们再构建一个LoadCon函数以在打开通讯录时读取文件中的信息。
int main()
{
struct contact con;
InitCon(&con);
LoadCon(&con);//将文件中的信息读取到通讯录中
//循
//环
//部
//分
return 0;
}
LoadCon函数的内部实现
void LoadCon(struct contact* con)
{
assert(con);//断言
FILE* pf = fopen("C:\\Users\\Administrator\\Desktop\\data.txt", "rb");//打开文件
if (pf == NULL)
{
perror("LoadContact");
return;
}
struct peoinfo tmp = { 0 };//创建临时空间保存信息
while (fread(&tmp, sizeof(struct peoinfo), 1, pf))//当没有信息可读取就跳出循环
{
if (con->size == con->capacity)//判断容量是否足够,若不够则需要增容
{
struct peoinfo* p = realloc(con->data,
(con->capacity + COM_CAP) * sizeof(struct peoinfo));
if (p == NULL)
{
printf("增容失败\n");
return;
}
con->data = p;
con->capacity += COM_CAP;
}
con->data[con->size] = tmp;//将信息从临时空间内放入通讯录中
con->size++;
}
fclose(pf);
pf = NULL;
}
与写入不同,读取信息时我们的size为0,所以无法直接读取size个信息到通讯录内,所以我们先使用一个tmp结构体接收一个人的信息,再判断fread函数的返回值是否为0,若为0则说明该次读取已经无法读取到信息了。若不为0则将该信息写入到通讯录中,再读取下一个人的信息。这里还要注意,通讯录初始化后的容量可能放不下所有人的信息,所以我们在每读取一个人的信息后,就要对通讯录的容量进行判断,若容量不够则需要进行扩容。
这样,一个可以将信息保存到文件中的通讯录就改造完成了。