【C语言】文件操作
2026/5/14 8:48:04 网站建设 项目流程

一.为什么使用文件?

1.1 内存数据的局限性

我们写的程序在运行时,数据是存储在内存中的。内存的特点是读写速度极快,但它有一个致命的缺点:易失性。

什么叫易失性?就是当程序正常退出、异常崩溃、或者计算机关机后,内存中保存的所有数据都会被操作系统回收,彻底清空。

c int main() { int count = 0; scanf("%d", &count); printf("你输入了:%d\n", count); return 0; }

每次运行这个程序,count都是从0开始。上一次运行时用户输入了什么,下一次完全看不到。

1.2 持久化的需求

在很多实际场景中,我们希望数据能够跨程序运行存在

场景需求
游戏存档退出游戏后,下次还能从上次进度继续
文本编辑器保存文档后关闭,下次打开还是原来的内容
数据库系统存储用户信息、订单记录等,不能因为重启就丢失
日志系统程序运行记录要保留,供后续排查问题

这些需求的核心就是两个字:持久化

1.3 文件的解决方案

文件就是解决持久化问题的标准方案:

  • 程序运行时,数据在内存中(速度快,但临时)

  • 需要保存时,把数据从内存写入文件(速度慢,但永久)

  • 下次程序运行,再从文件读回内存

总结:文件让数据能够超越程序的生命周期,实现持久化存储。


二.什么是文件?

2.1 文件的两种分类

从程序设计的角度,文件分为程序文件数据文件两大类:

程序文件

程序文件是与程序本身的编译、链接、执行相关的文件:

类型后缀(Windows)作用
源程序文件.c.cpp我们编写的源代码
目标文件.obj编译后生成的机器码(未链接)
可执行程序.exe链接后可以直接运行的程序

这些文件是开发过程的一部分,普通用户一般不直接接触目标文件和源文件。

数据文件

数据文件是程序运行时读写的数据文件。它可以是:

  • 输入数据文件:程序启动后需要读取的配置、资源、用户数据

  • 输出数据文件:程序运行结果保存到的文件

c // 从文件读取配置 FILE *fp = fopen("config.txt", "r"); fscanf(fp, "%s", config); // 把结果写入文件 FILE *out = fopen("result.txt", "w"); fprintf(out, "计算结果:%d\n", sum);

注意:我们平时讨论的、本章要学习的就是数据文件

2.2 从终端到磁盘:输入输出的扩展

在学习文件之前,我们处理的数据输入输出都是以终端为对象的:

输入来源:键盘(scanf、cin) 输出目标:显示器(printf、cout) text 键盘 → 程序 → 显示器

这种方式的问题很明显:数据无法保存。关了终端,数据就没了。

有了文件之后,输入输出的对象扩展了:

  • 输入可以来自文件:程序启动时从磁盘读取数据

  • 输出可以写到文件:程序结果保存到磁盘

text

磁盘文件 → 程序 → 磁盘文件 ↘ 显示器(实时查看)

这样,数据就实现了脱离终端的持久化存储

2.3 文件名的完整结构

一个文件必须有一个唯一的文件标识,方便用户识别和引用。

文件标识的完整格式包含三部分:

文件路径 + 文件名主干 + 文件后缀

具体例子:

组成部分示例说明
文件路径c:\code\文件在磁盘上的位置
文件名主干test文件的主要名称
文件后缀.txt标识文件类型

合起来就是:c:\code\test.txt

不同操作系统路径写法不同

操作系统路径示例分隔符
WindowsC:\Users\name\a.txt反斜杠\
Linux / macOS/home/name/a.txt正斜杠/

为了简便起见,在日常交流和学习中,我们常把文件名主干+后缀称为"文件名"。

2-4 ⼆进制⽂件和⽂本⽂件?


根据数据的组织形式,数据⽂件被称为⽂本⽂件或者⼆进制⽂件。

数据在内存中以⼆进制的形式存储,如果不加转换的输出到外存的⽂件中,就是⼆进制⽂件。
如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的⽂件就是⽂本⽂件。
⼀个数据在⽂件中是怎么存储的呢?
字符⼀律以ASCII形式存储,数值型数据既可以⽤ASCII形式存储,也可以使⽤⼆进制形式存储。如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占⽤5个字节(每个字符⼀个字节),⽽⼆进制形式输出,则在磁盘上只占4个字节。

代码测试:

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

这是在VS上的打开方法


三.文件的打开和关闭

