【C语言】文件操作(万字解读超详细解析)

最好的时光,在路上;最好的生活,在别处。独自上路去看看这个世界,你终将与最好的自己相遇。💓💓💓

目录

• ✨说在前面

🍋知识点一:什么是文件?

 • 🌰1.程序文件

 • 🌰2.数据文件

 • 🌰3.文件名

 • 🌰4.文本文件与二进制文件

🍋知识点二:文件的打开与关闭

  • 🌰1.流与标准流

🔥流的概念

 🔥标准流

  • 🌰2.文件指针

  • 🌰3.文件的打开与关闭

🔥fopen和fclose

🍋知识点三:文件的顺序读写

   • 🌰1.顺序的读写函数

🔥fgetc

🔥fputc

🔥fgets

🔥fputs

   • 🌰2.输入输出函数

🔥fprintf

​🔥fscanf

​🔥sprintf

​🔥sscanf

🔥fwrite

🔥fread

🍋知识点四:文件的随机读写

 • 🌰1.fseek

​ • 🌰2.ftell

​ • 🌰3.rewind

🍋知识点五:文件读取结束的判定

 • 🌰1.被错误使用的feof

 ​• 🌰2.拷贝文件

🍋知识点六:文件缓冲区

 ​• 🌰1.fflush

​ ​• 🌰2.缓冲区示例

• ✨SumUp结语


• ✨说在前面

亲爱的读者们大家好!💖💖💖,我们又见面了,我们紧接着要进入一个新的内容,就是文件的读与写,上一篇文章我们详细解析了C语言中的动态内存管理的相关函数,包括malloc、calloc,reallioc等,希望大家能够掌握并应用~

    

今天这篇文章给大家带来的是C语言中关于文件读与写的知识,涉及到比较抽象的如流的概念,希望大家好好学习,我们后面也可能还会用到,希望可以给大家带来帮助,带来收获,感谢大家支持!

 

  博主主页传送门:愿天垂怜的博客

🍋知识点一:什么是文件?

📌为什么要使用文件?

如果没有文件,那么我们写的程序就会全部存储在电脑的内存中,如果程序退出,内存被回收,数据就会丢失,等程序再次运行,是看不到上次剩下运行的数据的,这部分数据就找不到了。为了使使数据在内存中进行持久化的保存,我们需要使用文件。

那什么是文件呢?磁盘(硬盘)上的文件就是文件。

但是在程序设计中,我们主要谈论的文件只有两种:程序文件、数据文件,这是根据文件的功能来分类的。

 • 🌰1.程序文件

程序文件包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)。

 • 🌰2.数据文件

数据文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取的文件或者输出的文件,本章讨论的是数据文件。

在以前我们学习的内容中所处理的各种数据的输入输出都是以终端为对象的,即从终端的键盘输入数据,运行结果显示到显示屏上。其实有时候我们也需要把信息输出到磁盘上,当需要的时候再从磁盘上把数据再读取到内存中进行使用,这里就是在处理磁盘上的文件。

 • 🌰3.文件名

一个文件要有一个唯一的文件标识,以便用户识别和引用。

文件名包含了3个部分:文件路径+文件名主干+文件后缀

比如:D : \ 2023C \ text . txt

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

 • 🌰4.文本文件与二进制文件

根据数据的组织形式,可以将数据文件分为文本文件、二进制文件。

🔥如果要求在外存上以ASCII码的形式存储,则需要在存储前进行转换。以ASCII码字符的形式存储的文件就是文本文件。

🔥数据在内存中以二进制的形式存储,如果不加以转换就输出到外存的文件中,就是二进制文件,二进制文件通过文本编辑器是看不懂的。

📌一个数据在内存中是怎么存储的呢?

字符一律是以ASCII码的形式存储,数值型数据既可以用ASCII码的形式存储,也可以用二进制的形式存储。

假设有整数10000,如果以ASCII码的形式存储到磁盘,则磁盘中占用5个字节(每个字符一个字节),而二进制形式输入,则在磁盘上只占4个字节。

 📌如何在VS上查看二进制文件?

我们从上面的内容中可以知道,二进制文件通过文本编辑器是没办法看懂的。我们先看看下面的代码,细节看不懂没关系,下面是会讲的

💯代码演示:

#include <stdio.h>
int main()
{
	int a = 10000;
	FILE* pf = fopen("text.txt", "wb");
	fwrite(&a, 4, 1, pf);//二进制文件写入文件中
	fclose(pf);
	pf = NULL;

	return 0;
}

💯操作步骤:

按照上面的步骤,我们就能够在VS上查看二进制文件了。 

🍋知识点二:文件的打开与关闭

文件操作包括三个部分:打开文件、读/写文件、关闭文件

  • 🌰1.流与标准流

🔥流的概念

