耗尽心力整理——你对文件到底了解多少?(万字预警,目录里各取所需)

什么是文件

文件保存在硬盘上的一种特殊形式。
我们一般谈的文件有两种:程序文件、数据文件
程序文件:包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀 为.exe)。
数据文件:文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容文件。
主要讨论的是数据文件

文件=文件的内容+文件的属性(元信息)【我们是通过属性来认识世界的】
文件大小为0kb的时候,文件再硬盘上的大小真的是0kb吗?答案是不是的,除了文件的内容还有文件的属性。文件的属性包括文件名、文件类型、创建日期这些都是数据都要存储的。

文件的操作

对内容的操作+对属性的操作
一个文件要有一个唯一的文件标识,以便用户识别和引用
文件的路径:定位
文件名称:确定某个路径下的指定文件
文件名=文件路径+文件名主干+文件后缀(代表文件类型)

文件类型

根据数据的组织形式,数据文件被称为文本文件或者二进制文件。
以10000为例
数据在内存中以二进制(32个01 形式)的形式存储,并且在硬盘上(外存)上也以二进制存储,那么这就是二进制文件。
如果要求在硬盘上(外存)上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件。字符在内存中以ASCII形式存储,以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符一个字节)。
在这里插入图片描述

二进制文件代码

内存中将10000 转换成二进制,在文件中用二进制解释,只是最后显示的时候用16进制显示。

int a = 10000;
	//FILE* pf = fopen("E:\\test.txt", "wb");
	FILE* pf = fopen("E:\\test.txt", "w");

	//注意反斜杠会被特殊解释,所以在这里要对\再次 \
	//如果此文件不存在会创建
	//打开什么文件文件名,用二进制写入的方式。
	//返回值* pf
	if (pf ==NULL)
	{
		perror("fopen");
		system("pause");
		return 1;
	}
	//fwrite(&a, 4, 1, pf);
    //写谁 写入的基本单位的大小
	//一次写几个基本单位
	//pf 向哪里写入
	fclose(pf);
	//关闭文件
	pf = NULL;


将文本test.txt放在编译器中,切换打开方式用二进制的方式打开,显示如图所示
在这里插入图片描述
实际存储:31
字符0的ascii码值是48 1 对应 49 (16进制显示31)
在这里插入图片描述

文本文件代码

改变文件的写入方式

FILE* pf = fopen("E:\\test.txt", "w");
//默认写入方式w
if (pf ==NULL)
	{
		perror("fopen");
		system("pause");
		return 1;
	}
	const char* str = "10000";
	fwrite(str, strlen(str), 1, pf);
	fclose(pf);
    pf = NULL;

在这里插入图片描述
补充
拿记事本来说,它首先读取文件物理上所对应的二进制比特流(前面已经说了,存储都是二进制的),然后按照你所选择的解码方式来解释这个流,然后将解释结果显示出来。一般来说,你选取的解码方式会是ASCII码形式。但请注意,这里可以手动的更改解释方式。从而解释了.txt后缀的不一定是文本文件。
.txt后缀的一定是文本文件,这样的理解是不正确的。
注:后缀名只是用来关联打开的程序的,与文件的编码方式无关,所以与文件格式也就无关。.txt也可以是二进制文件。

手动更改txt 文件读取方式的操作:
手动更改txt 文件读取方式的操作

文件缓冲区

观察缓冲区所带来的现象

将以下代码在linux 中进行运行并观察有何区别

在这里插入图片描述
2.更改为

print("hello,world\n");
sleep(3);

发现好像第一个是程序先等待一会,之后再退出前打印出来 hello,world
而第二个是先打印出来了 hello,world,之后等待一会,程序退出。
当然,这只是我们目前观察到的。

理解
代码自上向下运行,不论是否有\n,printf都是最先运行的。在linux 中一切文件,往显示器打印的过程与文件类似。
其实执行完printf之后,hello ,world 已经被写到了缓冲区,只是刷新的策略不一样。
有\n时,立即将缓冲区的内容按照行刷新到显示器上。
没有\n时,在重新退出前才会刷新。
在这里插入图片描述

fflush 函数与缓冲区

那么如何使不加\n 还能够和加了\n 的效果一样呢?即先打印hello,world在sleep再程序退出?
用到函数fflush

fflush用于清空缓冲流,默认printf是缓冲输出的。
头文件:#include <stdio.h>
定义函数:int fflush(FILE* stream);
函数说明:fflush()会强迫将缓冲区内的数据写回参数stream 指定的文件中. 如果参数stream 为NULL,fflush()会将所有打开的文件数据更新.
返回值:成功返回0, 失败返回EOF, 错误代码存于errno 中.
补充知识

