【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);//gfseek(pf, -3, SEEK_END);
ch = fgetc(pf);
printf("%c\n", ch);//gfseek(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);//afseek(pf, -3, SEEK_END);ch = fgetc(pf);printf("%c\n", ch);//grewind(pf);ch = fgetc(pf);printf("%c\n", ch);//areturn 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.mzph.cn/pingmian/5586.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【分布式通信】NPKit,NCCL的Profiling工具

NPKit介绍 NPKit (Networking Profiling Kit) is a profiling framework designed for popular collective communication libraries (CCLs), including Microsoft MSCCL, NVIDIA NCCL and AMD RCCL. It enables users to insert customized profiling events into different C…

STM32 HAL库F103系列之IIC实验

IIC总线协议 IIC总线协议介绍 IIC&#xff1a;Inter Integrated Circuit&#xff0c;集成电路总线&#xff0c;是一种同步 串行 半双工通信总线。 总线就是传输数据通道 协议就是传输数据的规则 IIC总线结构图 ① 由时钟线SCL和数据线SDA组成&#xff0c;并且都接上拉电阻…

[华为OD] C卷 5G网络 现需要在某城市进行5G网络建设,已经选取N个地点设置5G基站 200

题目 现需要在某城市进行5G网络建设&#xff0c;已经选取N个地点设置5G基站&#xff0c;编号固定为1到N,接 下来需要各个基站之间使用光纤进行连接以确保基站能互联互通&#xff0c;不同基站之间架设光纤的成 本各不相同&#xff0c;且有些节点之间已经存在光纤相连&#…

python项目入门新手攻略

最近工作需要接手了代码量比较大的python开发的项目&#xff0c;平时写python不多&#xff0c;记录一下如何熟悉项目。 分析调用流程-pycallgraph 因为代码量比较大&#xff0c;所以希望通过工具生成代码调用流程&#xff0c;因此用到了pycallgraph。 pycallgraph&#xff0…

【AIGC调研系列】InternVL开源多模态模型与GPT-4V的性能对比

InternVL和GPT-4V都是多模态模型&#xff0c;但它们在性能、参数量以及应用领域上有所不同。 InternVL是一个开源的多模态模型&#xff0c;其参数量为60亿&#xff0c;覆盖了图像/视频分类、检索等关键任务&#xff0c;并在32个视觉-语言基准测试中展现了卓越性能[2]。InternV…

linux操作系统,进入救援模式的方法

准备好操作系统的 ISO 文件 或 刻录好的U盘启动盘 登录服务器 BMC 管理界面&#xff0c;选择镜像之后&#xff0c;点击 “启动媒体”&#xff08;如果使用U盘启动盘 则跳过这一步骤&#xff0c;直接看下一步&#xff09; 重启服务器&#xff0c;开机界面一般按键盘 “Delete”…

优雅处理枚举值映射和分页

文章目录 引言I pagehelper分页1.1 pagehelper配置1.2 使用方法1.3 SQLServerException: LIMIT 附近有语法错误。II mybatis-plus2.1 多数据源配置2.2 优雅处理枚举值映射@EnumValue2.3 配置枚举扫描包2.4 常见问题引言 分页效果 优雅处理枚举值映射

PotatoPie 4.0 实验教程(34) —— FPGA实现摄像头图像二值化腐蚀效果

链接直达 https://item.taobao.com/item.htm?ftt&id776516984361 图像二值化腐蚀处理有什么作用&#xff1f; 图像二值化腐蚀处理在图像处理中起到了以下作用&#xff1a; 物体分割与提取&#xff1a;在图像二值化之后&#xff0c;通过腐蚀操作可以消除噪声、连接相邻的…

【C语言】动态内存分配

即使行动导致错误&#xff0c;却也带来了学习与成长;不行动则是停滞与萎缩。&#x1f493;&#x1f493;&#x1f493; 目录 •&#x1f319;知识回顾 &#x1f34b;知识点一&#xff1a;为什么要有动态内存分配 &#x1f34b;知识点二&#xff1a;malloc和free • &#x1…

shell脚本,删除30天以前的日志,并将日志推送到nas,但运行出现/bin/bash^M。

删除30天以前的日志 将日志推送到nas中&#xff0c;然后删除pod中的日志 pod挂载到本地 运行出现/bin/bash^M 1、删除30天以前的日志&#xff1a; #! /bin/bash# 定义源日志目录 LOG_DIR/home/log/ # 删除日志 find $LOG_DIR -type f -name "*.log" -mtime 30 -exec…