我们程序的数据需要输出到各种的外部设备,也需要从外部设备中获取数据,而不同的外部设备其输入输出的操作各不相同,如果都需要掌握所有外部设备的输入输出,那对程序员的要求也太高了,如下图

为了方便程序员对各种设备进行方便的操作,我们抽象出了流的概念,我们可以把流想象成流淌着字符的河。C程序针对文件、画面、键盘灯数据的输入输出操作都是通过流操作的,一般情况下,我们要想向流里写入数据,或者从流中读取数据,都是要打开流,然后操作。

 🔥标准流

那为什么我们从缉拿盘上输入数据,或向屏幕上输出数据,并没有打开流呢?

那是因为C语言程序在启动的时候,默认已经打开了三个流:

🎉stdin 标准输入流,在大多数的环境中从键盘输入,scanf函数就是从标准输入流中读取数据。

🎉stdout 标准输出流,大多数的环境中输出直显示器界面,printf函数就是将信息输出到标准输出流。

🎉stderr 标准错误流,大多数环境输出到显示器界面。

就是因为默认打开了三个流,我们使用scanf、printf等函数就可以直接进行输入和输出的操作。

stdin、stdout、stderr 三个流的类型是:FILE*,通常称为文件指针

C语言中,就是通过 FILE* 的文件指针来维护流的各种操作。

  • 🌰2.文件指针

缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”

每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(入文件名字,文件状态以及文件当前的位置等)。这些信息是保存在一个结构体变量中的,该结构体由系统声明的,取名为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 结构的变量,这样使用起来更加方便。

简单粗暴的理解为:指向文件的指针。

下面我们可以创建一个 FILE* 的指针变量:

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

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

  • 🌰3.文件的打开与关闭

文件在读写之前应该先打开文件,在使用结束之后应该关闭文件

在编写程序的时候,在打开文件的同时,都会返回一个 FILE* 的指针变量指向该文件,也相当于建立了指针和文件的关系。

🔥fopen和fclose

ANSIC 规定使用 fopen 函数来打开文件, fclose 函数来关闭文件。

//打开文件
FILE* fopen(const char* filename, const char* mode);

//关闭文件
int fclose(FILE* stream);

mode表示文件的打开模式,下面都是文件的打开模式:

文件使用方式含义如果指定文件不存在
"r"(只读)为了输入数据,打开一个已经存在的文本文件出错
"w"(只写)为了输出数据,打开一个文本文件建立一个新的文件
"a"(追加)向文本文件尾添加数据建立一个新的文件
"rb"(只读)为了输入数据,打开一个二进制文件出错
"wb"(只写)为了输出数据,打开一个二进制文件建立一个新的文件
"ab"(追加)向一个二进制文件尾添加数据建立一个新的文件
"r+"(读写)为了读和写,打开一个文本文件出错
"w+"(读写)为了读和写,建立一个新的文件建立一个新的文件
"a+"(读写)打开一个文件,在文件尾进行读写建立一个新的文件
"rb+"(读写)为了读和写,打开一个二进制文件出错
"wb+"(读写)为了读和写,新建一个二进制文件建立一个新的文件
"ab+"(读写)打开一个二进制文件,在文件尾进行读和写建立一个新的文件

注意:"w"(只写)和"a"(追加)的区别是如果文件存在且有内容,只写操作会清空内容。

💯代码演示:

#include <stdio.h>
int main()
{
	FILE* pf = fopen("text.txt", "w");//打开文件
	if (pf == NULL)//打开失败
	{
		perror("fopen");
		return 1;
	}
	//写文件
	//...

	//关闭文件
	fclose(pf);
	pf = NULL;

	return 0;
}

🍋知识点三:文件的顺序读写

   • 🌰1.顺序的读写函数

函数名功能适用于
fgetc字符输入函数所有输入流
fputc字符输出函数所有输出流
fgets文本行输入函数所有输入流

fputs

文本行输出函数所有输出流
fscanf格式化输入函数所有输入流
fprintf格式化输出函数所有输出流
fread二进制输入文件输入流
fwrite二进制输出文件输出流

上面说的适用于所有输入流一般指使用与标准输入流和其他输入流(如文件输入流);所有输出流一般指标准输出流和其他输出流(如文件输出流)。

在进行文件操作的时候,我们需要向文件中写入字符,从文件中读取字符,这个时候就可以用fgetc和fputc来完成任务。

🔥fgetc

函数原型:

int fgetc ( FILE* stream );

功能:函数用于从指定文件流中读取一个字符,并将其返回为一个无符号字符。

参数:

stream:指向 FILE 类型结构体的指针,指定了要读取字符的文件流。

返回值:

返回读取的字符,返回的是字符的ASCII码值,如果到达文件末尾或者发生错误,则返回EOF,即(-1)。

使用注意事项:

• 需要包含 stdio.h 头文件。

• 在使用fgetc()之前,需要确保文件已经以可读的方式打开。

• 返回的字符是以 unsigned char 形式返回,但是它会被转换为 int 类型以支持特殊值EOF。