c程序在启动的时候,由系统默认打开三个文件,标准输入,标准输出,标准错误,依次对应的设备是键盘,显示器,显示器。
对应的FILE *分别为 叫做:stdin stdout stder

因此要做到上述的需求,不加\n 还能够和加了\n 的效果一样,添加代码fflush(FILE*stdout),对显示器刷新即可。
显示器和文件没什么差别(可以这么理解)

认识缓冲区

缓冲区是什么:一块内存区域
缓冲区存在的价值:提高程序运行效率
缓冲刷新策略:
无缓冲、行缓冲、全缓冲
普通硬盘上面的文件c采用的是全缓冲
一般显示器对应的缓冲方式则是行缓冲。(一般文件是在磁盘上的)

行缓冲:缓冲区大小就是一行,写满或者主动刷新(为了用户友好)。
全缓冲:可以理解缓冲区的容量比较大,不是按照行,是按照缓冲区的整体容量刷新的(写满整个缓冲区再执行刷新策略)那什么时候用呢?缓冲区写满了,数据据显示到硬盘中。(硬盘中的文件内容是不需要马上显示给用户)

fwrite 函数采用的是全缓冲,程序要退出是采用刷新策略,问价大小才会变化。
为什么刷新不直接把数据刷新到显示器上,还要再硬盘里面再拷贝一份呢?

在这里插入图片描述

文件指针

文件的标识作用,文件指针又称为句柄,通过文件指针来对文件操作。

(挟天子以令诸侯,掌天下之柄)
文件相当于天下,文件指针相当于汉献帝,曹操通过汉献帝操纵天下,我们通过文件指针操纵文件。

程序要运行起来必须先加载到内存,程序即使就是一个exe文件,所以数据文件是需要从硬盘拷贝一份到内存的。

系统自动会给每个被使用的文件,在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及 文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是有系统声明的,取名FILE.

FILE* pf;//文件指针变量
本质是一个结构体指针
定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文 件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件。

在这里插入图片描述

文件的打开和关闭

在这里插入图片描述

对比用 r 和 w 的方式打开一个不存在文件

用只读方式打开不存在的文件会报错
在这里插入图片描述

如果用只写的方式打开一个文件,如果文件不存在,就会建立一个文件。
在这里插入图片描述

对比W 和 a 的 读写文件的区别

“w”(只写)清空式写入

在这里插入图片描述
每打开一次文件,文件都会被清空
在这里插入图片描述

“a”(追加)追加式写入

在这里插入图片描述

重现打开文件, 向文本文件尾添加数据 。追加写入我们可以发现原来写入的123456并没有被置空。

在这里插入图片描述
结论

文件每打开一次,w 会被置空文件, 从文件开始写入。

文件每打开一次,a 不会被置空文件,而是从文件末尾开始写。

文件顺序读写(文本类)

文本行输出函数 fputs

作用:将字符串写入文件
int fputs ( const char * str, FILE * stream );
返回值成功时,将返回非负值。
出现错误时,该函数返回EOF并设置错误指示符(ferror)。

在前面曾经提到过,一切皆文件的概念,在电脑看来我们程序的terminal (及终端 每次程序运行弹出的黑框框)也是文件。既然终端框框也是文件,这也就意味着,我们可以往终端中写入内容,那么fputs 和printfs到底有什么区别?

fputs 和printfs区别

简而言之的区别就是:是否会格式化写入终端文件
在这里插入图片描述
在这里插入图片描述

字符输出函数 fputc

int fputc ( int character, FILE * stream );
返回值:成功时是该是字符,失败返回EOF
(注意,在此处再次强化说明记忆以下,字符也是特殊的整型,他是整型家族的)

与fpus 的功能是类似的,都是往文件里面写入。但注意函数传递的参数是完全不同的,fputc的参数是单个字符,而fputs传递的参数是字符串 如果用fputs 传递单个字符会出现访问冲突的错误。

字符输入函数 fgetc

作用:读取文件里面的字符
int fgetc ( FILE * stream );
EOF== -1
如果在读取文件的过程中,文件已经读到了结尾,那么这时候的返回值是EOF,实际上,十进制显示,EOF就是-1.
在键盘中输入 ctrl+z就可 ^Z 代表文件结束

