掌握C语言文件操作:从理论到实战指南

        文件操作是C语言编程中不可或缺的一部分,它使得程序能够持久化存储数据,并在需要时高效读写。本文将从基础概念到实战技巧,系统讲解C语言文件操作的核心知识点,并结合代码示例帮助读者深入理解。

一. 为什么需要文件操作?

        程序运行时,数据存储在内存中,一旦程序结束,内存数据就会被释放。文件操作解决了数据的持久化问题,例如:

        保存用户配置:如游戏的存档和设置。

        处理大规模数据:如日志文件或数据库的读写。

        跨进程通信:通过文件共享数据。

        二. 文件类型

        先补充文件和文件名的概念:

        文件:磁盘(硬盘)上的文件是文件。

        文件名:也称文件标识,例如C:\code\test.txt。由文件路径、文件名主干、文件后缀三部分组成,以便用户识别和引用。 

        在程序设计中,我们一般讲两种文件:程序文件数据文件(从文件功能的角度分类)。

1. 程序文件

        程序文件包含三种:

        源文件(.c):开发者编写的代码文件。

        目标文件(.obj):编译后的中间文件。

        可执行文件(.exe):链接后直接运行的程序。 

        2. 数据文件 

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

        数据文件包含两种:

        文本文件:如.txt、.csv,内容为ASCII字符。

        二进制文件:如图片、音频,内容为二进制数据。

        不同数据存储的数据文件类型不同,字符型数据一律以ASCII形式存储,数值型数据可以用ASCII形式存储,也可以使用二进制形式存储。

        例如,十进制整型10000,二进制为00000000 00000000 00100111 00010000。如果以ASCII码的形式输出到磁盘(文本文件),为“1” “0” “0” “0” “0”,磁盘中占用5个字节(每个字符1个字节);如果以二进制形式输出(二进制文件),为0x10,0x27,0x00,0x00,则在磁盘中只占4个字节

        测试代码:

#include <stdio.h>int main()
{int a = 10000;FILE* pf = fopen("test.txt", "wb");//wb - write binaryfwrite(&a, 4, 1, pf);//二进制的形式写到文件中fclose(pf);pf = NULL;return 0;
}

        这段代码创建了一个新文件,并将整数10000以二进制形式写入文件中。运行后调试控制台没有任何结果,但是编译器已经创建了一个二进制文件(.txt),可以通过添加现有项找到该二进制文件。

         我们通过选择打开方式为二进制编译器就能打开该二进制文件。

        文件内容:

        三. 流和标准流 

        程序的数据需要输出到各种外部设备,也需要从外部设备获取数据,不同的外部设备的输入输出操作各不相同。为了方便程序员对各种设备进行方便的操作,我们抽象出了流的概念。 

        1. 什么是流?

        在C语言中,流(stream)是一个抽象概念,表示程序与外部设备(如键盘、显示器、文件、网络等)之间数据传输的通道。

        可以将流想象成一条“数据河流”,数据在这条河中单向流动,因此有输入流也有输出流:

        输入流:数据从外部设备(如键盘、文件)流向程序。

        输出流:数据从程序流向外部设备(如显示器、文件)。

        2. 流的抽象意义 

        流的抽象意义有两点: 

        1. 统一接口:不同设备的操作方式差异巨大(例如键盘输入和文件读取),但流通过统一接口(如fgetc、fprintf)屏蔽了底层细节,程序员无需关心设备的具体实现。
        2. 缓冲机制:流通常与缓冲区(Buffer)结合使用。例如,数据从内存写入磁盘时,先暂存到缓冲区,缓冲区满后一次性写入,提升IO效率(计算机系统在进行输入/输出操作时的性能表现)。 

        3. 标准流 

        但是我们从键盘输入数据,向屏幕上输出,并没有打开流。是因为C语言程序启动时,默认打开三个预定义的流,称为标准流:

        1. stdin - 标准输入流,通常关联键盘输入,scanf函数就是从标准输入流中读取数据。

        2. stdout - 标准输出流,通常关联显示器输出,printf函数就是将信息输出到标准输出流中。

        3. stderr - 标准错误流,专用于输出错误信息,默认也关联显示器。

         标准流的特点: 

        1. 无需手动打开和关闭:程序启动时自动创建,结束时自动释放。

        2. 数据类型为FILE*(称为文件指针):C语言中,就是通过FILE*的文件指针来维护流的各种操作。 

         四. 文件指针

         每个被使用的文件,都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息,如文件的名字,文件状态以及文件当前的位置等。

        这些信息被保存在一个名为FILE的结构体变量中。该结构体是由系统声明的,如VS2013编译环境下提供的stdio.h头文件中有以下的文件类型声明:

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

        不同的编译器的FILE类型包含的内容不完全相同,但是大同小异。每当打开⼀个文件的时候,系统会根据文件的情况自动创建⼀个FILE结构的变量,并填充其中的信息,使用者不必关心细节。

        ⼀般都是通过⼀个FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便。下面我们可以创建⼀个FILE*的指针变量: 

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

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

        示意图:

        五. 文件操作核心函数 

        1. 打开与关闭文件 

        文件在读写之前应该先打开文件,在使用结束后应该关闭文件。ANSI C规定使用fopen函数来打开文件,fclose函数来关闭文件。函数原型如下:

        fopen打开文件,需指定文件路径(文件名,filename)和模式(mode)。打开文件失败时,会返回NULL

        比如:

#include <stdio.h>int main()
{FILE* pf = fopen("test.txt", "r");//用只读的方式打开文件if (pf == NULL){perror("fopen");return 1;}fclose(pf);pf = NULL;return 0;
}

        这里并不存在文件名为test.txt的文件,所以pf为NULL。运行结果:

        文件的打开模式有以下几种:

模式含义文件不存在时的行为
"r"(只读)为了读取数据,打开一个已经存在的文本文件出错
"w"(只写)为了写入数据,打开一个文本文件(写入会覆盖原有内容)建立一个新文件
"a"(追加)向文本文件尾添加数据建立一个新文件
"rb"(只读)为了读取数据,打开一个已经存在的二进制文件出错
"wb"(只写)为了写入数据,打开一个二进制文件建立一个新文件
"ab"(追加)向二进制文件尾添加数据建立一个新文件
"r+"(读写)为了读和写,打开一个文本文件出错
"w+"(读写)为了读和写,建立一个新的文本文件建立一个新文件
"a+"(读写)打开一个文本文件,在文件尾进行读写 建立一个新文件
"rb+"(读写)为了读和写,打开一个二进制文件出错
"wb+"(读写)为了读和写,建立一个新的二进制文件建立一个新文件
"ab+"(读写)打开一个二进制文件,在文件尾进行读写 建立一个新文件

        比如:

#include <stdio.h>int main()
{int a = 10000;FILE* pf = fopen("test.txt", "w");//用只写的方式打开文件if (pf == NULL){perror("fopen");return 1;}fclose(pf);pf = NULL;return 0;
}

        运行后就会发现,程序文件相同目录下生成了一个新文件,名为test.txt。

        默认在当前目录下读或写,也可以通过绝对路径和相对路径让代码按照指定路径读/写文件:

        绝对路径:从根目录开始,完整描述文件或目录位置的路径,以“ / ”或“ \ ” 分隔。例如,C:\User\Username\Documents\file.txt。

        相对路径:相对于当前工作目录或文件位置的路径。例如,当前路径是C:\User\Username\Documents,那么file.txt的相对路径可以是..\Picture\file.jpg,表示文件file.jpg位于当前目录的上一级目录Picture中( . 表示当前目录,.. 表示上一级目录,以此类推)。

        注意:代码中要连用两个反斜杠,表示一个反斜杠,防止它被解释为一个转义序列符。

        2. 顺序读写函数 

        当我们掌握了打开和关闭文件,就要来学习如何读写文件。

        顺序读写函数有以下几种:

函数名功能适用于
fgetc字符输入函数(读取单个字符)所有输入流
fputc字符输出函数(写入单个字符)所有输出流
fgets文本行输入函数(读取一行文本)所有输入流
fputs文本行输出函数(写入一行文本)所有输出流
fscanf格式化输入函数所有输入流
fprintf格式化输出函数所有输出流
fread二进制输入文件输入流
fwrite二进制输出文件输出流

        上面说的适用于所有输出/入流,一般指适用于标准输出/入流和其他输出/入流(如文件输出/入流) 。前六个函数是针对文本数据进行文件的输出和输出,最后两个是针对二进制数据进行文件的输出和出入。

        2.1 fputc和fgetc

       fputc函数的原型:

         作用:fputc函数将字符character(传递参数是字符的ACSII码值),写入stream流(指向的对应文件信息区的指针),并前进位置指示器(即光标)。

        返回值:如果写入成功,会返回该字符的ACSII码值;如果写入失败,会返回EOF

        例如:

#include <stdio.h>int main()
{FILE* pf = fopen("data.txt", "w");if (pf == NULL){perror("fopen");return 1;}//写文件fputc('a', pf);	fputc('b', pf);fputc('c', pf);fputc('d', pf);//关闭文件fclose(pf);pf = NULL;return 0;
}

        这段代码将字符a、b、c、d写入data.txt中。运行之后就会发现,程序文件相同路径下生成了一个data.txt文本文件,打开后会有如下内容:

        当写入第一个字符a时,光标就会移动到a的后面,随后写入字符b,再次移动光标,以此类推。 

       fgetc函数的原型:

        作用:fgetc函数从流中获取字符,并前进光标。

        返回值:如果获取成功,会返回该字符的ACSII码值(int类型);如果获取字符失败,会返回EOF。 

        例如:

#include <stdio.h>int main()
{FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return 1;}//读文件(文件原有字符串“abcd”)int ch = fgetc(pf);printf("%c\n", ch);ch = fgetc(pf);printf("%c\n", ch);ch = fgetc(pf);printf("%c\n", ch);ch = fgetc(pf);printf("%c\n", ch);//也可以通过fgetc的返回值读取文件中所有字符://while ((int ch = fgetc(pf)) != EOF)//{//	printf("%c", ch);//}//关闭文件fclose(pf);pf = NULL;return 0;
}

        这段代码从data.txt中读取了前四个字符,并依次打印。运行结果:

         当读取第一个字符a时,光标就会移动到a的后面,随后写读取字符b,再次移动光标,以此类推。 

        再来看一个例子:

#include <stdio.h>int main()
{int ch = fgetc(stdin);putchar(ch);//打印一个字符,相当于printf("%c", )//getchar -- 读取一个字符,相当于scanf("%c", )return 0;
}

        fgetc函数从标准输入流stdin中获取字符,putchar函数再将字符输出。运行后会发现,控制台窗口没有输出任何数据,光标停在首位。因为此时标准输入流中没有数据,我们可以通过键盘输入字符,这个字符就会进入标准输入流,并被获取和打印。运行结果:

        这说明,fgetc函数适用于所有输入流,同样的也可以证明fputc函数适用于所有输出流,代码如下:

#include <stdio.h>int main()
{int ch = fgetc(stdin);fputc(ch, stdout);return 0;
}

        我们通过键盘输入一个字符,对应的就会打印这个字符。运行结果:

         2.2 fputs和fgets

        fputs函数的原型:

        作用:fputs函数将指针str指向的字符串写入流中。

        返回值:如果写入成功,会返回一个非负值(non-negative value);如果写入失败,会返回EOF。 fputs函数将字符串写入流中时,遇到“ \0 ”结束写入。

        例如:

#include <stdio.h>int main()
{FILE* pf = fopen("data.txt", "w");if (pf == NULL){perror("fopen");return 1;}//写文件//写入一行字符fputs("How are you?\n", pf);fputs("abcdefg\n", pf);//关闭文件fclose(pf);pf = NULL;return 0;
}

        这里就会发现,data.txt多出了很多内容:

        注意:

        1. 写入字符串含“\n”,写入文件时就会换行。

        2. 我们会发现,原本的“abcd”已经不见了,这就是“w”只写模式的特点:写入会覆盖原有内容。 

        fgets函数的原型:

        作用:从流中读取字符,并将其作为字符串储存到str中,直到读取(num-1)个字符或者到达换行符或文件结束符(end-of-file)为止(以先发生的为准),并移动光标至读取字符的后面 。当换行符使fgets停止读取时,换行符仍被函数认为是一个有效字符,并包含在复制到str的字符串中。

        返回值:如果成功,该函数返回str;如果发生读取失败,则返回的指针是空指针

        例如:

#include <stdio.h>int main()
{FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return 1;}//读文件,data.txt中有字符串“abcdefghijk”//读取多个字符char ch[10];fgets(ch, 10, pf);//关闭文件fclose(pf);pf = NULL;return 0;
}

        调试并打开监视:

        这里说明,函数参数num为10时,实际上是只读取了9个有效字符,和一个“\0”。把数组大小和num改为20,再调试并打开监视: 

        fgets读取完所有字符后就不会再读取。

        再举一个例子:

#include <stdio.h>int main()
{FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return 1;}//读文件//data.txt中有://hello world//hahahachar ch[20];fgets(ch, 20, pf);printf("%s", ch);//关闭文件fclose(pf);pf = NULL;return 0;
}

        运行结果:

        并没有读取到hahaha。但是调试并打开监视:

        这里ch储存了转行符,所以只要再读取一次就能打印hahaha:

        2.3 fprintf和fscanf

        fprintf函数的原型:

 

        fprintf函数将具有一定格式的数据写入流中。函数参数后面有省略号,这被称为可变参数。printf函数的参数中也存在可变参数,例如:

printf("%d",10);
printf("hello");
printf("%d %s",10,:"hello");

        参数的类型和数量都不同,所以函数参数用可变参数代替。fprintf的使用和printf非常相似,只是fprintf的函数参数比printf多了一个文件指针类型的流,因此使用fprintf函数并不困难,例如:

#include <stdio.h>int main()
{int age = 18;char name[20] = "zhangsan";double grades = 95.5;FILE* pf = fopen("data.txt", "w");if (pf == NULL){perror("fopen");return 1;}//写文件fprintf(pf, "%d %s %.2lf", age, name, grades);//关闭文件fclose(pf);pf = NULL;return 0;
}

        运行结果:

        能够按一定格式输出,也就能按一定格式输入,fscanf函数就能从文件中获取数据。

        fscanf函数的原型如下:

        fscanf函数和scanf函数的参数相似(fscanf函数的参数多了“FILE* stream”),使用方法也很相似,例如:

#include <stdio.h>int main()
{int age;char name[20];double grades;FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return 1;}//读文件//文件原有://18 zhangsan 99.50//scanf("%d %s %lf", &age, name, &grades);--再加上流就是fscanf函数:fscanf(pf, "%d %s %lf", &age, name, &grades);//关闭文件fclose(pf);pf = NULL;return 0;
}

        运行结果:

         2.4 fwrite和fread

        fwrite函数的原型:

    

        fwrite函数将数据以二进制形式写入流中。该函数有以下参数:

        1. const void* ptr: ptr指向被写的数据。

        2. size_t size:被写的数据中一个元素的长度(单位是字节)。

        3. size_t count:元素的个数。

        4. FILE* stream:写入数据到stream流。

        例如,将一个整型数组数据以二进制形式写入data.txt文件中,代码如下:

#include <stdio.h>int main()
{FILE* pf = fopen("data.txt", "wb");//以二进制写的方式打开文件if (pf == NULL){perror("fopen");return 1;}//写文件int arr[] = { 1,2,3,4,5,6,7,8,9,10 };fwrite(arr, sizeof(int), 10, pf);//关闭文件fclose(pf);pf = NULL;return 0;
}

        运行后打开data.txt,会发现写入了数据,但是并不能看出来是10个数字:

        用文本文件的方式打开二进制文件,数据就会变为乱码,但实际上数组中的十个整型数据已经写入了data.txt。 

        我们可以用fread函数读取数据验证想法,fread函数的原型如下:

        fread函数从流中读取二进制数据。该函数的四个参数和fwrite函数一样。例如:

#include <stdio.h>int main()
{FILE* pf = fopen("data.txt", "rb");//以二进制只读的方式打开文件if (pf == NULL){perror("fopen");return 1;}//写文件int arr[10] = {0};fread(arr, sizeof(int), 10, pf);int i = 0;for (i = 0; i < 10; i++){printf("%d ", arr[i]);}//关闭文件fclose(pf);pf = NULL;return 0;
}

        运行结果:

        说明fread函数成功从data.txt中读取这十个数字,原本文件中确实储存着这十个数字。 

        如果让fread函数读取文本文件,就不会读取到正确数据。例如,我们在data.txt中写入十进制整型1到10,再用相同代码读取数据,运行结果:

        读取数据明显不正确。所以要注意文本文件和二进制文件的区别,以及对应的数据之间的区别。

        3. sscanf和sprintf

        这里补充sscanfsprintf函数。

        我们介绍过printf、fprintf、scanf和fscanf函数:

        scanf -- 针对stdin的格式化的输入函数

        printf -- 针对stdout的格式化的输出函数

        fscanf -- 针对所有输入流 格式化的输入函数

        fprintf -- 针对所有输出流的格式化的输出函数

        sscanf和sprintf函数能够实现有格式的数据与字符串之间的转换

        sscanf -- 从字符串中,按照格式提取格式化的数据

        sprintf -- 将带有格式的数据,按照格式转化成字符串

        sprintf函数的原型:

        sprintf函数写格式化的数据到str指向的字符串中,也就是将格式化的数据转换成字符串。例如:

#include <stdio.h>int main()
{int age = 18;char name[20] = "zhangsan";double grades = 95.5;char buf[120] = { 0 };//printf("%d %s %.1lf", age, name, grades)sprintf(buf, "%d %s %.1lf", age, name, grades);printf("%s\n", buf);return 0;
}

        运行结果:

        sprintf函数将age、name和grades这三个不同类型的变量转换成了字符串并写入buf指向的字符串中。 

        sscanf函数的原型:

        sscanf函数和sprintf函数的作用相反,sscanf函数从字符串中读取有格式的数据。例如:

#include <stdio.h>int main()
{int age = 18;char name[20] = "zhangsan";double grades = 95.5;char buf[120] = { 0 };sprintf(buf, "%d %s %.1f", age, name, grades);int age2 = 0;char name2[20] = "";double grades2 = 0;//scanf("%d %s %lf", &age2, &name2, &grades2);sscanf(buf, "%d %s %lf", &age2, &name2, &grades2);printf("%d\n", age2);printf("%s\n", name2);printf("%.1lf\n", grades2);return 0;

        运行结果:

        sscanf函数从buf指向的字符串中,按%d %s %lf的格式顺序读取数据,并分别存储在变量中。 

4. 文件的随机读写

        文件的读写,既支持顺序读写,也支持随机读写。比如文件中有“abcdef”,假设此时光标默认在f的右边,我们可以通过一些函数让光标移动到e的左侧,以此为起始位置进行读写,这就是文件的随机读写。 

        4.1 fseek 

        fseek函数的原型:

        fseek函数可以根据文件指针的位置和偏移量来重新定位文件指针(即文件内容的光标)。该函数有三个参数:

        1. FILE* stream:指向文件的流。

        2. long int offset:相对于起始位置origin的偏移量(单位字节),向右偏移是正数,向左偏移是负数。

        3. int origin:origin有三种情况:SEEK_SET(文件的起始处),SEEK_CUR(光标的当前位置),SEEK_END(文件的末尾处)。

        例如,文件中有“abcdefghi” :

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

        这样就会打印a和b两个字符。如果想要读取a后立即读取e,就可以用fseek函数重新定位光标(光标应该在e的左侧):

fseek(pf,4,SEEK_SET);//从文件起始处,偏移量为4的地方
fseek(pf,3,SEEK_CUR);//从光标的当前位置(即a的右侧),偏移量为3的地方
fseek(pf,-5,SEEK_END);//从文件末尾处,偏移量为-5(向左偏移,为负数)的地方

        这三种写法都是定位光标在e的左侧。完整代码:

#include <stdio.h>int main()
{int age;char name[20];double grades;FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return 1;}//读文件int ch = fgetc(pf);printf("%c\n", ch);fseek(pf, 4, SEEK_SET);//fseek(pf, 3, SEEK_CUR);//fseek(pf, -5, SEEK_END);ch = fgetc(pf);printf("%c\n", ch);//关闭文件fclose(pf);pf = NULL;return 0;
}

        运行结果:

        4.2 ftell 

        ftell函数的原型:

        ftell函数会返回文件指针相对于起始位置的偏移量(返回类型为long int)。 

        例如:

#include <stdio.h>int main()
{int age;char name[20];double grades;FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return 1;}//读文件//文件原有内容:abcdefghiint ch = fgetc(pf);printf("%c\n", ch);fseek(pf, 4, SEEK_SET);ch = fgetc(pf);printf("%c\n", ch);printf("%ld\n", ftell(pf));//关闭文件fclose(pf);pf = NULL;return 0;
}

        当第二次fgetc函数运行后,光标指在e和f之间,与起始位置的偏移量为5。 

        运行结果:

4.3 rewind 

        rewind函数的原型:

        rewind函数可以让文件指针的位置回到文件的起始位置

        例如:

#include <stdio.h>int main()
{int age;char name[20];double grades;FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return 1;}//读文件int ch = fgetc(pf);printf("%c\n", ch);fseek(pf, 4, SEEK_SET);ch = fgetc(pf);printf("%c\n", ch);rewind(pf);//光标再次回到起始位置ch = fgetc(pf);//此时读取的就是字符aprintf("%c\n", ch);//关闭文件fclose(pf);pf = NULL;return 0;
}

        第二次fgetc后,rewind函数让光标再次指向了初始位置,则第三次fgetc函数读取到的字符仍然是字符a。

运行结果:

        以上就是文件的随机读写中最重要的三个函数。 

六. 文件读取结束的判定

        在读取文件时,我们可以利用“文件是否读取结束”这一信息判断是否继续读取文件。

        例如:

#include <stdio.h>int main()
{int age;char name[20];double grades;FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return 1;}//读文件//文件有:"abcdefghi"int ch = 0;while ((ch = fgetc(pf)) != EOF){printf("%c\n", ch);}//关闭文件fclose(pf);pf = NULL;return 0;
}

        运行结果:

        这里就是循环判断fgetc函数的返回值是否为EOF(文件结束标志),如果返回值不是EOF,说明文件还可以正常读取,代码就会继续读取并打印数据;如果返回值是EOF,说明文件已经读取结束,跳出循环。

        判定文件读取结束,一般都是判断函数的返回值:

        1.文本文件(EOF/NULL):

                 fgetc(函数返回数据的ASCII码值):判断返回值是否为EOF

                 fgets(函数返回str):判断返回值是否为NULL

        2. 二进制文件(返回值是否小于实际要读的个数):

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

        文件读取结束有两种情况:

        1. 正常读取:遇到文件末尾而结束。

        2. 异常读取:发生读取错误而结束。

        想要知道是哪种情况导致文件读取结束,就需要用到feof函数和ferror函数:

        int feof ( FILE * stream ):如果文件读取时遇到文件末尾,则返回非0的整型。

        int ferror ( FILE * stream ):如果文件读取时发生错误,则返回非0的整型。

        例如(文本文件):

int main(void)
{FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return 1;}//fgetc 当读取失败的时候或者遇到文件结束的时候,都会返回EOFint c; // 注意:int,非char,要求处理EOFwhile ((c = fgetc(pf)) != EOF) //I/O读取文件循环{putchar(c);}//判断是什么原因结束的if (ferror(pf))puts("I/O error when reading");else if (feof(pf))puts("End of file reached successfully");fclose(pf);pf = NULL;
}

        运行结果:

        根据返回结果,文件读取结束是因为到达文件末尾。

        例如(二进制文件): 

#include <stdio.h>enum { SIZE = 5 }; int main(void)
{double a[SIZE] = { 1.,2.,3.,4.,5. }; FILE * fp = fopen("test.bin", "wb"); // 必须⽤⼆进制模式fwrite(a, sizeof *a, SIZE, fp); // 写 double 的数组fclose(fp);double b[SIZE];fp = fopen("test.bin","rb");size_t ret_code = fread(b, sizeof *b, SIZE, fp); // 读 double 的数组if(ret_code == SIZE) {//判断返回值是否小于实际要读的个数puts("Array read successfully, contents: ");for(int n = 0; n < SIZE; ++n)printf("%f ", b[n]);putchar('\n');} else { // error handlingif (feof(fp))printf("Error reading test.bin: unexpected end of file\n");else if (ferror(fp)) {perror("Error reading test.bin");}}fclose(fp);
}

        这里就是判断fread函数的返回值(ret_code)是否小于实际要读的个数,如果不小于,代码接着打印数据, 如果小于,代码就不会再打印数据,而是用feof函数和ferror函数检测错误情况。

        运行结果:

        由此可知,在文件读取过程中,不能用feof函数和ferror函数的返回值直接来判断文件是否结束,正确方法应该是判断各种文件顺序写函数的返回值

