前言
接着上次所说到文件的顺序读写函数,这次主要介绍文件的随机读写函数以及如何通过文件来实现通讯录的联系人信息的存储与读取。
一、文件随机读写
1.fseek函数(根据文件指针的位置和偏移量来定位文件指针)
根据我们之前所学的fgetc函数可知,每当我们从文件读取一个字符之后,文件指针自动跳到下一个位置去。就好比下图所示。
#include<stdio.h>
int main()
{
//文件打开
FILE*pf = fopen("test.txt","w");
if (pf==NULL)
{
perror("fopen");
return 1;
}
//读文件
int ch = fgetc(pf); //a
printf("%c\n",ch);
ch = fgetc(pf); //b
printf("%c\n", ch);
ch = fgetc(pf); //c
printf("%c\n", ch);
//文件关闭
fclose(pf);
pf = NULL;
return 0;
}
那么我们不禁会思考,我要直接读取字符d,或者字符f该怎么办呢?
这里我们就用到了文件的随机读函数fseek。
其中,stream是从哪读,offset是对于文件指针起始位置的偏移量,origin为起始位置。
其实位置有三种:SEEK_SET(开头),SEEK_CUR(当前位置),SEEK_END(末尾)。
用法如下:
从文件开头开始:偏移3个找到d
//读取字符d
fseek(pf, 3, SEEK_SET);
ch = fgetc(pf);
printf("%c\n", ch);
从文件结尾开始:偏移-3个找到d
//读取字符d
fseek(pf, -3, SEEK_END);
ch = fgetc(pf);
printf("%c\n", ch);
从当前位置开始:先读取a,指针后移,指向b,再偏移2个找到d
int ch = fgetc(pf); //a
printf("%c\n",ch);
fseek(pf, 2, SEEK_CUR);
ch = fgetc(pf);
printf("%c\n", ch);
2.ftell函数(返回文件指针相对于起始位置的偏移量)
int ch = fgetc(pf); //a
printf("%c\n",ch);
fseek(pf, 2, SEEK_CUR);
ch = fgetc(pf);
printf("%c\n", ch); //d
int pos = ftell(pf);
printf("%d\n",pos);
注意!!!每次读取字符后,文件指针自动往后偏移一个。所以此时的文件指针指向字符e,字符e相对于文件起始位置偏移量为4。
3.rewind(让文件指针的位置回到文件的起始位置)
FILE*pf = fopen("test.txt","r");
if (pf==NULL)
{
perror("fopen");
return 1;
}
//读文件
int ch = fgetc(pf); //a
printf("%c\n",ch);
fseek(pf, 2, SEEK_CUR);
ch = fgetc(pf);
printf("%c\n", ch); //d
int pos = ftell(pf);
printf("%d\n",pos);
rewind(pf); //回到起始位置
ch = fgetc(pf);
printf("%c\n", ch);
二、通讯录优化(文件版)
通过之前学习结构体,编写了通讯录程序,又通过学习动态内存分配函数又将静态通讯录优化为动态通讯录,当通讯录程序运行时,我们通过添加,删除通讯录联系人信息数据,此时的数据是存储在内存中的,当程序结束退出的时候,通讯录的数据也不复存在了,等下次程序运行,还得重新录入通讯录联系人信息。
我们在想既然是通讯录就应该把信息记录下来,只有我们自己选择删除数据的时候,数据才不复存在。 这就涉及到了数据持久化的问题,我们一般数据持久化的方法有,把数据存放在磁盘文件、存放到数据库等方式。本次就介绍如何通过文件来实现通讯录数据的存储与读取。
首先我们的思路是:程序结束时,将通讯录联系人信息存储到文件中,程序开始的时候将文件中的数据读取出来。
1.文件存储信息SaveContact()
因为只有当程序结束的时候我们才需要存储信息到文件中,所以我们编写一个文件存储函数SaveContact()并且放在退出前。
case 0:
SaveContact(&con);
printf("保存成功!!");
Destroy_contact(&con);
break;
这里我们先打开一个二进制文件,通过fwrite函数将通讯录信息写入文件中去。
写的思路为:通讯录中有pc->sz个联系人信息,写pc->sz次,每次通过pc->data+i,for循环中i的数值变化,因为pc->data是struct PeoInfo *的,通过pc->data+i每次跳过一个struct PeoInfo型,访问通讯录中所有联系人信息。每次写入一个struct PeoInfo大小到文件里去。
具体代码实现如下:
void SaveContact(struct contact*pc)
{
//打开文件
FILE* pfw = fopen("data.txt", "wb"); //新建一个二进制文件
if (pfw==NULL)
{
perror("SaveContact::fopen");
return;
}
//写文件
int i = 0;
for (i = 0; i < pc->sz;i++)
{
fwrite(pc->data+i, sizeof(struct PeoInfo),1,pfw); //将通讯录信息写入文件中去。
}
//关闭文件
fclose(pfw);
pfw == NULL;
}
2.读取文件信息LoadContact()
因为每次程序开始都会初始化,所以编写一个读取文件信息函数LoadContact()放入初始化函数中,每次程序运行初始化都会从文件中读取所存储的通讯录联系人信息。
void InitContact(struct contact*pc) //用malloc动态开辟个DEFAULT_SZ字节的空间;
{
assert(pc);
pc->data = (struct PeoInfo*)malloc(DEFAULT_SZ * sizeof(struct PeoInfo));
if (pc->data == NULL)
{
perror("InitContact()"); //如果malloc开辟空间失败,返回空指针,报错
return;
}
pc->sz = 0;
pc->capacity = DEFAULT_SZ;
//加载文件中的信息到通讯录
LoadContact(pc);
}
读的思路:新建一个struct PeoInfo结构体变量并初始化为0,用来接收从文件读取的信息。
struct PeoInfo temp = {0};
通过fread二进制文件读取函数每次读取一个struct PeoInfo大小的信息。通过fread函数返回值的特性,读到了就返回读取数据的个数,因为每次读取一个,所以读到就返回1,没读到就返回0.每次读的时候需考虑增容的问题,所以先调用check_capacity()检查容量。
然后将读到的信息数据temp赋值到pc->data+pc->sz结构体指针所指向的空间中去。
pc->data[pc->sz] = temp;
即*(pc->data+pc->sz)=temp
使用while循环,并且每次读到之后,pc->sz++。
代码如下:
void LoadContact(struct contact*pc)
{
//打开文件
FILE*pfr= fopen("data.txt","rb");
if (pfr==NULL)
{
perror("LoadContact::fopen");
return;
}
//读文件
struct PeoInfo temp = {0};
while (fread(&temp, sizeof(struct PeoInfo), 1, pfr)) //读到一个返回1
{
//考虑增容的问题
check_capacity(pc);
pc->data[pc->sz] = temp;
pc->sz++;
}
//关闭文件
fclose(pfr);
pfr == NULL;
}
代码实现效果图:
文件存储:
程序重新载入显示文件内容: