C语言重点篇:近万字总结文件操作函数

Posted 变秃变强 呀

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言重点篇:近万字总结文件操作函数相关的知识,希望对你有一定的参考价值。

铺垫知识

什么是文件

文件是计算机文件,属于文件的一种,与普通文件的载体不同,计算机文件是以计算机硬盘为载体存储在计算机上的信息集合。
在程序设计中,我们一般关注的文件有两类,即程序文件和数据文件。
程序文件: 包括源程序(以.c为后缀)和可执行程序(以.exe为后缀)。
数据文件: 文件的内容不一定是程序,而是程序运行时读写的数据。比如程序运行时需要从中读取数据的文件,或者输出内容的文件。
注:这里本篇内容讨论的是数据文件。

文件名

一个文件要有一个唯一的文件标识,以便用户识别和引用。
文件标识主要包含三部分:文件路径+文件名主干+文件后缀。
例如

但是为了方便起见,文件标识常被称为文件名。

文件类型

根据数据的组织形式,数据文件被称为二进制文件或文本文件。
二进制文件: 数据在内存中以二进制的形式进行存储,如果不加转换直接输出到外存,就是二进制文件。
文本文件: 如果要求在外存上以ASCII码的形式存储,则需要在存储前进行转换。以ASCII码的形式进行存储的文件就是文本文件。
那么一个数据在外存中是如何存储的呢?
字符一律以ASCII码值进行存储;数值型数据既可以以ASCII码值进行存储,也可以以二进制的形式进行存储。
例如,有整数10000,如果我们以ASCII值的形式输出到磁盘,那么它将在磁盘中占用5个字节(一个字符一个字节);而如果以二进制的形式进行输出,那么它只在磁盘中占用4个字节(一个整型大小即可存储):

文件缓冲区

文件缓冲区是用以暂时存放读写期间的文件数据而在内存区预留的一定空间。使用文件缓冲区可减少读取硬盘的次数。

文件缓冲系统

是指系统自动地在内存中为程序中每一个正在使用的文件开辟开辟一块“文件缓冲区”。从内存向磁盘输出的数据会先送到内存中的缓冲区,待缓冲区装满后才一起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区,待缓冲区装满后,再从缓冲区逐个地将数据送到程序数据区。缓冲区的大小是根据C编译系统决定。

为什么存在文件缓冲区

相信有很多人心里还不理解为什么存在缓冲区。举个比较形象的例子,当你的老师在忙于备考时,你有一点小问题就去请教老师,有一点小问题又去请教老师,这种情况下老师的备考效率会大大降低,那么你为什么不将你的小问题累计起来,当累计到一定量时再一次性去请教老师,这样老师的备考效率也会相对提高。
例子中的“老师”就好比操作系统,“小问题”就好比需要操作系统传输的信息,而“你积累问题到一定量再去请教老师”就好比缓冲区的工作机制。
总而言之,缓冲区的存在大大提高了操作系统的效率。

文件指针

每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字、文件的状态及文件的当前位置等)。这些信息是保存在一个结构体变量中的,该结构体变量是由系统声明的,并将该结构体类型重命名为FILE。
例如,在VS2013编译器环境提供的stdio.h头文件中有以下的文件类型声明:

struct _iobuf {
        char *_ptr;
        int   _cnt;
        char *_base;
        int   _flag;
        int   _file;
        int   _charbuf;
        int   _bufsiz;
        char *_tmpfname;
        };
typedef struct _iobuf FILE;

不同的C编译器的FILE类型包含的内容不完全相同,但是都大同小异。
每当我们打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并填充其中的信息。
我们可以创建一个FILE*的指针变量:

FILE* pf;//文件指针变量

定义pf是一个指向FILE类型的指针变量,可以使pf指向某个文件的文件信息区,通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量就能找到与它关联的文件。
比如:

相对路径和绝对路径

"data.txt"这个文件是与我们目前运行的源代码同级的文件

相对路径
上一级:…/data.txt (只要要到上一级去就要在前面加上…/)
下一级:Debug/data.txt(此时Debug与我们运行的源代码在同级目录在,而data则为Debug的下一级)

可能有的小伙伴会觉得这样操作比较复杂,那下面我们来看看绝对路径

绝对路径

D:\\\\code\\\\File_function\\\\File_function\\\\data.txt

