正片开始👀
昨天了解了文件操作我才知道我的编程习惯这么差,老实说之前应该先老老实实学完文件再去搞通讯录,不然根本没有给通讯录注入灵魂。
目的🤔
在学习数据结构时,写完通讯录后在运行的时候,可以在通讯录中实现增删查改,此时我们的数据都是存放在内存中的,当我们的程序退出(终止)时,自然就会销毁,里面被操作过的数据也就没有了,在下次运行时数据又要重新录入,咱就说难不难受吧
既然我们是通讯录就该把信息记录下来,只有我们在内部进行了删除数据操作才会真正删掉,这就涉及到我们数据的持久化问题,我们一般保存数据的方式就是存放在磁盘文件或数据库中等方法。
因此一句话文件操作就是把数据直接存放在电脑硬盘上从而实现数据的持久化。
分类👏
只要放在磁盘里的文件都叫文件,而文件又分为了程序文件和数据文件:
程序文件指包括后缀.c源程序文件,后缀 .ob 的目标文件,后缀.exe 的可执行文件。
数据文件的文件内容就不一定是程序,而是程序运行实读写的数据,比如参与程序运行的读取数据或输出内容的文件。
关系如下(手残ppt勿喷):
今天我们深入讨论就是数据文件。
文件名👏
文件名的构造虽然基础但是必须知道
例如:c:\user\data.txt
文件名三个部分包含了路径,文件名,后缀。
文件指针👏
文件的打开和关闭就必须射击文件指针,缓冲文件系统中,每个被使用的文件都会在内存中开辟一个相应的文件信息区,用来存放文件的相关信息(文件名,文件地址等),这些信息都被放在一个结构体变量中,该结构体是由系统声明的,取名FILE。
不同编译器下FILE类型包含的内容不完全相同,但是差别不大,每打开一个文件时,系统会根据文件情况自动创建一个FILE结构的变量,并填充其中的信息,作为用户我们不必关心细节。
文件指针变量:
FILE* p;
1
定义 p 是一个指向FILE类型数据的指针变量,可以使p指向某个文件的文件信息区(结构体变量),通过该文件信息区能够访问文件,相当于通过文件指针变量可以找到与他关联的文件。
打开与关闭👏
文件在读写之前应该先打开文件结束应该关闭文件就要用到 fopen 和 fclose 函数
#include<errno.h> #include<string.h> int main() { FILE* p = fopen("data.txt","r"); if(p == NULL) { printf("%s\n",strerror(errno)); return 0; } //接下来才写文件 //………… //写完记得关闭文件 fclose(p); p = NULL; return 0; }
这里打开文件会有一个参数:“r”,代表以只读的方式打开文件(该文件必须已经存在,若文件不存在,则会出错)
其他常见的还有:
“w”:以只写的方式打开文件,若文件存在则文件长度清为0,即该文件内容会消失。若文件不存在则建立该文件(不用担心出错)
“a”:追加,位置指针移到文件末尾,向文件尾部添加数据,原文件数据保留,若文件不存在则会出错
“b”:与上面的字符串组合,表示打开二进制文件
示例我用的 r 代表读文件,他会查看当前文件名的文件是否存在,存在即返回一个 FILE* 类型,没有则返回空指针,所以必须对返回值进行检验。
读写文件👏
顺序读写😎
接下来就是对文件进行读写操作,这里就涉及到一组函数:
比如写文件:
int main() { FILE* p; //打开文件完成 //写文件 fputc('a',p); } //关闭文件 return 0; }
大家注意到了吗同样是写入数据,之前我们的程序执行时会在屏幕上显示出数据,为什么过程中没有涉及到打开屏幕,关闭屏幕这些操作呢?
其实所有C语言程序都有三个流(这些流的类型都是 FILE* 类型):
stdin 标准输入流;
stdout 标准输出流;
stderr 标准错误流;
其实在 printf 打印数据时标准输出流已经打开了,是向标准输出流上进行格式化的输出函数,也就自然而然的来到了屏幕上。因此面向所有流的 fprintf,fscanf 就更为强大。
注意 fgetc 这些函数的返回值是字符对应的 ASCII 码值,所以类型注意一下:
*FILE p; int a = 0; while((a=fgetc(p) != EOF)) { printf("%c ",a); }
其实类型用 char 就能接受 fgetc 的返回值,但这里 int 类型也回应了 EOF 的返回值 -1,所以要视情况而定(不想用 while 挨个儿输入,就换成 fputs 进行行输入即可)。
随机读写😎
我们知道 fgetc 函数每次 get 一个元素后指针会自动向后偏移一个元素,如果文件中的文本是“abcdefg”,在 get 一个 ‘a’ 后,我不想要 ‘b’,而想他定位到 ‘ f ’,这就是随机顺序的读取。
fseek🤔
fseek 函数就能实现指哪打哪,设置流 stream 的文件位置为给定的偏移 offset,参数 offset 意味着从给定的 whence 位置查找的字节数,声明为:
int fseek(FILE *stream, long int offset, int whence)
stream 是指向 FILE 对象的指针;whence 是表示开始偏移的位置,它一般指定为下列常量之一:SEEK_SET 文件开头,SEEK_CUR 文件指针当前位置,SEEK_END 文件的末尾;offset 是相对 whence 的偏移量,以字节为单位。
以刚才例子为例 ,可以给出定位到字符 ‘f’ 的两种等价方法:
fseek(p,5,SEEK_SET);
fseek(p,-1,SEEK_END);
1
2
rewind🤔
如果不知道某个字符偏移量也没有关系,ftell 函数会告诉我们他的偏移量,返回值是给定流 stream 的当前文件位置为 int 类型。
如果 ftell 告诉了我偏移量多少,但我现在在一个我不知道的地方,rewind就能实现回到起始位置。声明如下:
void rewind(FILE *stream)
其实除了这些,还有很多文件处理的函数,这里就不一一列举了。
文本文件与二进制文件👏
一个数据在内存中怎么存储呢?我们知道字符一律以 ASCII 码值形式存储,数值型数据既可以以 ASCII 码值形式存储,也可以使用二进制形式存储。
比如一个整数 100,将单个字符‘ 1 ’,‘ 0 ’,‘ 0 ’以 ASCII 码形式依次输出到磁盘,则磁盘一共占用 3 个字节;如果以二进制形式输出,则将 100 化成二进制形式输出占 4 个字节。
所以要二进制形式写入100就应该是
int num = 100;
FILE* p = fopen("data.txt","wb");
fwrite(&num,4,1,p);
1
2
3
读取结束的判定👏
请牢记,在文件读取过程中,不能使用 feof 函数返回值(feof 函数不是判断是否结束,而是已经结束的判断,只是不知道原因)直接判断是否结束,应当判定结束时是读取失败结束还是遇到文件尾的结束,比如 fgetc 判断返回值是否为 EOF , fgets 判断返回值是否为 NULL, fread 判断返回值是否为实际目标的读取个数。
文件缓冲区👏
文件缓冲区是用以暂时存放读写期间的文件数据而在内存区预留的一定空间
说白了,就是数据从内存输送到磁盘或磁盘输送到程序,都会被优先送入文件缓冲区,待到装满缓冲区后再一并送入磁盘,就像咱倒垃圾一样,强迫症不装满不倒的那种。
🤔 那为什么会存在这么个缓冲区? 🤔
比如我们在程序中C语言使用 printf 函数将数据打印到屏幕上,但过程远没有这么简单,我们 printf 函数其实是系统先调用API,让操作系统在屏幕上打印信息。
但是!操作系统不会为这个程序一对一辅导,既然是系统就要兼顾全局,他会在处理这个 printf 时还要服务其他程序,保证了程序运行的效率。