顺序读写

导读
大家好,很高兴又和大家见面啦!!!
在上一篇内容中,我们共同探索了C语言文件操作的核心基础:
-
流(Stream)的本质:理解了数据流动的抽象概念,将各种输入输出设备统一为数据通道
-
三大标准流:认识了程序启动时自动打开的
stdin(键盘输入)、stdout(屏幕输出)和stderr(错误输出) -
文件指针的关键作用:掌握了
FILE*指针如何作为程序与文件信息区的桥梁 -
文件开关操作:深入学习了
fopen的各种模式(r/w/a等)差异和fclose的资源释放重要性
这些基础知识为我们打开了文件操作的大门。现在,我们将迈出关键一步——学习如何对文件内容进行实际读写操作。本篇将系统讲解C语言提供的八大顺序读写函数,它们构成了文件内容处理的核心工具集:
-
字符级处理:
fgetc/fputc实现单个字符的精准读写 -
字符串操作:
fgets/fputs完成整行文本的高效处理 -
格式化读写:
fscanf/fprintf实现结构化数据的输入输出 -
二进制处理:
fread/fwrite应对图像、音频等非文本文件
这些函数如同文件操作的"瑞士军刀",每种工具都有其独特的使用场景和技巧。接下来,让我们直接进入正题,开始探索这些函数的奥秘与应用技巧。
一、顺序读写函数
在头文件 stdio.h 中,存在着一些专门对文件进行顺序读写的函数:
fgetc—— 字符输入函数,用于从所有流中获取字符fputc—— 字符输出函数,用于向所有流中写入字符fgets—— 文本输入函数,用于从所有流中获取文本(字符串)fputs—— 文本输出函数,用于向所有流中写入文本(字符串)fscanf—— 格式化输入函数,用于从所有流中获取格式化数据fprintf—— 格式化输出函数,用于向所有流中写入格式化数据fread—— 二进制输入函数,用于对二进制文件进行二进制输入fwrite—— 二进制输出函数,用于对二进制文件进行二进制输出
接下来我们还是通过 cplusplus.com 网站来逐一学习这些函数;
二、 fgetc
2.1 函数介绍

该函数的使用方式如下所示:
- 向函数中传入一个需要被读取的文件指针
stream - 函数会在该指针指向的指定流中获取一个字符
- 获取成功时,返回该字符
- 当读取字符成功后,文件中的位置指示器会指向下一个字符
- 获取失败时,返回 EOF并设置相应的指示器
- 流中的位置指示器位于文件末尾,设置流的文件末尾指示器
- 流在读取时发生错误,设置流的错误指示器
- 获取成功时,返回该字符
2.2 重要概念
在该函数的用法中,我们看到了几个新的概念:
- 位置指示器
- 文件末尾指示器
- 错误指示器
这里我们简单的介绍一下,这几个概念:
2.2.1 位置指示器
所谓的位置指示器,我们就可以理解为文件中的光标。
下面我们通过 fopen 与 fclose 这两个函数在桌面创建一个名为:data.txt 的文本文件:
void test1() {
FILE* pf = fopen("C:\\Users\\LIQIAN\\Desktop\\data.txt", "w");
if (pf == NULL) {
perror("fopen");
return;
}
fclose(pf);
pf = NULL;
}
现在我们就成功的在桌面生成了一个文本文件:

可以看到,此时的文件内部是没有任何内容的,并且在文件的首行会有一个闪烁的光标,这个光标就是文件内部的位置指示器。
2.2.2 文件末尾指示器
文件末尾指示器(feof: end-of-File indicator):指的是文件位置指示器位于文件末尾。
文件末尾指示器标志着当前的文件内容已经全部结束,后续会有详细的介绍,这里就不再展开,只做简单了解即可。
2.2.3 文件错误指示器
文件错误指示器(ferror: error indicator):指的是对文件的操作出现了错误。
文件错误指示器标志着当前对文件的操作出现了错误,后续会有详细的介绍,这里就不再展开,只做简单了解即可。
2.3 函数使用
接下来我们可以通过在 data.txt 文件中输入一些文本内容,再通过 fgetc 来读取该文件中的内容:
void test2() {
FILE* pf = fopen("C:\\Users\\LIQIAN\\Desktop\\data.txt", "r");
if (pf == NULL) {
perror("fopen");
return;
}
while (1) {
int ch = fgetc(pf);
if (ch == EOF) {
printf("ch = %d\n", ch);
break;
}
printf("ch = %c\n", ch);
}
fclose(pf);
pf = NULL;
}
现在我们需要对文件 data.txt 进行读操作,因此我们在通过 fopen 函数打开文件时,需要通过 r/r+/w+/a+ 等操作模式打开文件。这里我选择的事通过 r 模式打开文件:

在函数中我们是通过循环来依次读取文件中的内容。不难发现,每当成功读取一个字符后,下一次 fgetc 函数会获取文件中的下一个字符,直到所有的字符都被读取,最后,函数会返回 EOF 。在这次的测试结果中,我还获取了一个关键信息:
EOF的整型值为-1。
该函数的用法比较简单,这里我就不再继续赘述。
三、fputc
3.1 函数介绍

该函数的使用方法如下:
- 向函数中传入两个参数:
- 需要被写入的字符:
character - 指向需要被写入字符的文件的文件指针
stream
- 需要被写入的字符:
- 函数会向文件中将写入被传入的字符:
character- 当写入成功时,会返回被写入文件的字符
- 当写入失败时,会返回
EOF并设置错误指示器
3.2 写入模式的区别
在使用该函数时,我们需要区别两种写入模式:w/a
w:将写入的内容直接覆盖文件中的内容a:在文件中的内容末尾追加新内容
下面我们就来分别看一下这两种写入模式。为了更好的区分两种写入模式的区别,下面我们需要再创建一个文件:data_.txt,并将 data.txt 中的文本内容写入。

这里我们是通过鼠标来实现 data_.txt 的创建以及文本的写入,之后我们分别通过两种打开模式分别向两个文件中写入字符:L。测试代码如下所示:
void test3() {
FILE* pf1 = fopen("C:\\Users\\LIQIAN\\Desktop\\data.txt", "w");
if (pf1 == NULL) {
perror("pf1");
return;
}
FILE* pf2 = fopen("C:\\Users\\LIQIAN\\Desktop\\data_.txt", "a");
if (pf2 == NULL) {
perror("pf2");
return;
}
int ch1 = fputc('L', pf1);
if (ch1 == EOF) {
printf("ch1 = %d\n", ch1);
}
else {
printf("ch1 = %c\n", ch1);
}
int ch2 = fputc('L', pf2);
if (ch2 == EOF) {
printf("ch2 = %d\n", ch2);
}
else {
printf("ch2 = %c\n", ch2);
}
fclose(pf1);
pf1 = NULL;
fclose(pf2);
pf2 = NULL;
}
下面我们就来运行函数进行测试:

从测试结果中可以看到,这两种写入模式的区别:
- 通过模式
w进行写入时,写入的内容会直接覆盖文件中的原本内容 - 通过模式
a进行写入时,写入的内容会直接追加到文件中的内容末尾
3.3 函数使用
现在我们已经区分了两种写入模式,接下来我们就来尝试着将 data_.txt 中的文本内容通过 fputc 写入到 data.txt :
void test4() {
FILE* pf1 = fopen("C:\\Users\\LIQIAN\\Desktop\\data.txt", "a");
if (pf1 == NULL) {
perror("pf1");
return;
}
FILE* pf2 = fopen("C:\\Users\\LIQIAN\\Desktop\\data_.txt", "r");
if (pf2 == NULL) {
perror("pf2");
return;
}
int ch = fgetc(pf2);
while (ch != EOF) {
ch = fputc(ch, pf1);
if (ch == EOF) {
perror("fputc");
break;
}
ch = fgetc(pf2);
}
fclose(pf1);
pf1 = NULL;
fclose(pf2);
pf2 = NULL;
}
这次测试中,我们只需要将 data_.txt 中的文本内容写入到 data.txt 中,因此我们有两种方式实现:
- 直接追加:通过
a模式打开文件data.txt,将data_.txt中的内容追加到文件中 - 先覆盖再追加:先通过
w模式打开data.txt,完成首字符的覆盖,再通过a模式将剩余内容追加到文件中
这里我们采取的是第一种方式,下面我们就来测试一下:

可以看到通过循环,以及 fgetc 与 fputc 的相互配合,我们成功的将 data_.txt 中的文本内容复制到了 data.txt 中。
四、fgets
4.1 函数介绍

