传送门:C语言-第九章:文件读写
第一节:文件位置指示器
文件位置指示器是下一次操作要对文件进行操作的位置(读或写),可以理解为光标的位置。
进行读操作和写操作时,指示器的初始位置都在文件开头;
进行追加操作时,指示器的位置在文件结尾。
进行读写操作时,指示器的位置也会随之后移,我们也可以用用一些函数改变文件指示器的位置。
1-1.fseek 函数
fseek 函数根据一个起始位置,将指示器偏移到对应位置,函数原型如下:
stream:要改变指示器位置的文件
offset:相对于 origin 位置的偏移量,设置0就表示指示器指向 origin
origin:起始位置,有三种选项:
①SEEK_CUR :指示器当前的位置
②SEEK_END:文件结尾
③SEEK_SET :文件开头
返回值:如果设置成功,它将返回0;否则返回非0值
1-2.ftell 函数
ftell 函数将返回指示器相对于文件开头位置的偏移量,函数原型如下:
stream:返回此文件的指示器偏移量
返回值:成功后。将返回偏移量;否则返回-1
1-3.rewind 函数
让指示器返回到文件开头,函数原型如下:
stream:让stream的指示器指向文件开头
1-4.案例
下面是一个案例:得到一个文件的大小并读取文件中的所有内容。
我们先自定义好一份文件中的内容,随便在网上复制一些内容:
完整代码如下:
cpp
#include <stdio.h>
#include <stdlib.h>
int main()
{
FILE* pf = fopen(".\\text.txt", "r"); // 以读方式打开文件
if (pf == NULL)
{
perror("文件打开失败");
return 0;
}
// 让指示器指向文件结尾
fseek(pf,0,SEEK_END);
// 得到当前位置(文件结尾)相对于文件开头的偏移量
int size = ftell(pf); // 这个偏移量就是文件内容的总字节大小
// 将指示器复位(重新指向文件开头),以便之后的读取
rewind(pf);
// 开辟一块空间,用于存放读取的文件内容,注意'\0'也要计算大小
char* content = (char*)malloc(size+1);
// 读取文件内容
fgets(content,size+1,pf);
// 打印content
printf("%s\n",content);
return 0;
}
第二节:文件的二进制读写
为什么要有二进制读写?
文件之所以要有二进制读写方式,主要是基于以下几个原因:
-
精确控制数据 :
二进制读写允许你以字节为单位精确地控制数据的读写。在处理非文本文件(如图片、视频、音频文件等)时,这些文件中的数据是以二进制形式(即0和1的组合)存储的。使用二进制读写可以直接处理这些字节数据,而无需进行任何形式的转换或解释。
-
性能优化 :
对于大文件或需要高速读写的应用场景,二进制读写通常比文本读写更高效。文本读写可能需要将二进制数据转换为特定的字符编码(如UTF-8),并在读写时进行转换,这会增加额外的处理时间。而二进制读写则直接操作字节,避免了这些额外的开销。
-
兼容性和移植性 :
在处理跨平台或跨语言的文件时,二进制读写可以保证数据的精确性和一致性。由于文本文件可能受到不同平台字符编码和换行符差异的影响,使用二进制读写可以避免这些问题,确保数据在不同平台和语言之间的一致性和可移植性。
-
处理复杂数据结构 :
在序列化复杂数据结构(如对象、数组等)到文件时,二进制格式提供了一种更加紧凑和高效的方式来存储这些结构。通过将数据结构转换为二进制表示,并在需要时重新恢复为原始结构,可以实现快速的数据交换和存储。
-
直接操作底层数据 :
在需要直接操作硬件或系统底层数据的场景中(如驱动程序开发、嵌入式系统开发等),二进制读写是必不可少的。这些场景要求直接读写设备的内存地址或寄存器,而这些数据通常是以二进制形式存在的。
以上内容来自文心一言
二进制操作如下:
cpp
#include <stdio.h>
#include <stdlib.h>
int main()
{
FILE* pf = fopen(".\\text.txt", "w+b"); // 以二进制读写方式打开文件
if (pf == NULL)
{
perror("文件打开失败");
return 0;
}
// 以二进制形式存储数据
char put[] = "你好,世界,hello,world";
fwrite(put, sizeof(put), 1, pf);
// 指示器复位
rewind(pf);
// 读取二进制内容
char get[sizeof(put)];
fread(get, sizeof(put), 1, pf);
// 打印数据
printf("%s\n", get);
return 0;
}
文件中的中文变成了一些看不懂的符号,但是读取回来的内容与之前相同。说明vs编译器和txt文件对英文字符的编码格式相同,对中文的编码格式不同。
接下来我们简单介绍一下二进制的读写操作:
2-1.文件的二进制打开
①rb:二进制读
②wb:二进制写
③ab:二进制追加
④r+b:二进制读写,不清空文件内容
⑤w+b:二进制读写,要清空文件内容
⑥a+b:二进制追加+读
2-2.fread 二进制读函数
函数原型如下:
ptr:存放读取到的内容的空间
size:一次读取的字节数(一个元素的大小)
count: 读取次数(元素个数)
stream:被读取的文件
返回值:返回成功读取的元素的个数;读取失败返回值小于count,如果因为文件内容少,读到文件结尾返回值也会小于count,需要用 feof 函数判断文件是不是正常结束的。
feof 函数的用法如下:
cpp
int feof(FILE* stream); // 正常结束返回真,否则返回假
2-3.fwrite 二进制写函数
函数原型如下:
ptr:存放将写入的内容的空间
size:一次写入的字节数(一个元素的大小)
count: 写入次数(元素个数)
stream:被写入的文件
返回值:返回成功写入的元素的个数;写入时如果发生错误则返回值小于count。
2-4.图片读写
图片在文件中是以二进制的形式存储的,我们可以用二进制读写来操作图片文件,比如将一份图片进行复制,具体做法如下:
首先在网上随便找一张图片放在代码文件夹下,然后自己创建一个副本,后缀要一致:
此时这个副本里是没有内容的,完整代码如下:
cpp
#include <stdio.h>
#include <stdlib.h>
int main()
{
FILE* pf1 = fopen(".\\山水风景.png", "r+b"); // 以二进制读方式打开原图片
FILE* pf2 = fopen(".\\山水风景副本.png", "w+b"); // 以二进制写方式打开副本图片
if (pf1 == NULL || pf2 == NULL)
{
perror("文件打开失败");
return 0;
}
// 让指示器指向文件结尾
fseek(pf1, 0, SEEK_END);
// 得到当前位置(文件结尾)相对于文件开头的偏移量(文件大小)
int size = ftell(pf1);
// 开辟一块空间,用于存放读取的文件内容
char* content = (char*)malloc(size);
// 返回文件开头
rewind(pf1);
// 读取内容
fread(content, size, 1, pf1);
// 写入内容
fwrite(content, size, 1, pf2);
return 0;
}
执行上述代码,就得到一个副本图片了:
第三节:printf 与 fprintf、scanf 与 fscanf的联系
3-1.printf 与 fprintf
printf 函数实际上是 fprintf 函数的一种特殊情况:printf 函数的本质是向屏幕文件 写入数据,而 fprintf 函数是向**任意文件(包括屏幕文件)**写入数据。
我们知道,一个文件在被操作时先要要用 fopen 打开这个文件,但是 printf 函数似乎不需要打开屏幕文件,就可以向屏幕输出数据。真实情况是屏幕文件在程序执行时是被默认打开的,不需要我们打开。
既然屏幕文件默认是打开的,那么它有没有文件指针指向它呢?答案是肯定的,这个文件指针是一个来自C语言标准库的外部变量 stdout,它包含在<stdio.h>文件中。
我们可以使用 stdout + fprintf 向屏幕打印数据:
cpp
#include <stdio.h>
#include <stdlib.h>
int main()
{
char arr[] = "Hello,world";
fprintf(stdout,"%s\n",arr); // 与下面条语句等价
printf("%s\n", arr);
return 0;
}
屏幕文件的本质:
屏幕文件本质是一块缓冲区,当缓冲区的内容满足一定条件时,缓冲区的内容才会输出到屏幕上,有以下3种条件:
(1)屏幕文件关闭:当程序结束和调用 fclose(stdout) 时,屏幕文件都会关闭,缓冲区的内容都会输出到屏幕上;
(2)缓冲区满:当缓冲区被占满时,里面的内容会被输出到屏幕上,以腾出空间;
(3)出现'\n':当缓冲区的内容中有换行符时,会把换行符之前(包括换行符)的所有数据输出到屏幕。
3-2.scanf 与 fscanf
与上面类似,scanf 是 fscanf 的一种特殊情况:scanf 是从键盘文件 获取数据写到任意变量中;fscanf 是从任意文件(包括键盘文件) 获取数据写到任意变量中。而指向键盘文件的文件指针是 stdin。
我们也就可以使用 stdin + fscanf 从键盘获取数据写到变量中:
cpp
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{
int a = 0;
fscanf(stdin,"%d",&a); // 等价于 scanf("%d", &a);
printf("a=%d\n", a);
return 0;
}
下期预告:
下一次的内容是综合案例,我们要使用前面所学的知识(特别是文件操作)写一个通讯录,它具有以下功能:
(1)添加联系人
(2)删除联系人
(3)根据名字查询联系人的其他信息
(5)修改已有联系人的信息
(6)程序每次运行都保存上次的所有联系人