七. 文件缓冲区

        ANSI C 标准采用“缓冲文件系统” 处理数据文件。

        缓冲文件系统是指系统自动地在内存中为程序中每⼀个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才⼀起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。

        缓冲区的大小根据C编译系统决定的。 

        示意图:

        例如:

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

        根据代码指示在不同时间段打开text.txt文件,就会发现文件内容的变化。

        这里得出结论:因为有缓冲区的存在,C语言在操作文件的时候,需要做刷新缓冲区或者在文件操作结束的时候关闭文件。如果不做,可能导致读写文件的问题。

八. 总结

        文件操作是C语言中实现数据持久化的核心技能。通过本文的学习,我们了解到文件的打开、读写、随机访问及错误处理等关键操作。 

        掌握文件操作,让你的程序真正“记住”数据!

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

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

相关文章

Linux 线程:从零构建多线程应用:系统化解析线程API与底层设计逻辑

线程 线程的概述 在之前&#xff0c;我们常把进程定义为 程序执行的实例&#xff0c;实际不然&#xff0c;进程实际上只是维护应用程序的各种资源&#xff0c;并不执行什么。真正执行具体任务的是线程。 那为什么之前直接执行a.out的时候&#xff0c;没有这种感受呢&#xf…

014_多线程

多线程 多线程创建线程方式一&#xff1a;继承Thread类方式二&#xff1a;实现Runable接口方式三&#xff1a;实现Callbale接口 Thread的常用方法线程安全线程同步方式一&#xff1a;同步代码块同步方法方式三&#xff1a;Lock锁 线性池创建线程池处理Runnable任务处理Callable…

机场跑道异物检测数据集VOC+YOLO格式33793张31类别

数据集分辨率都是300x300,都是贴近地面拍摄&#xff0c;具体看图片 据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;33793 标注数量(xml文件…

Spring Cloud 远程调用

4.OpenFeign的实现原理是什么&#xff1f; 在使用OpenFeign的时候&#xff0c;主要关心两个注解&#xff0c;EnableFeignClients和FeignClient。整体的流程分为以下几个部分&#xff1a; 启用Feign代理&#xff0c;通过在启动类上添加EnableFeignClients注解&#xff0c;开启F…

Unity中使用FMETP STREAM传输实时画面

一、客户端&#xff08;发送端&#xff09; 总体思路&#xff1a;先把画面编码Encoder&#xff0c;再发送给服务端 新建场景&#xff0c;创建一个实体&#xff0c;名为FMnet&#xff0c;添加组件FMNetworkManager&#xff0c;将NetworkType设置为客户端Client&#xff0c;设置…

Baklib三步构建企业内容中台

需求调研构建内容中台 企业内容中台建设的首要环节在于精准识别业务需求与知识管理痛点。通过Baklib 是什么类型的工具的定位分析可知&#xff0c;其作为知识管理中枢&#xff0c;能够系统梳理客户服务场景中的高频咨询、产品文档更新需求及跨部门协作流程。在需求调研阶段&am…

实现抗隐私泄漏的AI人工智能推理

目录 什么是私人AI? 什么是可信执行环境? TEE 如何在 AI 推理期间保护数据? 使用 TEE 是否存在风险? 有哪些风险? Atoma 如何应对这些风险 为什么去中心化网络是解决方案 人工智能推理过程中还有其他保护隐私的方法吗? 私人人工智能可以实现什么? 隐私驱动的应…

一、TorchRec里边的输入输出类型

TorchRec中的输入和输出格式 文章目录 TorchRec中的输入和输出格式前言一、JaggedTensor1.1 核心概念1.2 核心属性&#xff0c;也就是参数1.3 关键操作与方法 二、KeyedJaggedTensor2.1 核心概念2.2 核心属性&#xff0c;也就是参数 3、KeyedTensor总结 前言 TorchRec具有其特…

JAVA实现在H5页面中点击链接直接进入微信小程序

在普通的Html5页面中如何实现点击URL链接直接进入微信小程序&#xff0c;不需要扫描小程序二维码&#xff1f; 网上介绍的很多方法是在小程序后台设置Schema&#xff0c;不过我进入我的小程序后台在开发设置里面 没有找到设置小程序Schema的地方&#xff0c;我是通过调用API接口…

uniapp解决上架华为应用市场审核要求-监听权限的申请

支持android平台全局监听权限的申请。当申请权限时&#xff0c;会在页面顶部显示申请权限的目的。主要解决上架华为应用市场审核要求&#xff1a;APP在调用终端权限时&#xff0c;应同步告知用户申请该权限的目的。 因为如果不提示&#xff0c;你上架应用市场会被打打回来 Tip…

文件IO5(JPEG图像原理与应用)

JPEG图像原理与应用 ⦁ 基本概念 JPEG&#xff08;Joint Photographic Experts Group&#xff09;指的是联合图像专家组&#xff0c;是国际标准化组织ISO制订并于1992年发布的一种面向连续色调静止图像的压缩编码标准&#xff0c;所以也被称为JPEG标准。 同样&#xff0c;JP…

vue3 history路由模式刷新页面报错问题解决

在使用history路由模式时刷新网页提示404错误&#xff0c;这是改怎么办呢。 官方解决办法 https://router.vuejs.org/zh/guide/essentials/history-mode.html

3D激光轮廓仪知识整理(待完善)

文章目录 1.原理和应用场景1.1 相机原理1.1.1 测量原理1.1.2 相机激光器1.1.3 沙姆镜头1.1.4 相机标定1.1.5 中心线提取 1.2 应用场景1.2.1 测量相关应用1.2.2 缺陷检测相关应用 2.相机参数介绍及选型介绍2.1 成像原理2.2 原始图成像2.3 生成轮廓图2.4 相机规格参数2.4.1 单轮廓…

w285药店管理系统的设计与实现

&#x1f64a;作者简介&#xff1a;多年一线开发工作经验&#xff0c;原创团队&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的网站项目。 代码可以查看文章末尾⬇️联系方式获取&#xff0c;记得注明来意哦~&#x1f339;赠送计算机毕业设计600个选题excel文…

Google Chrome Canary版官方下载及安装教程【适用于开发者与进阶用户】

谷歌浏览器&#xff08;Google Chrome&#xff09;以其高性能、强扩展性和良好的用户体验深受全球用户喜爱。在其多个版本中&#xff0c;Chrome Canary因具备最前沿的功能测试环境&#xff0c;成为开发者和技术探索者的首选。如果你希望第一时间体验Google Chrome最新功能&…

RocketMQ深度百科全书式解析

​一、核心架构与设计哲学​ ​1. 设计目标​ ​海量消息堆积​&#xff1a;单机支持百万级消息堆积&#xff0c;适合大数据场景&#xff08;如日志采集&#xff09;。​严格顺序性​&#xff1a;通过队列分区&#xff08;Queue&#xff09;和消费锁机制保证局部顺序。​事务…

每日一题(小白)暴力娱乐篇19

样例&#xff1a; 6 1 1 4 5 1 4 输出&#xff1a; 56 66 52 44 54 64 分析题意可以得知&#xff0c;就是接收一串数字&#xff0c;将数字按照下标每次向右移动一位&#xff08;末尾循环到第一位&#xff09;&#xff0c;每次移动玩计算一下下标和数字的乘积且累加。 ①接收…

如何应对“最后时刻任务堆积”(鼓包现象)

应对“最后时刻任务堆积”&#xff08;鼓包现象&#xff09;的方法包括&#xff1a;合理规划项目时间表、强化进度跟踪管理、明确任务优先级、有效的资源配置、提升团队沟通效率。其中&#xff0c;强化进度跟踪管理尤为关键。根据项目管理协会&#xff08;PMI&#xff09;的调查…

19C-19.3环境-impdp导入到view时卡死

帮客户导入一个用户时&#xff0c;发现VIEW部分无法进行下去 Processing object type SCHEMA_EXPORT/TABLE/IDENTITY_COLUMN Processing object type SCHEMA_EXPORT/PACKAGE/PACKAGE_SPEC Processing object type SCHEMA_EXPORT/FUNCTION/FUNCTION Processing object type SCH…

一、简单的 Django 服务

一、配置虚拟环境 1.1 创建一个文件夹在导航栏输入cmd打开 1.2 安装依赖两个库 pip install virtualenv virtualenvwrapper-win -i https://pypi.tuna.tsinghua.edu.cn/simple验证是否安装成功 virtualenv --version pip show virtualenvwrapper-win 1.3 创建虚拟环境 mkvi…