该函数的用法为:
- 向函数传入三个参数:
str:指向存储被读取的字符的字符数组指针num:被拷贝到str的字符的最大个数stream:指向一个识别输入流的FILE对象指针
- 函数会从
stream中识别num - 1个字符并存储到str中,并自动在末尾添加一个终止符(\0):- 识别成功时,返回
str - 识别失败或遇到文件末尾时,返回一个空指针:
- 在识别
- 在还未读取任一字符时遇到文件末尾,会设置一个文件末尾指示器,
str中的内容不改变 - 识别错误时,会设置一个错误指示器,
str中的内容可能已经发生改变
- 识别成功时,返回
4.2 函数使用
下面我们就尝试着通过 fgets 函数将 data.txt 中的文本内容写入一个字符数组 str 中:
void test5() {
FILE* pf1 = fopen("C:\\Users\\LIQIAN\\Desktop\\data.txt", "r");
if (pf1 == NULL) {
perror("pf1");
return;
}
char str[20] = "xxxxxxxxxxxxxxxxxx";
printf("str = %s\n", str);
char* pstr = fgets(str, 8, pf1);
printf("str = %s\n", str);
printf("pstr = %s\n", pstr);
fclose(pf1);
pf1 = NULL;
}
因为咱们只需要对文件中的内容进行读操作,所以我们在打开文件时使用 r 模式即可:

我们给函数中传入的 num = 8 ,但是实际函数运行的过程中,只读取了文件中的7个字符,之后在末尾自动添加了一个 \0 ,总共是8个字符。因此函数在实际使用的过程中,函数只会在文件中获取 num - 1 个字符。
从函数的返回值可以看到,此时因为函数读取成功,因此不仅改变了 str 中的内容,并且还将 str 作为返回值直接返回。
五、fputs
5.1 函数介绍

该函数的使用方法如下:
- 向函数中传入两个参数:
str:存储着写入流的内容的字符数组stream:指向流的文件指针
- 函数会将
str中的字符内容写入stream指向的文件中- 写入成功时,返回一个非负值
- 写入失败时,返回
EOF,并设置一个错误指示器
该函数在使用时需要注意,函数不会将
str末尾的\0写入到文件中
5.2 函数使用
下面我们就尝试着通过函数将文本内容 Hello world!!! 写入到 data.txt 中,这里有两种写入方式:
- 追加写入:通过
a模式完成写入 - 覆盖写入:通过
w模式完成写入
这里我们选择覆盖写入的方式实现:
void test6() {
FILE* pf1 = fopen("C:\\Users\\LIQIAN\\Desktop\\data.txt", "w");
if (pf1 == NULL) {
perror("pf1");
return;
}
char* str = "Hello\tWorld!!!\na\0b\nc";
printf("str = %s\n", str);
int ret = fputs(str, pf1);
printf("ret = %d\n", ret);
fclose(pf1);
pf1 = NULL;
}
这次测试中,我们不仅测试了写入文本内容,还测试了识别 \t、\n、\0 三个空白字符:

可以看到,函数在执行的过程中,会正常识别 \t 与 \n 并将其写入到文本文件中,但是在遇到 \0 时,并不会将其进行写入,而是直接停止执行。
从程序的运行终端中我们不难发现,fputs 函数的执行过程与 printf 函数对字符串的识别过程是一致的,他们之间的区别为:
fputs函数是将识别的内容写入到文本文件中printf函数是将识别的内容以文本的形式输出到终端中
六、fwrite
6.1 函数介绍

函数的用法如下所示:
- 该函数是用来对二进制文件进行写操作的函数
- 向函数中传入4个参数
ptr:指向大小至少为size * count的内存块指针,该指针被转换成void*size:被写入的每个元素的大小,单位为字节count:被写入的元素个数,每个元素的大小为size字节stream:指向识别输入流的FILE对象指针
- 函数会从
ptr中获取count个大小为size的元素写入stream中- 函数会返回写入成功的元素总数量
- 当这个总数与
count不相等时,写入错误阻止函数继续执行,这时会设置错误指示器 - 当
size或者count二者中的其中之一为0时,函数返回0,并且错误指示器保持不变
6.2 函数使用
由于该函数是用于对二进制文件进程操作的函数,因此以下几点我们需要注意:
- 函数能够操作的文件后缀为:
.bin - 通过
fopen打开的文件的模式需要加一个后缀b,如rb/wb/ab/……
接下来我们就通过 fwrite 创建一个文件 data.bin 并在文件中写入 {0, 1, 2, 3, 4, 5} :
void test7() {
FILE* pf1 = fopen("C:\\Users\\LIQIAN\\Desktop\\data.bin", "wb");
if (pf1 == NULL) {
perror("pf1");
return;
}
int arr[6] = { 0, 1, 2, 3, 4, 5 };
int ret = fwrite(arr, sizeof(int), 6, pf1);
printf("ret = %d\n", ret);
for (int i = 0; i < 6; i++) {
printf("arr[%d] = %d\n", i, arr[i]);
}
fclose(pf1);
pf1 = NULL;
}
这里我们通过 ret 的值来获取成功写入的元素个数:

由于写字板无法直接读取 .bin 文件,因此我们在读取文件中的内容时,需要通过二进制文件编辑器打开,这里我使用的是 Notepad++ 进行的文件读取;
上图的测试结果中,我们也不难发现数据的存储是按照 高地址存储在高位,低地址存储在低位 的存储方式进行存储,即 小端存储。
从 ret 的值可以看到,我们成功的写入了6个元素,从文件中可以看到,我们成功的通过 fwrite 正确的完成了数据的写入;
七、fread
7.1 函数介绍

该函数的用法如下:
- 向函数中传入4个参数
ptr:指向大小至少为size * count的内存块指针,该指针被转换成void*size:被读取的每个元素的大小,单位为字节count:被读取的元素个数,每个元素的大小为size字节stream:指向识别输入流的FILE对象指针
- 函数会从
stream中识别count个大小为size字节的元素,并将其存储在ptr指向的内存块中- 识别成功时,会返回识别的元素总数量
- 当这个总数与
count不相等时,会被设置合适的指针指示器:- 发生读取错误时,设置错误指示器,能够被
ferror检测 - 遇到文件末尾时,设置文件末尾指示器,能够被
feof检测
- 发生读取错误时,设置错误指示器,能够被
- 当
size或者count二者中的其中之一为0时,函数返回0,并且不会改变ptr中的内容以及stream的状态
7.2 函数使用
下面我们就尝试着通过 fread 函数从 stream 中获取一组整数 {0, 1, 2, 3, 4, 5} ,并将其存储到 ptr 指向的内存块中:
void test8() {
FILE* pf1 = fopen("C:\\Users\\LIQIAN\\Desktop\\data.bin", "rb");
if (pf1 == NULL) {
perror("pf1");
return;
}
int arr[6] = { 0 };
int ret = fread(arr, sizeof(int), 6, pf1);
printf("ret = %d\n", ret);
for (int i = 0; i < 6; i++) {
printf("arr[%d] = %d\n", i, arr[i]);
}
fclose(pf1);
pf1 = NULL;
}
这一次由于我们是需要从二进制文件中读取数据,因此我们打开文件的模式采用的是:rb :

可以看到,此时我们成功的完成了从文件中读取数据,并存储在数组中。
八、标准输入输出
8.1 fscanf
该函数的介绍我们就不像前面的函数一样,下面我们通过函数的类比来说明该函数的使用方法:

上图展示的是 scanf 的函数定义。这个函数是我们十分熟悉的标准输入函数,我们在使用该函数进行输入时,通常采用下面的格式:
int a = 0;
char arr[10] = "";
int input = scanf("%d%s", &a, arr);
简单的说明就是:
- 对变量进行赋值时,需要对其取地址
- 对指针进行赋值时,则不需要进行取地址

上图展示的是 fscanf 的函数介绍,从函数定义可以看到,相比于 scanf ,该函数多了一个参数:stream 。
这个参数的含义也很简单,就是由 scanf 函数从标准输入流 stdin 中获取数据转变为了从指定流 stream 中获取数据——该指定流可以是 stdin 也可以是指针指向的流:
int a = 0;
char arr[10] = "";
// 从标准输入流中获取数据
int finput1 = fscanf(stdin, "%d%s", &a, arr);
FILE* pf1 = fopen("C:\\Users\\LIQIAN\\Desktop\\data.txt", "rb");
if (pf1 == NULL) {
perror("pf1");
return;
}
// 从文件中获取数据
int finput2 = fscanf(pf1, "%d%s", &a, arr);

该函数可以视为 fscanf 的一个变种,从函数定义可以看到,其参数 stream 替换为了 const char* s ,我们不难推测,这代表着我们获取数据的源地址从指定流变为了指定字符串:
int a = 0;
char arr[10] = "";
char* str = "Hello World!!!";
int sinput = sscanf(str, "%d%s", &a, arr);
相信大家看到这里应该明白这三个函数的具体使用方式了,这里我就不再继续展开赘述;
8.2 fprintf
与 fscanf 一样,fprintf 同样是从 printf 函数变种过来。下面我们一起来看一下其函数定义:

printf 函数我相信大家已经熟悉的不能再熟悉了这里我们直接展示其用法:
int a = 0;
char arr[10] = "";
int input = scanf("%d%s", &a, arr);
printf("a = %d\tarr = %s\n", a, arr);
与 scanf 不同,在 printf 中,我们不需要对占位符对应的参数进行取地址操作;