FILE*fp = fopen("E:\\xqr.txt", "r");
	if (fp == NULL)
	{
		perror("fopen");
		system("pause");
		return 1;
	}
	char c = 'a';
	while ((c = fgetc(fp))!=EOF)//注意要加括号,注意优先级问题。先从文件中一次读取字符,再判定该是否已经到达文件结尾。
	{
		fputc(c, stdout);
	}

	printf("%d\n",c);//读取退出时,c=-1

同理,还是提到过的一切皆是文件的概念,既然我们可以用fputc/s把字符/串写入终端中,自然也可以从终端中读取字符串


	FILE*fp = fopen("E:\\xqr.txt", "w");
	if (fp == NULL)
	{
		perror("fopen");
		system("pause");
		return 1;
	}
	char c = 'a';
	while ((c = fgetc(stdin)) != EOF)//从终端中读取字符后判定是否时结束的标志
	{
		fputc(c, fp);//将从终端中读取的字符写入fp文件中
	}

	printf("%d\n", c);

	fclose(fp);

在这里插入图片描述
文件写入成功
在这里插入图片描述
虽然这里我们往终端中输入的是一行,但实际读取的时候其实还是一个个读取,写入。

EOF

在while循环中以EOF作为文件结束标志,这种以EOF作为文件结束标志的文件,必须是文本文件。在文本文件中,数据都是以字符的ASCII代码值的形式存放。
我们知道,ASCII代码值的范围是0~127(还有一部分作为扩展ASCII码128~255当操作系统采用非ASCII编码时(比如汉字编码),两个acsii码代表一个汉字)不可能出现-1,因此可以用EOF作为文件结束标志。
实际上 EOF 的值通常为 -1,但它依系统有所不同。
要注意的是:在终端(黑框)中手动输入时,系统并不知道什么时候到达了所谓的“文件末尾”,因此需要用<Ctrl + z>组合键然后按 Enter 键的方式来告诉系统已经到了EOF,这样系统才会结束while.

fgets文本行输入函数

char * fgets ( char * str, int num, FILE * stream );
从文件中读取字符,并将它们作为C字符串存储到str中,传参时传递的是指向数组的指针,复制到str中的最大字符数(包括终止的空字符),以及什么地方获取文件

返回值:成功时,该函数返回str。如果在遇到文件结尾,则设置feof。
如果发生读取错误,则会设置错误指示符(ferror)并返回空指针(但str指向的内容可能已更改)。
请注意以下两种写法的本质区别

	FILE*fp = fopen("E:\\xqr.txt", "w");
	if (fp == NULL)
	{
		perror("fopen");
		system("pause");
		return 1;
	}
	char arr[1024];
fgets(arr, 1024,stdin);//从文件中获取  fgets(arr, 1024,fp);//从输入终端中获取字符串
fputs(arr,fp);//写入文件中
fclose(fp);

当我们按下回车键的时候,到底发生了什么?
把我们输好了数据之后,按下回车,表示我们的数据写入完毕,数据会被写入数组,并且回车就相当于是\n 且会自动在后面添加\0作为结束,因此这里的回车并不会被当成是文本的内容。
在这里插入图片描述

	FILE*fp = fopen("E:\\xqr.txt", "w");
	if (fp == NULL)
	{
		perror("fopen");
		system("pause");
		return 1;
	}
	char* ret = NULL;
    char arr[1024];
	do
	{
		ret=fgets(arr, 1024, stdin);
		if (ret == NULL)
		{
			break;
		}
			fputs(arr, fp);
	} while (1);
	fclose(fp);

在这段程序中如果我们fgets按下enter 后的确是将内容写入到了数组中,但程序退出的条件是判断这个函数的返回值是否为feof。此时返回值的数组的首元素的地址,并未退出。之后再次fgets的时候,先前写的数组内容被清空(由于数组的内容已经被写入fp,所以再次写入的时候其实就相当于是重新写入)。
在这种写法中按下enter 键实际上和上面的一样,当作一次读取字符的结束,由于可以多次循环的原因,看起来好像回车也变成了文本的内容。
直到我们按下ctrl+z 的时候,数组的值是随机值,ret 的值是NULL,循环退出
在这里插入图片描述

格式化输出函数:fprintf

int fprintf ( FILE * stream, const char * format, … )

FILE*fp = fopen("E:\\xqr.txt", "w");
	if (fp == NULL)
	{
		perror("fopen");
		system("pause");
		return 1;
	}
	int a = 0;
	int b = 0;

	fprintf(stdout, "a=%d b=%d\n", a, b);//终端格式化写入
	fprintf(fp, "a=%d b=%d\n", a, b);//文件里面格式化写入
	fclose(fp);
