二进制文件和文本文件
二进制文件就是以二进制的形式写入文件的,人是看不懂的,而文本文件就是以ACSLL码的形式进行保存的。
举个例子,10000这个数字如果保存在二进制文件中就应该是0010 0111 0001 0000 ,占四个字节;而在文本文件中则是以ACSLL码的形式保存,就是说明文本文件就是我们人正常看到的文字,10000就是以10000 的形式进行保存的,占5个字节。
流
当文件或者其他设备的内容需要通过计算机来写入或者读取到其他设备的时候,就需要知道怎么打开对应的通道,因为不同设备的转存需要不同的通道,例如光盘的数据要写到硬盘上和硬盘的数据要写到U盘上,都是需要不同的通道进行的
而流就是数据流,为了提高程序员的工作效率和学习便利,在计算机底层设计了一个流,它就是可以将不同的设备连接到其他的设备,实现数据的读取写入等操作,我们程序员只需要关心流是怎么打开关闭和怎么通过流写入读写的,知道这个就不用关心这个设备和另一个设备需要什么通道才能连接,这些计算机的底层已经设计好了。
文件的打开和关闭
文件的使用分三个步骤,打开文件,文件的读写操作,文件的关闭。
fopen
fopen 函数是用来打开文件的。filename 就是文件的名
当然文件也会有打开失败的情况,一旦打开失败就会返回空指针,我们只要写一段代码来避免空指针的解引用就可以了。
mode 是打开文件的形式,我们来看一下文件的打开形式:
⽂件使⽤⽅式 含义 如果指定⽂件不存在
“r”(只读) 为了输⼊数据,打开⼀个已经存在的文本文件 出错
“w”(只写) 为了输出数据,打开⼀个文本文件 建立⼀个新的文件
“a”(追加) 向⽂本⽂件尾添加数据 建立⼀个新的文件
“rb”(只读) 为了输⼊数据,打开⼀个二进制文件出错
“wb”(只写) 为了输出数据,打开⼀个二进制文件 建立⼀个新的文件
“ab”(追加) 向⼀个二进制文件尾添加数据 建立⼀个新的文件
“r+”(读写) 为了读和写,打开⼀个文本文件 出错
“w+”(读写) 为了读和写,建议⼀个新的⽂件 建立⼀个新的文件
“a+”(读写) 打开⼀个文件,在文件尾进行读写 建立⼀个新的文件
“rb+”(读写) 为了读和写打开⼀个二进制文件 出错
“wb+”(读写)为了读和写,新建⼀个新的二进制文件 建立⼀个新的文件
“ab+”(读写)打开⼀个二进制文件,在文件尾进行读和写 建立⼀个新的文件
fclose
fclose 是文件关闭函数,这个函数的使用比较便捷,我们值需要传入文件名即可。
文件缓冲区
在文件操作中,计算机会为文件开辟一个缓冲区(缓冲区的大小有C语言编译器决定的),只有缓冲区已满或者刷新缓冲区的时候,操作系统才会把缓冲区的数据输入到你设定的目标空间上。
因为操作系统上运行很多程序,不可能一直为编译器服务,所以设立了一个缓冲区来暂时存储数据,这样操作系统的运行效率才不会因为编译器而变得低下。
这就是为什么文件打开后需要关闭文件,如果不关闭就会一直占用空间。
文件顺序读写函数
字符输入
fgetc
字符输入函数,适用于所有输入流
从流里面得到单个字符,返回值是整型的原因是字符可以用ACSLL码值来表示,而且当读取失败或者文件末尾是会返回EOF(也就是 -1),所以使用 int 为返回类型。
实践:
fputc
字符输出函数,适用于所有输出流
如果写入失败就会返回EOF
实践:
文本行操作
fgets
文本行输入函数,适用于所有输入流
fgets 会从流里面读取字符串,一旦遇见换行或者文件末尾就会停止读取!
str 是读取到的字符串保存的地址,num是读取的个数,stream 就是流。
当读取成功的时候就会返回 str 这个地址,否则就会返回空指针!
读取的字符会在末尾自动添加 \0,所以你实际读取到的字符个数实际上是 num - 1
实践一下:
fputs
文本行输出函数,适用于所有输出流
fputs 是写一个字符串到流里面,参数str 就是你要输入的字符串内容的地址, stream 就是流。注意返回值是 int 类型的,如果写入成功就会返回一个非负值,如果失败就会返回EOF(-1)并且会标记错误的指示。
实践一下:
一组函数的对比
这里我们需要对比 scanf / printf 和 fscanf / fprintf 和sscanf / sprintf 的函数使用。scanf 和 printf 想必大家都知道这是标准的输入输出函数!
fscanf
格式化输入函数,适用于所有输入流
fsacnf 是可以从指定的流里面输入格式化的数据。它和scanf 最大的区别就是可以指定流,因此也就多了个参数 stream ,这个流也可以是标准流。
实践:
fprintf
格式化输出函数,适用于所有输出流
fprIntf 是可以输出格式化的数据到指定的流里面。它和 printf 最大的区别就是可以指定流,因此也就多了个参数 stream ,这个流也可以是标准流。
实践:
sscanf
适用于字符串,可以将字符串的数据以格式化的形式读取
它和 scanf 最大的区别就是 sscanf 是可以指定的字符串,因此也就多了个参数str (字符串的地址)
实践:
sprintf
适用于字符串,可以将格式化数据输出到字符串中
它和 printf 最大的区别就是 sprintf 是指定的字符串,因此也就多了个参数str (字符串的地址)
实践:
综合应用:
二进制的读写函数
fread
二进制输入,适用于文件
这个函数会将文件中的数据以二进制的形式写入到字符串中。
size 是指一个数据多大,count 表示有多少个数据需要写入。
实践:
#include <stdio.h>int main()
{FILE* pf = fopen("data.txt", "rb");if (pf == NULL){perror("fopen()");return 1;}int arr[20] = { 0 };fread(arr, sizeof(arr[0]), 20, pf);fclose(pf);return 0;
}
fwrite
二进制输出,适用于文件
把数据以二进制的形式写入流中
实践:
文件随机读写函数
当文件在读写操作中,会有指针的移动(也可以理解成光标的移动)它决定了读写下一个数据,为了使程序能更加灵活的掌控,C语言引入了随机读写函数,这些函数可以控制光标的移动。
fseek
这个函数可以移动光标。
解释一下参数部分:offset 是你想移动多少的(正数表示向后移动光标,负数表示向前移动光标),origin 是你想从哪里开始移动(这里给了三个参数,SEEK_SET 是文件的起始位置,SEEK_CUR 是文件的当前位置,SEEL_END 是文件的末尾)
我们来实践一下:
#include <stdio.h>int main()
{FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return 1;}//读文件int ch = fgetc(pf);printf("%c\n", ch);fseek(pf, 5, SEEK_SET);ch = fgetc(pf);printf("%c\n", ch);fseek(pf, 4, SEEK_CUR);ch = fgetc(pf);printf("%c\n", ch);fseek(pf, -4, SEEK_END);ch = fgetc(pf);printf("%c\n", ch);fclose(pf);pf = NULL;return 0;
}
ftell
这个函数是获得当前光标的位置。
rewind
这个函数比较简单,就是能将光标移动回原来的位置。只需要传入文件指针即可。
文件读写结束判定
在文件读取的过程中有两种情况会导致文件读取结束,一个是遇到文件末尾,一个是读取过程中遇到障碍导致没能遇到文件末尾而体纤导致文件读取失败。在上面的函数讲解中我们提到过文件读取结束回返回一个数值(有EOF,NULL等),并且如果不是正常的读取结束是回标记的,而且官方文档中有提到 ferf 这个函数,它就是来判断文件的读取结束时因为什么原因而结束的。
feof
feof 需要接收的参数就是文件指针,返回值有两种,一个是 非零值 (说明文件是遇到文件末尾正常读取完成而结束的);另外一种则是 0 (说明文件没有遇到文件末尾而导致结束)
有了这个函数,我们就可以判断文件读取结束后是不是遇到文件末尾的结束的!!!
实践:
#include <stdio.h>int main()
{FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen()");return 1;}char ch;while ((ch = fgetc(pf)) != EOF){printf("%c", ch);}if (feof(pf)){printf("文件不是遇到文件末尾而结束的!\n");}else{printf("文件正常读取而结束!\n");}fclose(pf);pf = NULL;return 0;
}