• 文件指针会随着每次读取的调用而向前移动。

fgetc示例:

我们假设现在text.txt文档中存在 abcd...xyz 一共26个字母

#include <stdio.h>
int main()
{
	FILE* pf = fopen("text.txt", "r");//打开文件
	if (pf == NULL)//打开失败
	{
		perror("fopen");
		return 1;
	}
	//读文件
	int ch = 0;
	while ((ch = fgetc(pf)) != EOF)
	{
		printf("%c", ch);
	}

	//关闭文件
	fclose(pf);
	pf = NULL;

	return 0;
}

输出结果:

🔥fputc

函数原型:

int fputc ( int character, FILE * stream );

功能:函数用于向指定的文件流中写入一个字符。

参数:

• c:要写入的字符,以整数形式给出。

• stream:指向 FILE 类型结构体的指针,指定了写入字符的文件流。

返回值:

如果成功写入字符,则返回写入的字符,以无符号字符表示。如果发生错误,返回EOF,即(-1)。

使用注意事项:

• 需要包含 stdio.h 头文件。

• 在使用fputc()之前,需要确保文件已经以可写的方式打开。

• 文件指针会随着每次读取的调用而向前移动。

fputc示例:

#include <stdio.h>
int main()
{
	FILE* pf = fopen("text.txt", "w");//打开文件
	if (pf == NULL)//打开失败
	{
		perror("fopen");
		return 1;
	}
	//写文件
	char ch = 0;
	for (ch = 'a'; ch <= 'z'; ch++)
	{
		fputc(ch, pf);
	}

	//关闭文件
	fclose(pf);
	pf = NULL;

	return 0;
}

 文件text.txt中:

 

 📌为什么fputc和fgetc函数的返回值是int型?

例如fputc:

int fputc ( int character, FILE * stream );

Writes a character to the stream and advances the position indicator.

Return Value:

On success, the character written is returned.
If a writing error occurs, EOF is returned and the error indicator (ferror) is set.

也就是说,如果成功,fputc会返回所写的字符,既可以直接返回 char 型的字符,也可以返回字符的ASCII码值,这时返回值就是 int 型;而如果失败,将会返回EOF,而EOF的本质是-1,所以返回值为 int 是为了兼容EOF的情况。

在进行文件操作的时候,我们需要向文件中写入一行字符,或从文件中读取一行字符,这时就可以使用fgets和fputs来完成任务。 

🔥fgets

函数原型:

char* fgets ( char* str, int num, FILE* stream );

功能:函数用于从指定的文件流中读取一行文本,并将其存储为一个字符串。

参数:

• str:指向一个字符数组的指针,用于存储读取的文本内容。该数组必须足够大以容纳最大长度为 n-1 的字符串,因为函数会在末尾添加一个 \0 终止符。

• n:要读取的最大字符数,包括末尾的 \0 终止符,如果读取的行超过 n-1 个字符,则剩余的字符会被截断。

• stream:指向 FILE 类型结构体的指针,指向了要读取的文件流。

返回值:
char*:如果成功读取到一行文本,则返回 str 参数的值。如果到达文件末尾或者发生错误,则返回NULL 。

使用注意事项:
• 需要包含 stdio.h 头文件。

• 在使用fgets()之前,需要确保文件已经以可读方式打开。

• 函数会将包括换行符在内的整行文本读取到 str 指向的缓冲区中,但会在末尾添加 \0 终止符。

• 如果一行文本的字符数不超过 n-1 ,则整行文本以及末尾的换行符都会被读取并存储。

• 如果一行文本的字符数超过 n-1,则前 n-1 个字符会被读取并存储,剩余字符会被截断。

• 如果文件中没有更多行可读,则fgetc()返回 NULL 。

fgets示例:

假设text文档中写有:

#include <stdio.h>
int main()
{
	FILE* pf = fopen("text.txt", "r");//打开文件
	if (pf == NULL)//打开失败
	{
		perror("fopen");
		return 1;
	}
	//读文件
	char arr[20] = { 0 };
	fgets(arr, 10, pf);//只读取9个字符
	puts(arr);

	//关闭文件
	fclose(pf);
	pf = NULL;

	return 0;
}

输出结果: 

如果一行文本的字符数超过 n-1,则前 n-1 个字符会被读取并存储,剩余字符会被截断,也就是说就打印一行,且包含换行符。所以如果你想将每行都打印出,可以使用while循环:

#include <stdio.h>
int main()
{
	FILE* pf = fopen("text.txt", "r");//打开文件
	if (pf == NULL)//打开失败
	{
		perror("fopen");
		return 1;
	}
	//读文件
	char arr[20] = { 0 };
	while (fgets(arr, 20, pf) != NULL)
	{
		printf("%s", arr);
	}

	//关闭文件
	fclose(pf);
	pf = NULL;

	return 0;
}