我们程序的数据需要输出到各种外部设备,也需要从外部设备获取数据,不同的外部设备的输⼊输出操作各不相同,为了⽅便程序员对各种设备进⾏⽅便的操作,我们抽象出了流的概念,我们可以把流想象成流淌着字符的河。
C程序针对⽂件、画⾯、键盘等的数据输⼊输出操作都是通过流操作的。
⼀般情况下,我们要想向流⾥写数据,或者从流中读取数据,都是要打开流,然后操作。

标准流

  • 那为什么我们从键盘输⼊数据,向屏幕上输出数据,并没有打开流呢?

那是因为C语⾔程序在启动的时候,默认打开了3个流:
• stdin - 标准输⼊流,在⼤多数的环境中从键盘输⼊,scanf函数就是从标准输⼊流中读取数据。
• stdout - 标准输出流,⼤多数的环境中输出⾄显⽰器界⾯,printf函数就是将信息输出到标准输出流中。
• stderr - 标准错误流,⼤多数环境中输出到显⽰器界⾯。
这是默认打开了这三个流,我们使⽤scanf、printf等函数就可以直接进⾏输⼊输出操作的。

stdin、stdout、stderr 三个流的类型是: FILE * ,通常称为⽂件指针。
C语⾔中,就是通过 FILE* 的⽂件指针来维护流的各种操作的。

3-1 标准流(Standard Streams)

C程序启动时,会自动打开三个流,无需手动操作:

流名含义默认设备对应函数
stdin标准输入流键盘scanf()
stdout标准输出流显示器printf()
stderr标准错误流显示器perror()
  • 这三个流的类型都是FILE*(文件指针)。

  • 所以我们直接用scanfprintf就能输入输出,背后就是通过这些默认打开的流来工作的。

为什么不需要fopen
因为系统已经帮我们打开好了stdinstdoutstderr,程序可以直接使用。


3-2 文件指针(FILE*)

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

每个被使⽤的⽂件都在内存中开辟了⼀个相应的⽂件信息区,⽤来存放⽂件的相关信息(如⽂件的名字,⽂件状态及⽂件当前的位置等)。这些信息是保存在⼀个结构体变量中的。该结构体类型是由系统声明的,取名 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*的指针变量: 1 FILE* pf;//⽂件指针变量

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

补充说明

1.stdin/stdout/stderr也是FILE*

你可以像操作普通文件一样操作它们(虽然一般不这样做):

c fprintf(stdout, "Hello\n"); // 等价于 printf("Hello\n"); fscanf(stdin, "%d", &a); // 等价于 scanf("%d", &a);

2.stderrstdout的区别

  • 都输出到显示器,但:

    • stdout缓冲输出(可能等缓冲区满或换行才真正显示)

    • stderr无缓冲输出(立即输出)

  • 作用:stderr用于输出错误信息,保证程序崩溃时错误信息仍能显示。

c fprintf(stdout, "普通信息\n"); fprintf(stderr, "错误信息\n");

3. FILE 结构体

虽然不同编译器实现不同,但通常包含:

  • 文件位置指针(当前读写到哪)

  • 缓冲区指针

  • 文件描述符(操作系统层面的整型编号)

  • 读写状态标志

我们不需要直接操作这些成员,只用

3-3 ⽂件的打开和关闭

⽂件在读写之前应该先打开⽂件,在使⽤结束之后应该关闭⽂件。在编写程序的时候,在打开⽂件的同时,都会返回⼀个FILE*的指针变量指向该⽂件,也相当于建⽴了指针和⽂件的关系。

ANSI C 规定使⽤ fopen 函数来打开⽂件, fclose 来关闭⽂件。 //打开⽂件 FILE * fopen ( const char * filename, const char * mode ); //关闭⽂件

mode表⽰⽂件的打开模式,下⾯都是⽂件的打开模式:

C语言文件使用方式(打开模式)表格

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

记忆技巧

  • r(read):读,文件必须存在,否则报错

  • w(write):写,文件不存在就新建,存在则清空内容

  • a(append):追加,文件不存在就新建,存在则在末尾追加

  • +:增加读或写的能力(即读写模式)

  • b:以二进制方式操作(不加b默认为文本模式)

注意:使用这些模式时,需配合fopen()函数,如:

#include <stdio.h> int main () { FILE * pFile; //打开⽂件 pFile = fopen ("myfile.txt","w"); //⽂件操作 if (pFile!=NULL) { fputs ("fopen example",pFile); //关闭⽂件 fclose (pFile); } return 0; }

四.⽂件的顺序读写

4.1 顺序读写函数介绍

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

上⾯说的适⽤于所有输⼊流⼀般指适⽤于标准输⼊流和其他输⼊流(如⽂件输⼊流);所有输出流⼀般指适⽤于标准输出流和其他输出流(如⽂件输出流)。