fprintf 与prinf 的区别

区别就是前面的FILE * stream
int printf ( const char * format, … );
可以看到printf没有前面的文件这个参数
其实可以理解成
printf 就是fprintf 中把文件写成 stdout 的一种特殊形式
这样,以后我们不仅可以在终端中格式化输出,普通文件也可以格式化输出

格式化输入函数 fscanf

int fscanf ( FILE * stream, const char * format, … );

FILE*fp = fopen("E:\\xqr.txt", "r");
	if (fp == NULL)
	{
		perror("fopen");
		system("pause");
		return 1;
	}
	int a = 0;
	int b = 0;
	fscanf(stdin, "%d %d", &a, &b);//相当于scanf
	fprintf(stdout, "a=%d b=%d\n", a, b);//打印出来看看有没有成功从stdin 读

	fscanf(fp, "%d %d", &a, &b);//从文件里面的数据读取,获取a b的值
	fprintf(stdout, "a=%d b=%d\n", a, b)//打印出来看看有没有成功从FP 读

提前往文件里面写好数据
在这里插入图片描述

在这里插入图片描述

fscanf 与scanf 的区别

同理== 可以把scanf 看成是fscanf 文件为stdin 的一种特殊形式==
原来,只能通过键盘格式化写入,现在,普通文件里面的内容也可以格式化写入

细节注意

1.当需要用如上的代码对比观察的时候,需要将文件的打开方式变成r 。前面提到过,如果用w 的方式,每执行一次程序,文件都会被清空重置,为了对比是否真的从文件中格式化输入了内容,必须把打开方式变成r,否则提前写好的数据会被清空。
2.写scanf 的时候手滑写成了如下scanf("%d %d\n",&a,*b);出现了很大的问题,原来只是知道,后面不能加\n,不然会出问题,今天研究了以下,指路博客关于scanf 的格式化你可能不以为然的问题

二进制操作文件函数

在二进制操作文件中,主要需要熟练运用两个函数分别为fread 和 fwrite

fread

缓冲区
一共想读取的字节数
从哪里读
读取成功值大于0

fwrite
用相关函数拷贝一份图片

通常情况下我们对于一张图片复制是采用ctrl+c ctrl+v 的方式,但现在我们用代码来实现这件事情。
由于图片的本质是二进制的数据,所以我们用
二进制输入输出的函数来来做一个小的练习。
从网上下载了一个中国地图,将该图片文件放在对应c 文件的debug 文件之下。
在这里插入图片描述

在这里插入图片描述
(这样的做法主要是因为可以在打开文件的时候,不用再加上前面的路径)。然后执行以下代码

FILE *fp=fopen("中国地图.jpg", "rb");
	
	if (fp == NULL)
	{
		ferror(fp);
		system("pause");
	}
	FILE* fp2 = fopen("中国地图复制.jpg","wb");
	if (fp == NULL)
	{
		ferror(fp);
		system("pause");
	}
	char buff[10240];
	fread(buff, 1, sizeof(buff), fp);
	fwrite(buff, 1, sizeof(buff), fp2);

	fclose(fp);
	fp = NULL;
	fclose(fp2);
	fp2 = NULL;
	

我们发现图片似乎和原来的图片有些差异

在这里插入图片描述
在这里插入图片描述
关键问题就出在文件的大小上,回到上面的代码,发现从原始文件拷贝并写入到新文件中的字节大小是10240字节 也就是10kb。这也就是导致我们无法将文件看完全的原因。

接着我修改了数组大小102400字节 100kb
在这里插入图片描述
确实图片可以完整显示,可这样开辟空间的方式实在是有些麻烦

从而发现了问题的关键点就是到底到底开辟空间多大的数组来临时存放文件所对应的一系列二进制呢

这就需要设计到一些其他方面的知识

对文件内容操作的接口
对文件属性操作的接口

文件结尾判断

判定文件读取结束(并不等同于判定文件结尾)有可能会出现异常情况

判定文件结束原因

结束

本人对文件的理解大部分是来自于老师传授和自己查阅资料,处于一个比较表层,刚开始学习的阶段。整个博客涉及的方面稍微多一些,所以自然每个方面的解释不会过于细致,如果此篇博客并未涉及到读者想要查阅的内容,还望海涵。同时,欢迎各位小伙伴交流学习,彼此参照,相互进步。留下一个👍呗,我会积极更新呦!

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值