输出结果:

🔥fputs

函数原型:

int fputs ( const char * str, FILE * stream );

功能:函数用于向指定的文件流中写入一行文本。

参数:

• str:包含要写入流的内容的C字符串。

• stream:指向 FILE 类型结构体的指针,指向了要写入的文件流。

返回值:
如果成功,则返回一个非负值,如果出现错误,该函数返回EOF。

fputs示例:

#include <stdio.h>
int main()
{
	FILE* pf = fopen("text.txt", "w");//打开文件
	if (pf == NULL)//打开失败
	{
		perror("fopen");
		return 1;
	}
	//写文件
	int ch = 0;
	fputs("To C or not to C, ", pf);
	fputs("it is a question.\n", pf);

	//关闭文件
	fclose(pf);
	pf = NULL;

	return 0;
}

  文件text.txt中:

   • 🌰2.输入输出函数

🔥fprintf

函数原型:

int fprintf ( FILE* stream, const char* format, ... );

功能:函数用于向指定文件流中写入格式化数据。

参数:

• stream:指向 FILE 类型结构体的指针,指向了要写入格式化数据的文件流。

• format:C字符串它可以包含格式说明符,这些格式说明符由后续附加参数中指定的值替换,并按要求进行格式化。

返回值:

如果成功,则返回写入的字符总数,如果出现写的错误,则返回一个负数。

fprintf示例:

希望将Crayon的数据存放在文件中