补充说明

  • “适用于所有输入流”意味着这些函数不仅可以操作文件(如FILE*指向的文件),也可以操作stdin(标准输入流)。

  • “适用于所有输出流”意味着可以操作文件,也可以操作stdoutstderr

举例对比:

函数示例(操作文件)示例(操作标准流)
fgetcch = fgetc(fp);ch = fgetc(stdin);← 等价于getchar()
fputcfputc('A', fp);fputc('A', stdout);← 等价于putchar('A')
fgetsfgets(buf, 100, fp);fgets(buf, 100, stdin);← 读取键盘输入(含空格)
fputsfputs("Hello", fp);fputs("Hello", stdout);
fscanffscanf(fp, "%d", &n);fscanf(stdin, "%d", &n);← 等价于scanf
fprintffprintf(fp, "%d", n);fprintf(stdout, "%d", n);← 等价于printf
freadfread(&data, size, count, fp);不适合stdin(标准输入一般不用二进制批量读)
fwritefwrite(&data, size, count, fp);不适合stdout

fread/fwrite只适用于文件流,因为标准输入输出通常是文本交互模式,不是纯粹的二进制数据流。

头文件均是 #include <stdio.h>

  1. fputc
//int fputc(int char, FILE *stream); //char:要写入的字符(int 类型,实际只取低8位) //stream:输出流指针(如 stdout 或文件指针) //返回值:成功返回写入的字符,失败返回 EOF int main() { FILE* pf = fopen("test.txt", "w"); if (pf == NULL) { perror("open"); return 1; } //写文件 //fputc('a', pf); //fputc('b', pf); //fputc('c', pf); char ch = 0; for (ch = 'a'; ch <= 'z'; ch++) { fputc(ch, pf); } //关闭文件 fclose(pf); pf = NULL; return 0; }

2.fgetc

//int fgetc(FILE *stream); //stream:输入流指针(如 stdin 或 fopen 返回的文件指针) //返回值:读取的字符(转为 int),失败或文件末尾返回 EOF int main() { FILE* pf = fopen("test.txt", "r"); if (pf == NULL) { perror("open"); return 1; } //读文件 int ch = 0; while ((ch = fgetc(pf))!= EOF) { printf("%c",ch); } //关闭文件 fclose(pf); pf = NULL; return 0; }

3.fputs

//int fputs(const char *str, FILE *stream); //str:要输出的字符串(必须以 \0 结尾) //stream:输出流指针 //返回值:成功返回非负整数,失败返回 EOF int main() { FILE* pf = fopen("test.txt", "w"); if (pf == NULL) { perror("fopen"); return 1; } //写文件 fputs("hello world\n", pf); fputs("hello world\n", pf); fputs("hello world\n", pf); //关闭文件 fclose(pf); pf = NULL; return 0; }
int main() { fputc('a', stdout);//文本形式 return 0; }

字符 'a' 在文本形式里就是 a 这个可见字符,对应ASCII码 97 (二进制 01100001 )

在二进制形式里,存储的就是 01100001 这个原始字节,不会直接显示成 a

4.fgets

//char *fgets(char *str, int n, FILE *stream); //str:存放读取字符串的缓冲区 //n:最多读取 n-1 个字符(留一个位置给 \0) //stream:输入流指针 //返回值:成功返回 str,失败或末尾返回 NULL int main() { FILE* pf = fopen("test.txt", "r"); if (pf == NULL) { perror("fopen"); return 1; } //读文件 char arr[20] = { 0 }; while (fgets(arr, 20, pf) != NULL)//拆成多次打印 { printf("%s\n", arr); } //fgets(arr, 10, pf);//只读一行,不会跨行,字符不够的话,还会把\n读进来 //关闭文件 fclose(pf); pf = NULL; return 0; }

5.fscanf/fprintf

//int fscanf(FILE *stream, const char *format, ...); //stream:输入流指针 //format:格式控制字符串(如 "%d %s") //...:对应格式的变量地址(指针) //返回值:成功匹配并赋值的参数个数,失败返回 EOF //int fprintf(FILE *stream, const char *format, ...); //stream:输出流指针 //format:格式控制字符串 //...:要输出的变量或常量 //返回值:成功输出的字符数,失败返回负值 struct S { char name[20]; int age; float score; }; int main() { struct S s = { 0}; //想从文件test.txt中读取数据放进s中 FILE* pf = fopen("test.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); fprintf(stdout,"%s %d %f", s.name, s.age, s.score); fclose(pf); pf = NULL; return 0; }

6.fwrite

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); //ptr:要写入的数据的内存地址 //size:每个数据单元的字节数 //nmemb:要写入的数据单元个数 //stream:文件流指针(必须是文件,不能是 stdout) //返回值:实际成功写入的单元个数 int main() { int arr[] = { 1,2,3,4,5,6,7,8,9 }; FILE *pf = fopen("test.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; }

