之前介绍过通讯录静态版本和动态内存开辟版本,这两个版本的通讯录运行过程中,通讯录数据是存放在内存中,当程序退出的时候,通讯录的数据自然就不存在了。每次重新运行数据,都得重新添加数据,这样操作起来就比较冗余。
因此我们就想找到一种新方法,只有在我们删除数据的时候,数据在不复存在,让数据持久化。我们一般将数据放在磁盘文件中或者存放在数据库中。
今天我就来介绍让数据持久化的一种方法,通过使用文件将数据直接存放在电脑的硬盘上,使数据持久化。
用一张图来总结文件操作:
目录
1、什么是文件
1.1 程序文件
1.2 数据文件
1.3 文件名
2、文件的打开和关闭
2.1 文件指针
2.2 文件的打开和关闭
3、文件的顺序读写
3.1、fgetc和fputc:
3.2、fgets 和 fputs :
3.3、fscanf 和 fprintf
3.4 、fread 和 fwrite
4.文件的随机读写
从文件功能上来分,文件可分为程序文件和数据文件。
例如:C : \ code \ test.txt 文件路径(C : \ code \) 文件名主干(test) 文件后缀(.txt)
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字、文件状态及文件当前的位置) 这些信息均保存在一个结构体变量中的,该结构体类型是由系统声明的,取名为 FILE
文件在读写之前应该先打开文件,在使用之后关闭文件。
打开文件的同时,会返回一个FILE*类型的指针,需要用一个同样是FILE*类型的指针来接受,因此可以通过文件指针变量能够找出与它相关联的文件。
打开文件和关闭文件的函数原型:
//打开文件
FILE * fopen(const char *filename, const char *mode);
//关闭文件
int fclose(FILE* stream);
我们打开文件的方式(形参mode)可以有很多种,我就列举几个常用的:
文件使用方式 | 含义 | 如果指定文件不存在 |
“r”(只读) | 为了输入数据,打开一个已经存在的文本文件 | 出错 |
“w”(只写) | 为了输出数据,打开一个文本文件 | 建立一个新的文件 |
“a”(追加) | 向文本文件尾添加数据 | 建立一个新的文件 |
“rb”(只读) | 为了输入数据,打开一个二进制文件 | 出错 |
“wb”(只写) | 为了输出数据,打开一个二进制文件 | 建立一个新的文件 |
注意:
/*fopen fclose example*/
int main()
{
//以写的形式打开文件
FILE*pfile = fopen("my_file", "w"); //这个是相对路径
//文件操作
if (pfile != NULL)
{
fputs("hello world", pfile);
}
//关闭文件
fclose(pfile);
pfile = NULL;
return 0;
}
补充一个知识点: 绝对路径 和 相对路径
相对路径: 如果我们在test.c中对文件读/写,且这个文件与test.c在同一大文件下,我们写fopen中形参filename的时候只需要写 文件名主干+文件后缀
举个例子:
我们在contact.c中进行了文件操作,数据文件为contact.txt,而contact.txt与contact.c在同一路径底下,因此我们filename写的是相对路径。
绝对路径:读/写的文件与test.c不在同一大文件下,我们写fopen中形参filename的时候需要写全文件名,也就是 文件路径+文件主干+文件后缀,并且 ‘ / ’ 要写成 “ // ”
举个例子:对桌面上的文件进行操作
我们右击点开文件属性,复制 位置 的内容 C:\Users\lenovo\Desktop,然后将‘ / ’ 替换成 “ // ”,再补上 文件主干 和 文件后缀 ,变成 C:\\Users\\lenovo\\Desktop\\contact.c
功能 | 函数名 | 适用于 |
字符输入函数 | fgetc | 所有输入流 |
字符输出函数 | fputc | 所有输出流 |
文本行输入函数 | fgets | 所有输入流 |
文本行输出函数 | fputs | 所有输出流 |
格式化输入函数 | fscanf | 所有输入流 |
格式化输出函数 | fprintf | 所有输出流 |
二进制输入 | fread | 文件流 |
二进制输出 | fwrite | 文件流 |
常见的流:
只有文件流需要我们自己打开,而后三个是在C程序运行起来后,就默认打开了。
接下来我举些例子来看看这些函数如何使用:
fgetc原型:
int fgetc(FILE * stream);
fputc原型:
int fputc(int character,FILE* stream);
character 是输入字符的ASCII码值
/* fgetc example*/
int main()
{
FILE * pfile;
char ch;
//对文件只读
pfile = fopen("data.txt", "r");
if (pfile == NULL)
{
perror("fopen");
return;
}
//从文件中读取数据
while ((ch = fgetc(pfile)) != EOF)
{
printf("%c", ch);
}
printf("\n");
fclose(pfile);
pfile=NULL;
//从键盘获得值
printf("%c", fgetc(stdin));
}
/* fputc example*/
int main()
{
FILE * pfile;
char ch;
//对文件只写
pfile = fopen("data.txt", "w");
if (pfile == NULL)
{
perror("fopen");
return;
}
//将数据写入文件中
for (char i = 'a'; i <= 'z'; i++)
{
ch = i;
fputc(ch, pfile);
}
fputc('\n', pfile);
fclose(pfile);
pfile=NULL;
//将值输入到屏幕上
fputc('h', stdout);
}
fgets的原型:
char * fgets(char * str,int num,FILE* stream);
先定义一个字符数组 str[A],把数据传到数组里面,num的最大值为A,但实际只会传A-1个字符进去,剩下一个放‘\0’ 。
fputs的原型:
int fputs(const char * str,FILE*stream);
第一个参数可以是数组的数组名,也可以直接是字符串。
/* fgets example*/
int main()
{
FILE * pfile;
char a[26];
//对文件只写
pfile = fopen("data.txt", "r");
if (pfile == NULL)
{
perror("fopen");
return;
}
//将数据写入数组中
fgets(a, 26, pfile);
puts(a);
fclose(pfile);
pfile = NULL;
}
fputs的使用方法跟fputc差不多,我就不再赘述了。
fscanf和scanf很像,fprintf和printf很像,其函数原型无非是多了一个参数,也就是fscanf和fprintf非要通过文件流才能实现硬盘和内存的连接:
int scanf(格式);
int fscanf(FILE* stream,格式);
int printf(格式);
int fprintf(FILE* stream,格式);
举个例子:
struct s
{
char name[20];
char sex[3];
int age;
};
/* fscanf example */
int main()
{
struct s s1= { 0 };
FILE * pread = fopen("data.txt", "r");
if (pread == NULL)
{
return;
}
//把文件的内容输入到程序(从磁盘到内存)
fscanf(pread, "%s%s%d", s1.name, s1.sex, &(s1.age));
//把屏幕上的内容输入到程序
fscanf(stdin,"%s%s%d", s1.name, s1.sex, &(s1.age));
//把结构体内容输出到屏幕上
printf("%-5s %-3s %-2d", s1.name, s1.sex, s1.age);
fclose(pread);
}
struct s
{
char name[20];
char sex[3];
int age;
};
/* fprintf example */
int main()
{
struct s s1= { 0 };
FILE * pwrite = fopen("data.txt", "w");
if (pwrite == NULL)
{
return;
}
scanf("%s%s%d", s1.name, s1.sex, &(s1.age));
//从程序中的结构体读取数据输出到文件中(从内存到磁盘)
fprintf(pwrite, "%-5s %-3s %-2d", s1.name, s1.sex, s1.age);
//从程序中的结构体读取数据输出到屏幕上
fprintf(stdout, "%-5s %-3s %-2d", s1.name, s1.sex, s1.age);
fclose(pwrite);
pwrite = NULL;
}
这里还得补充一对函数:sscanf和sprintf 原型如下:
int sscanf(const char *a, 格式);
int sprintf(char *a,格式);
sscanf的功能是把格式化的数据转化成字符串放到a中,sprintf的功能是把字符串转化成格式化的数据,返回的是成功转化的个数(不包括'\0')
struct s
{
char name[20];
char sex[3];
int age;
};
int main()
{
struct s s1 = {"xiaozhang","nv",20};
struct s s2 = { 0 };
char buf[100] = { 0 };
//把s1中格式化数据转化成字符串放到buf中
sprintf(buf, "%s %s %d", s1.name, s1.sex, s1.age);
printf("%s\n", buf);//buf的内容为“xiaozhang nv 20”
//从字符串buf中获取一个格式化的数据放到s2中
sscanf(buf, "%s %s %d", s2.name, s2.sex, &(s2.age));
printf("格式化:%s %s %d\n", s2.name, s2.sex, s2.age);
return 0;
}
注意:格式化数据如果有字符串,字符串不能有空格,否则就会被拆开,比如“xiaozhang”写成“xiao zhang”计入buf中,再一次被转化为格式化数据放在s2中,会变成“xiao”和“zhang”两个字符串。
比较一下三对函数:
scanf | 是针对标准输入流的格式化输入语句 |
printf | 是针对标准输出流的格式化输出语句 |
fscanf | 是针对所有输入流的格式化输入语句 |
fprintf | 是针对所有输入流的个时候输出语句 |
sscanf | 是将一个字符串转化成格式化的数据的语句 |
sprintf | 是将格式化的数据转化成字符串的语句 |
struct s
{
char name[20];
char sex[3];
int age;
};
int main()
{
struct s s1 = { "zhangsan","nv",20 };
//以二进制的形式写到文件中
FILE * pf = fopen("test.txt", "wb");
if (pf == NULL)
{
perror("fopen:");
return;
}
//以二进制的方式写
fwrite(&s1, sizeof(struct s), 1, pf);//把s1的内容以二进制的方式写进文件中,
//每次写1个struct s字节大小,如果成功写入,则返回1
fclose(pf);
pf = NULL;
return 0;
}
//以二进制的方式读
fread(&s1, sizeof(struct s), 1, pf);//把s1的内容以二进制的方式写进文件中,每次写1个struct s字节大小,如果成功写入,则返回1
fclose(pf);
pf = NULL;
printf("%s %s %d", s1.name, s1.sex, s1.age);
return 0;
之前都是文件的顺序读写,如果我们要直接能够定位文件中的内容,怎么办?此时我们应该学习如何随机读写文件,接下来,我会向大家介绍fseek,ftell,rewind 函数来实行文件的随机读写。
我们可以在Cplusplus中搜到这三个函数的原型:
int fseek ( FILE * stream, long int offset, int origin );
long int ftell ( FILE * stream );
void rewind ( FILE * stream );
//SEEK_SET 起始位置
//SEEK_CUR 现在位置
//SEEK_END 文件末尾
//文件内容为:abcdefg
int main()
{
FILE *pf = fopen("data.txt", "r");
if (pf == NULL)
{
ferror("fopen");
return;
}
fseek(pf, 2, SEEK_CUR);//找到文件中,文件开头往后2个字节处的位置
int ch = fgetc(pf);//c
printf("%c\n", ch);
printf("%d\n", ftell(pf));//3
fseek(pf, -1, SEEK_END); //找到文件中,距离文件末尾往前1个字节处的位置
ch = fgetc(pf);//f
printf("%d\n", ch);
printf("%c\n", ftell(pf));//6
rewind(pf);//让文件指针指向文件初始位置
ch = fgetc(pf);//a
printf("%c\n", ch);
fclose(pf);
pf = NULL;
}
总结一下三个函数的作用:
正文内容结束啦,觉得我的博客有用的话就帮我点个赞吧!