在 fprintf 函数中,新增了一个参数 stream ,意思是相比于 printf 从标准输出流 stdout 中输出数据,fprintf 则是从 stream 指定的流中输出数据——即指定流可以是 stdout 也可以是其他指定流 stream;
int a = 0;
char arr[10] = "";
// 从标准输入流中获取数据
int finput1 = fscanf(stdin, "%d%s", &a, arr);
// 从标准输出流中获取数据
fprintf(stdout, "a = %d\tarr = %s\n", a, arr);
FILE* pf1 = fopen("C:\\Users\\LIQIAN\\Desktop\\data.txt", "rb");
if (pf1 == NULL) {
perror("pf1");
return;
}
// 从文件中获取数据
int finput2 = fscanf(pf1, "%d%s", &a, arr);
// 从文件中获取数据
fprintf(pf1, "a = %d\tarr = %s\n", a, arr);

而 sprintf 与 fprintf 的区别就在于输出数据的目的地址不同:
fprintf向直流流中输出数据sprintf向指定字符串中输出数据
int a = 0;
char arr[10] = "";
char* str = "Hello World!!!";
char str2[20] = { 0 };
int sinput = sscanf(str, "%d%s", &a, arr);
sprintf(str2, "a = %d\tarr = %s\n", a, arr);
8.3 小结
不管是标准输入函数还是标准输出函数,其不同的函数之间的差异就是对象不同:
- 标准输入函数:不同的函数获取数据的源地址不同
- 标准输出函数:不同的函数输出数据的目标地址不同
输入与输出实际上就是对指定对象的读取与写入:
- 输入函数,向指定对象中读取数据
- 输出函数,向指定对象中写入数据
怎么理解呢?
我们以最熟悉的 scanf/printf 说明:
scanf函数就是从标准输入流stdin中读取数据,并将读取到的数据按照占位符复制到其对应的参数中;printf函数就是将占位符对应的参数的值替换到占位符中,并将format中的全部内容输出到标准输出流stdout中;
这里要注意,标准输入流就是指键盘,标准输出流就是指屏幕;
因此我们可以通过键盘将数据输入到标准输入流中,再通过 scanf 对其进行读取;同时我们也可以通过 printf 函数将这些数据输出到标准输出流中,在通过屏幕将流中的内容显示出来;
结语
今天的内容到这里就全部结束了。在本篇博客中,我们系统学习了C语言文件操作的核心读写函数,重点掌握了以下关键内容:
-
八大顺序读写函数的深入解析:
-
fgetc函数:字符输入函数,从指定流中逐个字符读取,成功时返回字符,失败返回EOF
-
fputc函数:字符输出函数,支持w(覆盖)和a(追加)两种写入模式,实际演示了模式差异
-
fgets函数:文本行输入函数,读取num-1个字符并自动添加\0终止符
-
fputs函数:文本行输出函数,不写入字符串末尾的\0,支持\t、\n等转义字符识别
-
fwrite函数:二进制写操作,专用于.bin文件,演示了小端存储方式
-
fread函数:二进制读操作,与fwrite配合完成二进制文件读写
-
-
标准输入输出函数族的对比分析:
-
scanf/fscanf/sscanf差异:
-
scanf:从标准输入流stdin读取
-
fscanf:从指定文件流读取
-
sscanf:从字符串中读取数据
-
-
printf/fprintf/sprintf差异:
-
printf:向标准输出流stdout输出
-
fprintf:向指定文件流输出
-
sprintf:向字符串缓冲区输出
-
-
-
重要概念的实践理解:
-
位置指示器:通过fopen创建文件时光标的位置,理解其为文件内部的"光标"概念
-
文件末尾指示器:当位置指示器到达文件末尾时触发,标志文件内容读取完毕
-
错误指示器:在文件操作发生错误时设置,用于错误检测
-
EOF值验证:通过实际测试确认
EOF的整型值为-1
-
如果这篇博客对您有帮助,请不吝赐予支持:
👍 点赞 - 您的认可是我持续创作的最大动力!
⭐ 关注 - 获取更多C语言编程技术的深度解析
💾 收藏 - 方便日后随时查阅,巩固知识点
🔄 转发 - 分享给更多正在学习编程的朋友,共同进步
💬 评论 - 您有任何疑问或建议,欢迎在评论区留言交流!
期待在接下来的编程学习中继续与您同行!

2106

被折叠的 条评论
为什么被折叠?