注意:文件的路径原本为"D:\\code\\File_function\\File_function\\data.txt",但是为了防止字符串中的’\\‘及其后面的字符被整体视为为转义字符,所以需要在每个’\\‘后面再加一个’\\’。

文件的打开方式

使用上面的模式说明符,文件将作为文本文件打开。为了将文件作为二进制文件打开,模式字符串中必须包含“b”字符。这个附加的“b”字符可以附加在字符串的末尾(从而形成以下复合模式:“rb”、“wb”、“ab”、“r+b”、“w+b”、“a+b”),也可以插入在“+”符号之前(“rb+”、“wb+”、“ab+”)。

举个几个例子
1.若我们要以文本形式打开一个名叫data.txt的文件,将要对其进行输入操作,那么打开文件时应该这样写:

FILE* pf = fopen("test.txt", "r");

注意:如果没有目标文件则会返回一个空指针

2.若我们要以二进制打开一个名叫data.bin的文件,将要对其进行输出操作,那么打开文件时应该这样写:

FILE* pf = fopen("test.txt", "wb");

注意:如果没有目标文件,当以w的任何组合形式写的时候,都会自动创建一个,且会丢弃其原来的内容。
一开始没有这个文件

经过代码执行以后:

如果我一开始在test文件中有内容

我们会发现当执行完代码后它会将原内容删除掉的

文件操作函数(顺序读写的)

字符输入输出函数

fputc


函数的第一个参数是要写入的字符,第二个参数写就是目的地(一个文件流),即将c写入到你要写入的文件中。如果成功了,则返回你存入的那个数据,失败则返回EOF。
例如:
将abcd存入到文件中

	FILE* pf = fopen("test.txt", "wb");
	if (pf == NULL)
	{
		printf("%s\\n", strerror(errno));
		return 1;
	}
	char a[] = "abcd";
	int i = 0;
	while (a[i] != '\\0')
	{
		fputc(a[i], pf);
		i++;
	}
	fclose(pf);
	pf = NULL;
	return 0;

fgetc


可以看到它只有一个参数,它的功能是将文件流中的内存一个字符的读出,读取成功则返回其对应的字符,若读取失败或者已经读取到文章末尾,则返回EOF。

例如:将刚刚输入文件中的abcd全部打印出来

	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		printf("%s\\n", strerror(errno));
		return 1;
	}
	int ch = 0;
	while ((ch = fgetc(pf)) != EOF)
	{
		printf("%c", ch);
	}
	fclose(pf);
	pf = NULL;
	return 0;

文本行输入输出函数

fputs


它和fputc非常相似,只不过他可以写入字符串到文件流中,而fputc只能一个字符的写入。如果写入成功则返回一个非负的值,如果失败,则返回EOF。

例如:直接将abcdfeg输入到目标文件中去

	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		printf("%s\\n", strerror(errno));
		return 1;
	}
	char a[] = "abcdefg";
	fputs(a, pf);
	fclose(pf);
	pf = NULL;
	return 0;

fgets


fgets函数的第三个参数是你要读取的文件的文件指针,第二个参数是你要读取的字符个数(也可以说是字节个数),第一个参数是你所读取到的数据的储存位置。fgets函数的功能就是从指定位置读取指定字符个数的数据储存到指定位置。该函数调用成功会返回用于储存数据的位置的地址,如果读取过程中发生错误,或是读取到了文件末尾,则返回一个空指针(NULL)。

例如:将刚刚存入的adcdefg读取并打印出来

int main()
{
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		printf("%s\\n", strerror(errno));
		return 1;
	}
	char a[8] = { 0 };
	fgets(a,8,pf);
	printf("%s", a);
	fclose(pf);
	pf = NULL;
	return 0;
}

注意:
1.在fgets函数读取到指定字符数之前,若读取到换行符(’\\n’),则停止读取,读取带回的字符包含换行符。
2. 直到fgets函数读取到第n-1个字符时都没有遇到换行符(’\\n’),则返回读取到的n-1个字符,并在末尾加上一个空字符一同返回(共n个字符)。

格式化输入输出函数

fprintf


它的功能是将第三个参数中的值输入到文件流中,返回值和printf一样,返回的是元素的个数。

其实,fprintf函数的功能就是将“区域三”的数据,以“区域二”的格式输出到“区域一”。
例如:将一个结构体型的变量信息输入到文件流中

