一、文件概述:到底什么是文件?
说实话,很多初学者学C语言时,对「文件」这个概念的理解就是——硬盘上存东西的那个玩意儿。嗯,这么说也没错,但不够准确。
我个人的理解是:文件就是一组相关数据的集合,它存储在外部介质上,比如硬盘、U盘、SD卡。在C语言的世界里,我们把文件当作一个数据流来处理。你往里面写数据,或者从里面读数据,就这么简单。
但这里有个关键点:C语言把文件看作是一个字节序列。不管你存的是整数、浮点数还是字符串,到了文件里,最终都是一堆字节。这个观念很重要,后面讲读写操作时你会体会到。
核心概念:在C语言中,文件操作的本质就是「字节流的输入与输出」。你操作的不是文件本身,而是文件对应的流(stream)。
1.1 文本文件 vs 二进制文件
这是很多新手容易搞混的地方。我当年刚入行时也踩过坑,有一次在嵌入式项目里用文本方式去读一个二进制配置文件,结果读出来全是乱码……嗯,从那以后我就再也不敢搞混了。
咱们直接看对比:
| 对比项 | 文本文件 | 二进制文件 |
|---|---|---|
| 存储方式 | 以字符形式存储,每个字节对应一个字符 | 以内存中的原样存储,字节就是字节 |
| 可读性 | 用记事本打开能看懂 | 用记事本打开是乱码 |
| 换行处理 | 不同平台有差异(Windows是\r\n,Linux是\n) | 不做任何转换,原样读写 |
| 典型用途 | 配置文件、日志文件、源代码 | 可执行文件、图片、音频、固件 |
| C语言打开方式 | "r"、"w"、"a" |
"rb"、"wb"、"ab" |
说白了,区别就一句话:文本文件存的是「人能看懂的字符」,二进制文件存的是「机器能看懂的原样数据」。
我的建议:如果你要存的是结构体、数组这类二进制数据,请务必用二进制模式。否则数据在读写过程中会被「篡改」——比如换行符被转换,那你的数据就全乱了。
1.2 文件缓冲区——为什么要有它?
你想想看,如果每次读写一个字节都要去访问硬盘,那效率得多低?硬盘的读写速度比内存慢好几个数量级。所以C语言引入了文件缓冲区的概念。
缓冲区,说白了就是一块内存区域。数据先攒到缓冲区里,攒够了再一次性写入磁盘。读的时候也一样,一次性读一大块到缓冲区,然后程序从缓冲区里慢慢取。
我举个例子你就明白了:
// 没有缓冲区的话,每次fputc都要写磁盘
// 有缓冲区的话,数据先攒到内存里
FILE *fp = fopen("test.txt", "w");
fputc('A', fp); // 这行代码执行后,'A'可能还在缓冲区里
fputc('B', fp); // 'B'也还在缓冲区
fclose(fp); // 关闭文件时,缓冲区里的数据才真正写入磁盘
注意:如果你在写文件过程中程序崩溃了,缓冲区里的数据可能会丢失!这就是为什么我建议在关键位置手动调用fflush()强制刷新缓冲区。
我曾经在一个数据采集项目中遇到过这个问题:采集器每秒钟采集1000个数据点,写入SD卡。一开始没注意缓冲区,结果有一次掉电,最后几秒的数据全丢了。后来我在每次写完一批数据后加了一句fflush(fp),问题就解决了。
1.3 文件缓冲区的三种模式
C语言提供了三种缓冲模式,你可以根据需求选择:
- 全缓冲:缓冲区满了才刷新。默认模式,适合读写大文件。
- 行缓冲:遇到换行符就刷新。标准输出(stdout)就是行缓冲,所以你用printf打印时,加上\n才会立即显示。
- 无缓冲:每次读写都直接操作设备。标准错误(stderr)就是无缓冲,保证错误信息能立即输出。
你可以用setvbuf()函数来设置缓冲模式:
FILE *fp = fopen("data.bin", "wb");
char buffer[4096];
setvbuf(fp, buffer, _IOFBF, sizeof(buffer)); // 设置全缓冲,缓冲区大小4KB
知识体系总览
下面这张图帮你理清本章的核心逻辑:
小结
这一章我们聊了三个核心概念:
- 文件——说白了就是一组字节的集合,C语言把它当作流来处理。
- 文本文件 vs 二进制文件——区别在于是否对数据做「翻译」处理。文本文件会处理换行符,二进制文件原样读写。
- 文件缓冲区——为了提升性能而引入的内存区域。理解缓冲模式,能帮你写出更健壮的程序。
我个人觉得,理解文件缓冲区是这章的重中之重。很多隐蔽的bug都跟缓冲区有关——数据没写完就断电、读写位置不对、换行符被意外转换……这些都是我实际项目中踩过的坑。
一个小技巧:在嵌入式开发中,如果文件操作涉及关键数据(比如配置参数、日志记录),我习惯在每次写操作后调用fflush(),并且用fsync()(Linux下)确保数据真正落盘。虽然会牺牲一点性能,但换来的是数据安全。
公众号:蓝海资料掘金营,微信deep3321