7.fread

//size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); //ptr:存放读取数据的内存缓冲区 //size:每个数据单元的字节数(如 sizeof(int)) //nmemb:要读取的数据单元个数 //stream:文件流指针(必须是文件,不能是 stdin) //返回值:实际成功读取的单元个数 int main() { int arr[9] = { 0 }; FILE* pf = fopen("test.txt", "rb"); if (pf == NULL) { perror("fopen"); return 1; } //读数据 int sz = sizeof(arr) / sizeof(arr[0]); fread(arr, sizeof(arr[0]), 9 ,pf);//以二进制的形式读取 for (int i = 0; i < 9; i++) { printf("%d", arr[i]); } fclose(pf); pf = NULL; return 0;

【补充】

特性文本形式(文本流)二进制形式(二进制流)
存储 / 传输内容字符编码(如 ASCII、UTF-8)存储,是人类可读的字符原始二进制字节存储,是机器可读的 0/1 序列
换行处理会做换行转换(如 Windows 下\n\r\n不做任何转换,原样读写
适用场景文本文件(.c.txt.md等源代码 / 文档)可执行文件、图片、音频、视频等
例子你写的这段 C 代码就是文本形式的源代码编译后生成的.exe/ 可执行文件是二进制形式

4-2 对⽐⼀组函数:scanf/fscanf/sscanf和printf/fprintf/sprintf

完整对比表格

功能输入函数输出函数来源/目标
标准流scanf(format, &var...)printf(format, var...)键盘 → 屏幕
文件流fscanf(fp, format, &var...)fprintf(fp, format, var...)文件/任意流
字符串sscanf(str, format, &var...)sprintf(str, format, var...)字符串 ← → 字符串
  1. scanf需要传入变量地址&),而printf族直接传值

  2. sprintf有安全风险(可能缓冲区溢出),推荐用snprintf

1. scanf / printf(标准输入输出)

c #include <stdio.h> int main() { int age; float height; char name[50]; // 从键盘读取 printf("请输入姓名、年龄、身高:"); scanf("%s %d %f", name, &age, &height); // 输出到屏幕 printf("--- 输出结果 ---\n"); printf("姓名:%s\n", name); printf("年龄:%d 岁\n", age); printf("身高:%.2f 米\n", height); return 0; } 输入示例:张三 25 1.75 输出: 姓名:张三 年龄:25 岁 身高:1.75 米

2. fscanf / fprintf(指定流:文件)

c #include <stdio.h> int main() { int id; float score; char name[50]; // 写入文件 FILE *fp = fopen("data.txt", "w+"); if (fp == NULL) { printf("文件打开失败\n"); return 1; } fprintf(fp, "1001 李四 88.5\n"); fprintf(fp, "1002 王五 92.0\n"); fprintf(fp, "1003 赵六 76.5\n"); // 将文件指针移回开头 rewind(fp); // 从文件读取 printf("--- 文件内容 ---\n"); while (fscanf(fp, "%d %s %f", &id, name, &score) == 3) { printf("学号:%d,姓名:%s,成绩:%.1f\n", id, name, score); } fclose(fp); return 0; } 输出: --- 文件内容 --- 学号:1001,姓名:李四,成绩:88.5 学号:1002,姓名:王五,成绩:92.0 学号:1003,姓名:赵六,成绩:76.5

3. sscanf / sprintf(字符串)​​​​​​​

c #include <stdio.h> int main() { char buffer[200]; int year, month, day; float price; char product[30]; // sprintf:将数据格式化写入字符串 sprintf(buffer, "商品:iPhone 15,价格:5999.00 元,日期:2024-12-25"); printf("拼接后的字符串:%s\n\n", buffer); // 模拟从某个文本行解析数据 char input[] = "2025-03-20 键盘 299.50"; sscanf(input, "%d-%d-%d %s %f", &year, &month, &day, product, &price); printf("--- 解析结果 ---\n"); printf("年份:%d\n", year); printf("月份:%d\n", month); printf("日期:%d\n", day); printf("商品:%s\n", product); printf("价格:%.2f 元\n", price); return 0; } 输出: 拼接后的字符串:商品:iPhone 15,价格:5999.00 元,日期:2024-12-25 --- 解析结果 --- 年份:2025 月份:3 日期:20 商品:键盘 价格:299.50 元

