C语言-第九章-加餐:文件位置指示器与二进制读写

传送门:C语言-第九章:文件读写

目录

第一节:文件位置指示器

        1-1.fseek 函数

        1-2.ftell 函数

        1-3.rewind 函数

        1-4.案例

第二节:文件的二进制读写

        2-1.文件的二进制打开

        2-2.fread 二进制读函数

        2-3.fwrite 二进制写函数

        2-4.图片读写

第三节:printf 与 fprintf、scanf 与 fscanf的联系

        3-1.printf 与 fprintf

        3-2.scanf 与 fscanf

下期预告:


第一节:文件位置指示器

        文件位置指示器是下一次操作要对文件进行操作的位置(读或写),可以理解为光标的位置。

        进行读操作和写操作时,指示器的初始位置都在文件开头;

        进行追加操作时,指示器的位置在文件结尾。

        进行读写操作时,指示器的位置也会随之后移,我们也可以用用一些函数改变文件指示器的位置。

        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.案例

        下面是一个案例:得到一个文件的大小并读取文件中的所有内容。

        我们先自定义好一份文件中的内容,随便在网上复制一些内容:

        完整代码如下: 

#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;
}

第二节:文件的二进制读写

        为什么要有二进制读写?

        

文件之所以要有二进制读写方式,主要是基于以下几个原因:

  1. 精确控制数据
    二进制读写允许你以字节为单位精确地控制数据的读写。在处理非文本文件(如图片、视频、音频文件等)时,这些文件中的数据是以二进制形式(即0和1的组合)存储的。使用二进制读写可以直接处理这些字节数据,而无需进行任何形式的转换或解释。

  2. 性能优化
    对于大文件或需要高速读写的应用场景,二进制读写通常比文本读写更高效。文本读写可能需要将二进制数据转换为特定的字符编码(如UTF-8),并在读写时进行转换,这会增加额外的处理时间。而二进制读写则直接操作字节,避免了这些额外的开销。

  3. 兼容性和移植性
    在处理跨平台或跨语言的文件时,二进制读写可以保证数据的精确性和一致性。由于文本文件可能受到不同平台字符编码和换行符差异的影响,使用二进制读写可以避免这些问题,确保数据在不同平台和语言之间的一致性和可移植性。

  4. 处理复杂数据结构
    在序列化复杂数据结构(如对象、数组等)到文件时,二进制格式提供了一种更加紧凑和高效的方式来存储这些结构。通过将数据结构转换为二进制表示,并在需要时重新恢复为原始结构,可以实现快速的数据交换和存储。

  5. 直接操作底层数据
    在需要直接操作硬件或系统底层数据的场景中(如驱动程序开发、嵌入式系统开发等),二进制读写是必不可少的。这些场景要求直接读写设备的内存地址或寄存器,而这些数据通常是以二进制形式存在的。

以上内容来自文心一言

        二进制操作如下:

#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 函数的用法如下:

int feof(FILE* stream); // 正常结束返回真,否则返回假

        2-3.fwrite 二进制写函数

        函数原型如下:

        ptr:存放将写入的内容的空间

        size:一次写入的字节数(一个元素的大小)

        count: 写入次数(元素个数)

        stream:被写入的文件

        返回值:返回成功写入的元素的个数;写入时如果发生错误则返回值小于count。

        2-4.图片读写

        图片在文件中是以二进制的形式存储的,我们可以用二进制读写来操作图片文件,比如将一份图片进行复制,具体做法如下:

        首先在网上随便找一张图片放在代码文件夹下,然后自己创建一个副本,后缀要一致:

 

         此时这个副本里是没有内容的,完整代码如下:

#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 向屏幕打印数据:

#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 从键盘获取数据写到变量中:

#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)程序每次运行都保存上次的所有联系人

传送门:C语言-综合案例:通讯录

  • 26
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值