2024年五一杯高校数学建模竞赛(C题) 建模解析| 冲击地压危险预测 |小鹿学长带队指引全代码文章与思路

我是鹿鹿学长&#xff0c;就读于上海交通大学&#xff0c;截至目前已经帮200人完成了建模与思路的构建的处理了&#xff5e; 本篇文章是鹿鹿学长经过深度思考&#xff0c;独辟蹊径&#xff0c;通过滑动平均法解决冲击地压危险预测问题。实现综合建模。独创复杂系统视角&#xf…

在做题中学习(48):朴素的二分查找

. - 力扣&#xff08;LeetCode&#xff09; 解法一&#xff1a; 暴力求解 for循环中&#xff0c;从nums[0]枚举到nums[n-1]&#xff0c;依次判断&#xff0c;返回 target的值。 时间复杂度 : O(N) :因为要遍历一遍数组 解法二&#xff1a;二分查找 因为此数组为有序的…

Mybatis进阶(动态SQL)

文章目录 1.动态SQL1.基本介绍1.为什么需要动态SQL2.基本说明3.动态SQL常用标签 2.环境搭建1.新建子模块2.删除不必要的两个文件夹3.创建基本结构4.父模块的pom.xml5.jdbc.properties6.mybatis-config.xml7.MyBatisUtils.java8.MonsterMapper.java9.MonsterMapper.xml10.测试Mo…

如何将安卓手机投屏到Windows 10电脑上

诸神缄默不语-个人CSDN博文目录 我之所以要干这个事是为了用手机直播的时候在电脑上看弹幕…… 文章目录 1. 方法一&#xff1a;直接用Win10内置的投影到此电脑2. 方法二&#xff1a;用AirDroid Cast投屏到电脑上 1. 方法一&#xff1a;直接用Win10内置的投影到此电脑 在设置…

C++ 多态详解

文章目录 1. 多态的概念2. 多态的定义及实现2.1 多态的构成条件2.2 虚函数2.3 虚函数的重写2.3.1 虚函数重写的两个例外 2.4 C11 override 和 final2.5 重载、覆盖(重写)、隐藏(重定义)的对比 3. 多态的原理3.1 虚函数表3.2多态的原理 4. 单继承和多继承关系的虚函数表4.1 单继…

docker安装【zookeeper】【kafka】【provectuslabs/kafka-ui】记录

目录 1.安装zookeeper:3.9.2-jre-172.安装kafka:3.7.03.安装provectuslabs/kafka-ui &#xff08;选做&#xff09;新环境没有jdk&#xff0c;安装jdk-17.0.10备用 mkdir -p /export/{data,apps,logs,conf,downloads}cd /export/downloadscurl -OLk https://download.oracle.…

新品发布!无人机装调检修实训系统

近年&#xff0c;我国密集出台相关产业政策&#xff0c;推动低空经济从探索走向发展&#xff0c;根据新华网数据&#xff0c;2030年低空经济规模有望达2万亿。无人机专业属于跨学科的综合性专业&#xff0c;其中装调检测技术是无人机教培的重要组成部分。 天途推出无人机装调检…

Apache SeaTunnel k8s 集群模式 Zeta 引擎部署指南

SeaTunnel提供了一种运行Zeta引擎(cluster-mode)的方法&#xff0c;可以让Kubernetes在本地运行Zeta引擎&#xff0c;实现更高效的应用程序部署和管理。在本文中&#xff0c;我们将探索SeaTunnel k8s运行zeta引擎(cluster-mode模式)的更多信息&#xff0c;了解如何更好地利用Ze…

HTML:元素分类

HTML&#xff1a;元素分类 概述块级元素&#xff08;Block-level Elements&#xff09;内联元素&#xff08;Inline Elements&#xff09;替换元素&#xff08;Replaced Elements&#xff09;表单元素&#xff08;Form Elements&#xff09; 概述 HTML&#xff08;HyperText M…

Docker容器:网络模式与资源控制

目录 一、Docker 网络模式 1、Docker 网络实现原理 2、Docker 网络模式概述 2.1 Host 模式 2.2 Container 模式 2.3 None 模式 2.4 Bridge 模式 2.5 自定义网络&#xff08;user-defined network&#xff09; 3、配置 docker 网络模式 3.1 查看网络基础命令 3.1.1 查…