#include <stdio.h>
struct S
{
	char name[20];
	int age;
	float score;
};
int main()
{
	struct S s = { "Crayon",26,95.5f };
	//想把s中的数据存放在文件中
	FILE* pf = fopen("text.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//写文件 - 是以文本的形式写进去的
	fprintf(pf, "%s %d %f", s.name, s.age, s.score);

	//关闭文件 
	fclose(pf);
	pf = NULL;

	return 0;
}

 文件text.txt中:

其实和printf是非常相似的,只是多了一个参数文件指针而已。

🔥fscanf

函数原型:

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

功能:函数用于从指定的文件流中读取格式化数据。

参数:

• stream:指向 FILE 类型结构体的指针,指向了要读取格式化数据的文件流。

• format:C字符串,包含一个字符序列,控制如何处理从流中提取的字符。

返回值:

如果成功,该函数返回参数列表中成功填充的项数,如果在读取时发生读取错误或到达文件末尾,则返回EOF。

fscanf示例:

希望将text文档中 Crystal 的信息读入结构体变量s中

假设text文档中写有:

#include <stdio.h>
struct S
{
	char name[20];
	int age;
	float score;
};

int main()
{
	struct S s = { 0 };
	//想从文件text.txt中读取数据存放在s中
	FILE* pf = fopen("text.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读文件 
	fscanf(pf, "%s %d %f", s.name, &s.age, &s.score);

	//打印在屏幕上看看
	printf("%s %d %f", s.name, s.age, s.score);

	//关闭文件 
	fclose(pf);
	pf = NULL;

	return 0;
}

输出结果:

和格式化输出函数(printf、fprintf)一样,fscanf和scanf是非常相似的,只是多了一个参数文件指针。

不过大家还能发现, printf 、 scanf 和 fprintf 、 fscanf 除了参数上的区别,在输入流和输出流上也存在区别(3.1表格)。前者是标准输入输出流,而后者是所有输入输出流,也就是说后者不仅可以从文件流中输入或输出,也可以从标准输入输出流中输入输出。

那我们是否可以用 fprintf 实现 printf 呢?答案是,可以!

我们将上面代码中的 printf 用 fprintf 替代:

只需要将fpirntf的第一个参数改成标准输出流就可以了,表示将其输出到标准输出流(一般指屏幕)上。

我们一定要理解他们的本质,就比如如果将fputc中的第二个参数改成 stdout ,就是将字符输出到屏幕上, fputs 同理。

#include <stdio.h>
int main()
{
	fputc('a', stdout);
	fputs("\nabc", stdout);

	return 0;
}
🔥sprintf

函数原型:

int sprintf ( char* str, const char* format, ... );

功能:函数用于将格式化的数据写入字符串。

参数:
• str:指向存储结果字符串的缓冲区的指针,缓冲区应该足够大以容纳结果字符串。

• format:包含格式字符串的C字符串,该格式字符串遵循与printf中的格式相同的规范。

返回值:

如果成功,则返回写入的字符总数。此计数不包括自动附加在字符串末尾的额外空字符,如果失败,则返回一个负数。

sprintf示例:

希望将 Crayon 的信息输出到 buf 中

#include <stdio.h>
struct S
{
	char name[20];
	int age;
	float score;
};
int main()
{
	char buf[200];
	struct S s = { "Crayon",26,95.5f };
	sprintf(buf, "%s %d %f", s.name, s.age, s.score);
	printf("%s\n", buf);

	return 0;
}

输出结果:

sprintf 和 printf 对比:

🔥sscanf

函数原型:

int sscanf ( const char* s, const char* format, ...);

功能:函数用于从字符串中读取格式化数据

 参数:

• s:C字符串,该函数将其处理为检索数据的源。

• format:包含格式字符串的C字符串,该格式字符串遵循与scanf中的格式相同的规范。

返回值:

如果成功,该函数返回参数列表中成功填充的项数,如果输入失败,则返回EOF。

sscanf示例:

#include <stdio.h>
struct S
{
	char name[20];
	int age;
	float score;
};
int main()
{
	char buf[200];
	struct S s = { "Crayon",26,95.5f };
	sprintf(buf, "%s %d %f", s.name, s.age, s.score);
	printf("%s\n", buf);//现在buf中存有s的数据

	struct S t = { 0 };
	sscanf(buf, "%s %d %f", t.name, &t.age, &t.score);//现在t中有buf的数据
	printf("%s %d %f\n", t.name, t.age, t.score);

	return 0;
}

输出结果:

sscanf 和 scanf 对比:

那现在我们可以对比下面两组函数:
 

scanffscanfsscanf
printffprintfsprintf

• scanf — 从标准输入流上读取格式化数据

• fscanf — 从指定输入流上读取格式化数据

• sscanf —从字符串中读取格式化数据

• printf — 把数据以格式化的形式打印在标准输出流

• fprintf — 把数据以格式化的形式打印在指定输出流

• sprintf — 把数据以格式化的形式转换成字符串

🔥fwrite

函数原型:

size_t fwrite ( const void* ptr, size_t size, size_t count, FILE* stream );

功能:函数用于将数据块写入文件流

参数:

• ptr:指向要写入的元素数组的指针,转换为const void*类型。

• size:要写入的每个元素的字节大小。

• count:元素的数目,每个元素的大小为size字节。

• stream:指向 FILE 类型结构体的指针,指向了要写入的文件流。

返回值:

返回成功写入的元素总数,如果size或count为零,则函数返回零。

fwrite示例:

希望将1 2 3 4 5以二进制形式写入文件

#include <stdio.h>
int main()
{
	int arr[] = { 1,2,3,4,5 };
	FILE* pf = fopen("text.txt", "wb");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//写数据
	int sz = sizeof(arr) / sizeof(arr[0]);
	fwrite(arr, sizeof(arr[0]), sz, pf);//以二进制的形式写进去的

	//关闭文件
	fclose(pf);
	pf = NULL;

	return 0;
}

  文件text.txt中:

🔥fread

函数原型:

size_t fread ( void* ptr, size_t size, size_t count, FILE* stream );

功能:函数用于从文件流中读取数据块

参数:

• ptr:指向大小至少为(size*count)字节的内存块的指针,转换为void*。

• size:要读取的每个元素的大小(以字节为单位)。

• count:元素的数目,每个元素的大小为size字节。

• stream:指向 FILE 类型结构体的指针,指向了要读取的文件流。

返回值:

返回成功读取的元素总数,如果size或count为零,则函数返回零,流状态和ptr所指向的内容保持不变。

fread示例

假设text文档中有内容如下

希望将其读取到数组arr中 

#include <stdio.h>
int main()
{
	int arr[5] = { 0 };
	FILE* pf = fopen("text.txt", "rb");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读数据
	fread(arr, sizeof(arr[0]), 5, pf);//以二进制的形式读取
	for (int i = 0; i < 5; i++)
	{
		printf("%d ", arr[i]);//1 2 3 4 5
	}

	//关闭文件
	fclose(pf);
	pf = NULL;

	return 0;
}

输出结果:

如果不知道具体的元素个数,我们可以用while循环:

#include <stdio.h>
int main()
{
	int arr[5] = { 0 };
	FILE* pf = fopen("text.txt", "rb");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读数据 - 读一个打印一个
	int i = 0;
	while (fread(&arr[i],sizeof(int),1,pf))
	{
		printf("%d ", arr[i]);
		i++;
	}

	//关闭文件
	fclose(pf);
	pf = NULL;

	return 0;
}

🍋知识点四:文件的随机读写

上面的函数实现的是文件的顺序读写,也就是按照顺序进行读写。我们也可以不按照顺序,指哪打哪,称作文件的随机读写。

 • 🌰1.fseek

根据文件指针的位置和偏移量来定位文件指针(文件内容的光标)。

int fseek ( FILE * stream, long int offset, int origin );

其中对于第三个参数起始位置(origin),我们有三种选择的位置:分别为SEEK_SET(文件的起始位置)、SEEK_CUR(文件指针的当前位置)、SEEK_END(文件的末尾)。

 

举例:

#include <stdio.h>
int main()
{
	FILE* pf = fopen("text.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	int ch = fgetc(pf);
    printf("%c\n", ch);//文件指针向前移动一位
	//读文件
	fseek(pf, 5, SEEK_CUR);//相对文件指针当前的位置偏移量为5的位置

	ch = fgetc(pf);
	printf("%c\n", ch);//g

	//关闭文件
	fclose(pf);
	pf = NULL;

	return 0;
}

大家可以判断一下下面文件指针的位置(假文件指针指向b,每个fseek相互独立)

fseek(pf, 5, SEEK_SET);
ch = fgetc(pf);
printf("%c\n", ch);//g

fseek(pf, -3, SEEK_END);
ch = fgetc(pf);
printf("%c\n", ch);//g

fseek(pf, 2, SEEK_CUR);
ch = fgetc(pf);
printf("%c\n", ch);//d

 • 🌰2.ftell

返回文件指针相对于起始位置的偏移量(offset)。

long int ftell ( FILE * stream );

举例:

#include <stdio.h>
int main()
{
	FILE* pf = fopen("text.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	int ch = fgetc(pf);//文件指针向前移动一位
	fseek(pf, 5, SEEK_CUR);//相对文件指针当前的位置偏移量为5的位置
	printf("%d\n", ftell(pf));//6
	//关闭文件
	fclose(pf);
	pf = NULL;

	return 0;
}

输出结果:

注意:是相对于文件起始位置的偏移量。

 • 🌰3.rewind

让文件指针的位置指向文件的起始位置。

void rewind ( FILE * stream );

rewind示例:

/* rewind example */
#include <stdio.h>
int main()
{
    FILE* pf = fopen("text.txt", "r");
    if (pf == NULL)
    {
        perror("fopen");
        return 1;
    }
    //读文件
    int ch = fgetc(pf);
    printf("%c\n", ch);//a

    fseek(pf, -3, SEEK_END);
    ch = fgetc(pf);
    printf("%c\n", ch);//g

    rewind(pf);
    ch = fgetc(pf);
    printf("%c\n", ch);//a

    return 0;
}

🍋知识点五:文件读取结束的判定

 • 🌰1.被错误使用的feof

函数原型:

int feof ( FILE * stream );

牢记:在文件读取过程中,不能用feof函数的返回值直接来判断文件是否结束。

feof 的作用是:当文化读取结束时,判断读取结束的原因是否是:遇到文件尾结束。

大家一定要分清楚:

文件读取结束原因判定函数
有可能遇到文件末尾feof
读取的时候发生了错误ferror

在打开一个流的时候,这个流上有两个标记值:一个标记值是是否遇到文件末尾、另一个是是否发生错误。因此,feof是检测第一个标记的,而ferror是检测第二个标记的。

🔥文本文件读取是否结束,判断返回值是否为EOF(fgetc),或者 NULL (fgets),例如:

• fgetc 判断是否为 EOF 。

• fgets 判断返回值是否为 NULL 。

🔥二进制文件的读取结束判断,判断返回值知否小于实际要读的个数。例如:

•  fread 判断返回值是否小于实际要读的个数。


文件结束示例:

假设text文档中有

#include <stdio.h>
#include <errno.h>
#include <string.h>
int main()
{
    FILE* pf = fopen("text.txt", "r");
    if (pf == NULL)
    {
        perror("fopen");
        return 1;
    }
    //读文件
    int ch = 0;
    while ((ch = fgetc(pf)) != EOF)//fgetc - 读取结束返回EOF
    {
        printf("%c\n", ch);
    }
    //判断是什么原因导致读取结束
    if (feof(pf))
    {
        printf("遇到文件末尾,读取正常结束\n");
    }
    else if (ferror(pf))
    {
        printf("%s\n", strerror(errno));
    }

    return 0;
}

输出结果:

 • 🌰2.拷贝文件

使用所学文件操作,在当前目录下放一个文件data.txt,写一个程序,将data.txt文件拷贝一份,生成data_copy.txt文件。

基本思路:

  1. 打开文件data.txt,读取数据
  2. 打开文件data_copy.txt,写数据
  3. 从data.txt中读取数据存放到data_copy.txt文件中,直到文件结束。
  4. 关闭两个文件

💯分析:

我们先创建data.txt和data.copy.txt文件

 可以将其添加到VS中以便观看

假设data.txt中有以下内容

写代码:

#include <stdio.h>
int main()
{
    FILE* pfin =  fopen("data.txt", "r");
    if (pfin == NULL)
    {
        perror("fopen:data.txt");
        return 1;
    }
    FILE* pfout = fopen("data_copy.txt", "w");
    if (pfout == NULL)
    {
        perror("fopen:data_copy.txt");
        return 1;
    }
    //读文件和写文件
    int ch = 0;
    while ((ch = fgetc(pfin)) != EOF)
    {
        fputc(ch, pfout);
    }
    //关闭文件
    fclose(pfin);
    pfin = NULL;
    fclose(pfout);
    pfout = NULL;

    return 0;
}

文件data_copy.txt中:

🍋知识点六:文件缓冲区

 ANSIC 标准采用"缓冲文件系统"处理数据文件,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块"文件缓冲区"。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。如果磁盘向计算机读取数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小是根据C编译系统决定的。

• 🌰1.fflush

函数原型:

int fflush ( FILE * stream );

功能:将其输出缓冲区中的任何未写入数据写入文件。

参数:

指向 FILE 类型结构体的指针,指向了指定的缓冲流。

返回值:

如果成功,则返回0,如果发生错误,则返回EOF。

注意:当文件关闭时,无论是由于调用fclose还是由于程序终止,与之相关的所有缓冲区都会自动刷新。

• 🌰2.缓冲区示例

#include <stdio.h>
#include <windows.h>
//VS2019 WIN11环境测试
int main()
{
	FILE * pf = fopen("data.txt", "w");
	fputs("abcdef", pf);//先将代码放在输出缓冲区
	printf("睡眠10秒-已经写数据了,打开data.txt文件,发现文件没有内容\n");
	Sleep(10000);
	printf("刷新缓冲区\n");
	fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到文件(磁盘)
	//注:fflush 在高版本的VS上不能使用了
	printf("再睡眠10秒-此时,再次打开data.txt文件,文件有内容了\n");
	Sleep(10000);
	fclose(pf);
	//注:fclose在关闭文件的时候,也会刷新缓冲区
	pf = NULL;

	return 0;
}

 

• ✨SumUp结语

文件操作部分的内容到这里就差不多了,本文十分详细,应该够大家用了,希望大家认真学习,能有收获。

如果大家觉得有帮助,麻烦大家点点赞,如果有错误的地方也欢迎大家指出~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/588246.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【项目学习01_2024.05.01_Day03】

学习笔记 3.6 开发业务层3.6.1 创建数据字典表3.6.2 编写Service3.6.3 测试Service 3.7 接口测试3.7.1 接口完善3.7.2 Httpclient测试 3.8 前后端联调3.8.1 准备环境3.8.2 安装系统管理服务3.8.3 解决跨域问题解决跨域的方法&#xff1a;我们准备使用方案2解决跨域问题。在内容…

模方试用版水面修整,调整水岸线功能进程缓慢该怎么解决?

答&#xff1a;水面修整&#xff0c;第一个点选取准确的高程位置和水边&#xff0c;其他点就可以包含整个水面范围就行&#xff0c;可以绘制大一些。上图绘制区域没有包含到所有的水面&#xff0c;可以尝试下图的红线绘制区域。 模方是一款针对实景三维模型的冗余碎片、水面残缺…

使用Neo4j和Langchain创建知识图谱

使用Neo4j和Langchain创建知识图谱 知识图谱是组织和整合信息的强大工具。通过使用实体作为节点和关系作为边缘&#xff0c;它们提供了一种系统的知识表示方法。这种有条理的表示有利于简化查询、分析和推理&#xff0c;使知识图在搜索引擎、推荐系统、自然语言处理和人工智能…

Docker:centos7安装docker

官网&#xff1a;https://www.docker.com/官网 文档地址 - 确认centos7及其以上的版本 查看当前系统版本 cat /etc/redhat-release- 卸载旧版本 依照官网执行 - yum安装gcc相关 yum -y install gccyum -y install gcc-c- 安装需要的软件包 yum install -y yum-utils- 设置s…

Java 基础重点知识-(泛型、反射、注解、IO)

文章目录 什么是泛型? 泛型有什么用?泛型原理是什么? Java 反射什么是反射? 反射作用是什么?动态代理有几种实现方式? 有什么特点? Java 注解什么是注解, 作用是什么? Java I/O什么是序列化?Java 是怎么实现系列化的?常见的序列化协议有哪些?BIO/NIO/AIO 有什么区别…

可靠的Mac照片恢复解决方案

当您在搜索引擎搜索中输入“Mac照片恢复”时&#xff0c;您将获得数以万计的结果。有很多Mac照片恢复解决方案声称他们可以在Mac OS下恢复丢失的照片。但是&#xff0c;并非互联网上的所有Mac照片恢复解决方案都可以解决您的照片丢失问题。而且您不应该花太多时间寻找可靠的Mac…

数据库(MySQL)—— DQL语句(聚合,分组,排序,分页)

数据库&#xff08;MySQL&#xff09;—— DQL语句&#xff08;聚合&#xff0c;分组&#xff0c;排序&#xff0c;分页&#xff09; 聚合函数常见的聚合函数语法 分组查询语法 排序查询语法 分页查询语法 DQL的执行顺序 我们今天来继续学习MySQL的DQL语句的聚合和分组查询&…

PyCharm 2024新版图文安装教程(python环境搭建+PyCharm安装+运行测试+汉化+背景图设置)

名人说&#xff1a;一点浩然气&#xff0c;千里快哉风。—— 苏轼《水调歌头》 创作者&#xff1a;Code_流苏(CSDN) 目录 一、Python环境搭建二、PyCharm下载及安装三、解释器配置及项目测试四、PyCharm汉化五、背景图设置 很高兴你打开了这篇博客&#xff0c;如有疑问&#x…

Django后台项目开发实战七

为后台管理系统换风格 第七阶段 安装皮肤包 pip install django-grappelli 在 setting.py 注册 INSTALLED_APPS [grappelli,django.contrib.admin,django.contrib.auth,django.contrib.contenttypes,django.contrib.sessions,django.contrib.messages,django.contrib.stat…

LLM应用:工作流workflow创建自定义模版使用

参考: https://www.coze.cn/ 本案例是在coze平台上操作的,也有其他工具支持工作流的创建例如dify;也例如图像生成的comfyui工作流工具 创建自定义模版 可以根据自己需求创建自己的工作流工具;本文案例是创建一个联网搜索的LLM应用: 创建工作流页面: https://www.coze.c…

RTMP 直播推流 Demo(二)—— 音频推流与视频推流

音视频编解码系列目录&#xff1a; Android 音视频基础知识 Android 音视频播放器 Demo&#xff08;一&#xff09;—— 视频解码与渲染 Android 音视频播放器 Demo&#xff08;二&#xff09;—— 音频解码与音视频同步 RTMP 直播推流 Demo&#xff08;一&#xff09;—— 项目…

Linux开发板 FTP 服务器移植与搭建

VSFTPD&#xff08;Very Secure FTP Daemon&#xff09;是一个安全、稳定且快速的FTP服务器软件&#xff0c;广泛用于Unix和Linux操作系统。它以其轻量级、高效和易于配置而受到赞誉。VSFTPD不仅支持标准的FTP命令和操作&#xff0c;还提供了额外的安全特性&#xff0c;如匿名F…

【Go语言快速上手(六)】管道, 网络编程,反射,用法讲解

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:Go语言专栏⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习更多Go语言知识   &#x1f51d;&#x1f51d; GO快速上手 1. 前言2. 初识管道3. 管…

面试:Spring(IOC、AOP、事务失效、循环引用、SpringMVC、SpringBoot的自动配置原理、Spring框架常见注解)

目录 一、Spring的单例Bean是否是线程安全的&#xff1f; 二、什么是AOP 1、介绍 &#xff08;1&#xff09;记录操作日志 &#xff08;2&#xff09;实现Spring中的事务 三、spring中事务失效的场景有哪些&#xff1f; 1、异常捕获处理 2、抛出检查异常 3、非public方…

【yolov8】yolov8剪枝训练流程

yolov8剪枝训练流程 流程&#xff1a; 约束剪枝微调 一、正常训练 yolo train model./weights/yolov8s.pt datayolo_bvn.yaml epochs100 ampFalse projectprun nametrain二、约束训练 2.1 修改YOLOv8代码&#xff1a; ultralytics/yolo/engine/trainer.py 添加内容&#…

freertos入门---创建FreeRTOS工程

freertos入门—创建FreeRTOS工程 1 STM32CubeMx配置 双击运行STM32CubeMX,在首页选择“ACCESS TO MCU SELECTOR”,如下图所示&#xff1a;   在MCU选型界面&#xff0c;输入自己想要开发的芯片型号&#xff0c;如&#xff1a;STM32F103C8T6: 2 配置时钟 在“System Core”…

手机测试之-adb

一、Android Debug Bridge 1.1 Android系统主要的目录 1.2 ADB工具介绍 ADB的全称为Android Debug Bridge,就是起到调试桥的作用,是Android SDK里面一个多用途调试工具,通过它可以和Android设备或模拟器通信,借助adb工具,我们可以管理设备或手机模拟器的状态。还可以进行很多…

与Apollo共创生态:探索自动驾驶的未来蓝图

目录 引言Apollo开放平台Apollo开放平台企业生态计划Apollo X 企业自动驾驶解决方案&#xff1a;加速企业场景应用落地Apollo开放平台携手伙伴共创生态生态共创会员权益 个人心得与展望技术的多元化应用数据驱动的智能化安全与可靠性的重视 结语 引言 就在2024年4月19日&#x…

简约大气的全屏背景壁纸导航网源码(免费)

简约大气的全屏背景壁纸导航网模板 效果图部分代码领取源码下期更新预报 效果图 部分代码 <!DOCTYPE html> <html lang"zh-CN"> <!--版权归孤独 --> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible…

pyqt QSplitter控件

pyqt QSplitter控件 QSplitter控件效果代码 QSplitter控件 PyQt中的QSplitter控件是一个强大的布局管理器&#xff0c;它允许用户通过拖动边界来动态调整子控件的大小。这个控件对于创建灵活的、用户可定制的用户界面非常有用。 QSplitter控件可以水平或垂直地分割其包含的子…
最新文章