struct stu
{
	int age;
	char name[20];
	char sex[5];
};

int main()
{
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		printf("%s\\n", strerror(errno));
		return 1;
	}
	struct stu a = { 10, "xujiangtao", "nan" };
	fprintf(pf, "%d %s %s\\n", a.age, a.name, a.sex);
	fclose(pf);
	pf = NULL;
	return 0;
}

fscanf


fscanf和scanf非常的相似,他也是用来输出的,它的功能是读取文件流中的信息。、
其实,fscanf函数的功能就是将“区域一”的数据,以“区域二”的格式输入到“区域三”。

例如:将刚刚输入到文件流中的结构体打印出来

	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		printf("%s\\n", strerror(errno));
		return 1;
	}
	struct stu a = {0};
	fscanf(pf,"%d %s %s\\n", &a.age, a.name, a.sex);
	printf("%d %s %s\\n", a.age, a.name, a.sex);
	fclose(pf);
	pf = NULL;
	return 0;

二进制输入输出函数

fwrite


它有四个参数,功能是写入数据到一个文件流中,在思考思考,我们大概就理解了,哦。原来他是将buffer这个空间里count个大小为size的元素写入到目标文件流中(以二进制的形式写入)。该函数调用完后,会返回实际写入目标位置的元素个数,当输出时发生错误或是待输出数据元素个数小于要求输出的元素个数时,会返回一个小于count的数。

例如:将数组a中的元素以二进制的形式写入到文件流中

int main()
{
	FILE* pf = fopen("test.txt", "wb");
	if (pf == NULL)
	{
		printf("%s\\n", strerror(errno));
		return 1;
	}
	int a[] = { 0, 1, 2, 3, 4 };
	fwrite(a, 4, 5, pf);
	fclose(pf);
	pf = NULL;
	return 0;
}

此时你会发现文件中出现了一些你看不懂的东西,这也很正常,因为它是要以二进制的形式写入的,人是看不懂得,要经过转化才行。

fread


它和fwrite参数基本一样,只不过它的功能是读取你存入到文件流中的信息。函数调用完会返回实际读取的元素个数,若在读取过程中发生错误或是在未读取到指定元素个数时读取到文件末尾,则返回一个小于count的数。

例如:将刚刚存入文件流中的数组元素读取出来

	FILE* pf = fopen("test.txt", "rb");
	if (pf == NULL)
	{
		printf("%s\\n", strerror(errno));
		return 1;
	}
	int a[5] = {0};
	fread(a, 4, 5, pf);
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%d ", a[i]);
	}
	fclose(pf);
	pf = NULL;
	return 0;

顺序读写的弊端

我们介绍了文件的顺序读写函数,即挨着顺序对文件中的数据进行输入或输出操作的函数。但是若只能规规矩矩地,从前到尾对文件进行输入输出操作,那么就比较笨拙,为了能更加灵活地对文件进行输入输出操作,出现了另一类文件操作函数,使得我们可以对文件进行随机读写。
需要注意的是,随机读写并非胡乱读写文件的某一位置,而是按照操作者的意愿,随机读写文件的某一位置。

所以,如果我们继续用fgetc函数向后读取文件内容,那么读取到的就是字符’c’了。
那么,在读取了字符’a’和字符’b’后,我们如何再次读取到字符’a’,或是不读取下一个字符’c’,而读取其后的某一指定位置的数据呢?
这就是所谓的文件的随机读写了。

文件操作函数(随机读写)

fseek


fseek函数的第一个参数既是要移动位置的文件指针,第三个参数是“初始位置”(并非文件信息区的起始位置),第二个参数是文件指针经操作后相对于这个“起始位置”的偏移量,单位为字节。fseek函数如果调用成功,则返回0;若调用失败,则返回一个非0的值。
因此我们可以得到一个公式:进行玩fseek之后的位置=origin+offset(可能是负数)
关于fseek函数的第三个参数,可以有以下三种形式:

例如:在输出完abcd后,我让pf回到a的位置,打印a

	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		printf("%s\\n", strerror(errno));
		return 1;
	}
	int ch = 0;
	while ((ch = fgetc(pf)) != EOF)
	{
		printf("%c", ch);
	}
	printf("\\n");
	fseek(pf, 0, SEEK_SET);
	printf("%c\\n", fgetc(pf));
	fclose(pf);
	pf = NULL;
	return 0;