五.文件的随机读写

5.1 fseek

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

fseek() - 移动文件位置指针 int fseek(FILE *stream, long offset, int origin); 参数: stream:文件指针 offset:偏移量(字节数),正数向后、负数向前 origin:起始位置,取值: SEEK_SET (0) - 文件开头 SEEK_CUR (1) - 当前位置 SEEK_END (2) - 文件末尾 返回值:成功返回 0,失败返回非 0
//文件的随机读写(fseek) int main() { FILE* pf = fopen("test.txt", "r"); if (pf == NULL) { perror("fopen"); return 1; } //读文件 int ch = fgetc(pf); printf("%c\n", ch);//a //fseek(pf, 4, SEEK_CUR); //fseek(pf, 5, SEEK_SET); fseek(pf, -4, SEEK_END); ch = fgetc(pf); printf("%c\n", ch);//b fclose(pf); pf = NULL; return 0; }

5-2 ftell

ftell() - 获取当前文件位置 long ftell(FILE *stream); 返回值:当前文件位置指针距开头的字节数,失败返回 -1L
//ftell饭返回文件指针相对于起始位置的偏移量 int main() { FILE* pf = fopen("test.txt", "r"); if (pf == NULL) { perror("fopen"); return 1; } //读文件 int ch = fgetc(pf); printf("%c\n", ch);//a fseek(pf, 0, SEEK_END); printf("%d\n", ftell(pf)); fclose(pf); pf = NULL; return 0; }

5-3 rewind

rewind() - 重置到文件开头 void rewind(FILE *stream); 功能:将文件位置指针移回文件开头,同时清除文件错误和结束标志
//返回指针返回值 //rewind int main() { FILE* pf = fopen("test.txt", "r"); if (pf == NULL) { perror("fopen"); return 1; } //读文件 int ch = fgetc(pf); printf("%c\n", ch);//a fseek(pf, -4, SEEK_END); ch = fgetc(pf); printf("%c\n", ch);//A rewind(pf); ch = fgetc(pf); printf("%c\n", ch);//a fclose(pf); pf = NULL; return 0; }

三个函数的对比总结

函数功能返回值是否清除标志
fseek(fp, offset, origin)移动位置指针0=成功,非0=失败
ftell(fp)获取当前位置(距开头字节数)当前位置,-1=错误
rewind(fp)重置到文件开头无(void)

注意事项

  1. 文本文件模式("r"/"w") 下,fseek/ftell行为可能不可靠(尤其在Windows上换行符转换),建议二进制模式使用;

  2. fseek 到文件末尾后,继续写入会追加数据(扩展文件大小);

  3. ftell 返回 long 类型,在超大文件(>2GB)上可能溢出,64位系统用fgetpos/fsetpos;

  4. rewind等价于fseek(fp, 0L, SEEK_SET); clearerr(fp).


六.⽂件读取结束的判定

6.1 被错误使⽤的 feof

在⽂件读取过程中,不能⽤feof函数的返回值直接来判断⽂件的是否结束。
feof 的作⽤是:当⽂件读取结束的时候,判断是读取结束的原因是否是:遇到⽂件尾结束。

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


2. ⼆进制⽂件的读取结束判断,判断返回值是否⼩于实际要读的个数。
例如:
• fread判断返回值是否⼩于实际要读的个数。

文件读取结束判定 feof 文件读取结束原因。有可能遇见文件的末尾 feof 读取的时候发生了错误 ferror 打开一个流,就有两个标记值上面就是那两个具体值 int main() { FILE* pf = fopen("test.txt", "r"); if (pf == NULL) { perror("fopen"); return 1; } 读取 /*int ch = 0; while ((ch = fgetc(pf)) != EOF) { printf("%c\n", ch); }*/ 写 char ch =0; for (ch = 'a'; ch <= 'z'; ch++) { fputc(ch, pf); } //判断什么原因读取结束 if ((feof(pf))) { printf("遇到文件的末尾:读取正常结束\n"); } else if (ferror(pf)) { perror("fputc"); } fclose(pf); pf = NULL; return 0; }

七.⽂件缓冲区

ANSIC 标准采⽤“缓冲⽂件系统” 处理的数据⽂件的,所谓缓冲⽂件系统是指系统⾃动地在内存中为程序中每⼀个正在使⽤的⽂件开辟⼀块“⽂件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才⼀起送到磁盘上。如果从磁盘向计算机读⼊数据,则从磁盘⽂件中读取数据输⼊到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的⼤⼩根据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; }

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询