ftell


为了更好地明确文件指针位于什么位置,于是出现了ftell函数,它用于计算当前文件指针相对于起始位置的偏移量。
ftell函数的参数是一个文件指针。ftell函数调用成功,则返回文件指针相对于起始位置的偏移量;若调用失败,则返回 - 1。
例如:我们在读取了abcd后,可以看看此时pf相对于其实位置的偏移量

	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		printf("%s\\n", strerror(errno));
		return 1;
	}
	int ch = 0;
	while ((ch = fgetc(pf)) != EOF)
	{
		printf("%c", ch);
	}
	printf("\\n");
	printf("%ld\\n", ftell(pf));
	fclose(pf);
	pf = NULL;
	return 0;

rewind

rewind函数的作用是让传入的文件指针返回文件的起始位置。例如,在上述例题中,我们可以直接让文件指针返回文件的起始位置,这样就能读取到字符’a’:

利用fseek和ftell求一个文件的大小

求一个文件的大小其实很简单,你只需要将文件指针移到该文件末尾,然后求文件指针相对于起始位置的偏移量即可。

	fseek(pf, 0, SEEK_END);//将文件指针置于文件末尾
	int FileLen = ftell(pf);//求文件指针相对于文件起始位置的偏移量
	fseek(pf, 0, SEEK_SET);//将文件指针放回文件开头

文件的结束判定

这个问题你是否思考过?

我们介绍了一系列的与文件的顺序读写有关的函数,它们调用成功与失败时的返回值各不相同,为了能更好地记忆这些函数,我们将这些函数的返回值进行了总结,并列入表格:

这样列举后我们会发现一个问题:
1.fgetc函数返回EOF时,可能是读取数据时发生错误,也可能是已经读取到文件末尾了。
2.fgets函数返回NULL时,可能是读取数据时发生错误,也可能是已经读取到文件末尾了。
3.fscanf函数返回EOF时,可能是读取数据时发生错误,也可能是在第一次转换之前到达文件流的末尾。
4.fread函数返回的值小于要求读取的完整项的数目时,可能是读取数据时发生错误,也可能是在达到读取的规定数目之前遇到文件结尾。

这些不同的情况返回的却是同一个值,当fgetc函数返回EOF时,我们不能断定一定是读取数据时发生错误了,当fscanf函数返回EOF时,我们也不能断定它就是文件读取结束了。所以,出现了一类函数,它们的功能就是,判断文件操作函数是以一种什么方式结束。

注意:以下介绍的函数的功能不是判断文件操作函数是否调用失败,而是在文件操作函数已经调用失败的情况下,判断文件操作函数调用失败的原因

ferror


ferror函数的功能就是判断使用某一文件指针的过程中,是否是因为发生错误而结束的,若使用时没有发生错误,则ferror函数返回0;否则,ferror函数将返回一个非零的值。调用ferror函数时,我们只需将待检查的文件指针传入即可。

	if (ferror)
	{
		printf("文件指针使用时,发生错误\\n");
	}

feof


feof函数的功能也是判断使用某一文件指针的过程中,是否读取到文件末尾,若使用时没有读取到文件末尾,则feof函数返回0;否则,feof函数将返回一个非零的值。调用feof函数时,也只需将待检查的文件指针传入即可。

	if (feof(pf))
	{
		printf("文件指针使用时,读取到文件末尾\\n");
	}

ferror函数和feof函数搭配使用

当前言中说到的文件操作函数调用失败时,我们就可以同时运用ferror函数和feof函数,来判断文件操作函数调用失败的准确原因。
例如,文件data.txt文件中的数据为"abcdf",我们要用fgetc函数读取data.txt文件中的数据,当数据读取完之后,我们就可以用ferror函数和feof函数,来判断最后一次fgetc函数调用失败的原因:

	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		printf("%s\\n", strerror(errno));
		return 近万字的Numpy总结——边学边练

近万字的Numpy总结——边学边练

爆肝近万字总结----Linux进程控制系列文章,你确定不点进来看看?

还不会Git吗?近万字总结,让你从入门到精通!

ORACLE数据库应用开发三十忌!助你涨薪10k的二十年经验教训总结,华为工程师也曾栽过跟头,近万字长文吐血推荐!!建议收藏!

近万字带你了解“c++“STL中的各种容器