其他文件操作
本节收集一些在实际工程中很常用、但不适合放进“打开/读写/定位”主线叙事的 stdio.h 能力:状态与错误处理、缓冲控制、重定向、临时文件等。
1. EOF 与错误状态:feof / ferror / clearerr
对一个流进行读操作后,如果发现“没有读到想要的数据”,你需要区分两类情况:
- 到达文件末尾(EOF);
- 发生 I/O 错误。
标准库提供了两个“状态查询”接口:
#include <stdio.h>
int feof(FILE* stream);
int ferror(FILE* stream);2
3
4
以及一个“清除状态”接口:
#include <stdio.h>
void clearerr(FILE* stream);2
3
注意:feof / ferror 不会“预测”下一次读操作是否会失败,它们仅反映过去的 I/O 操作设置的状态。判断是否读到数据,应该先看读函数的返回值,再在需要时用 feof / ferror 解释失败原因。
2. 打印错误信息:perror
#include <stdio.h>
void perror(const char* s);2
3
当某个库函数失败后,perror 会把 s 与一条实现提供的错误信息输出到标准错误流(通常是 stderr)。它常用于调试与简单工具程序。
TIP
更精细的错误处理通常还会配合 <errno.h> 中的 errno(这是标准库的一部分)。但本章不把“错误码体系”作为主线展开,你只需知道:遇到 I/O 失败时,先检查返回值,再决定是否打印信息/退出/重试。
3. 缓冲与刷新:fflush / setvbuf / setbuf
3.1 fflush
#include <stdio.h>
int fflush(FILE* stream);2
3
对输出流调用 fflush(stream),会尝试把用户态缓冲中的数据写出;成功返回 0,失败返回 EOF。
标准还允许传入空指针:fflush(NULL) 会刷新所有输出流。
注意
对输入流调用 fflush 的行为在标准中没有定义。可移植代码不要这样写。
3.2 setvbuf / setbuf
你可以通过 setvbuf 控制一个流的缓冲方式:
#include <stdio.h>
int setvbuf(FILE* stream, char* buf, int mode, size_t size);
void setbuf(FILE* stream, char* buf);2
3
4
其中 mode 常用取值为:
_IONBF:无缓冲;_IOLBF:行缓冲;_IOFBF:全缓冲。
缓冲策略与性能/交互体验关系很大,但它们也很“实现相关”。对教程而言,你只需要掌握两条规则:
- 默认缓冲策略通常已经足够;
- 当你在交互式程序中希望提示信息立刻出现时,可以考虑
fflush(stdout)。
4. 重定向流:freopen
freopen 可以把一个已有的流“重新绑定”到一个新文件上(常见用途:把 stdin / stdout 重定向到文件)。
#include <stdio.h>
FILE* freopen(const char* filename, const char* mode, FILE* stream);2
3
成功返回 stream,失败返回空指针。
注意
freopen 的具体效果包含实现细节(例如失败时原流是否关闭等),写代码时应以“返回值检查”为核心:只要返回空指针,就把它当作重定向失败来处理。
5. 临时文件:tmpfile / tmpnam
#include <stdio.h>
FILE* tmpfile(void);
char* tmpnam(char* s);2
3
4
tmpfile 会创建一个临时二进制文件并以“更新模式”打开它;当该流关闭,或程序正常结束时,这个临时文件会被自动删除(具体细节有实现相关部分,但这是它的设计目标)。
tmpnam 返回一个可能可用于临时文件的名字。但它有明显缺陷:名称生成与实际创建不是原子操作,在并发/多进程环境中可能产生竞态;因此更推荐使用 tmpfile。
习题
写一个程序:从标准输入读入若干行,并写入到文件 out.txt;每写入一行,就立刻刷新输出。
要求:
- 使用
fgets读入一行; - 使用
fputs写入文件; - 每次写入后调用
fflush并检查其返回值。
写一个程序:打开一个文件并尝试读取其中所有字符;若读到 EOF,根据 feof / ferror 判断是“正常结束”还是“读错误”。
要求:
- 以读函数返回值作为循环条件;
- 循环结束后再使用
feof/ferror解释原因; - 对错误分支输出
perror信息到标准错误流。
写一个程序:把标准输出重定向到文件 log.txt,然后输出若干行信息,再把输出恢复到终端。
提示:本题允许你查阅 freopen 的文档;注意检查每一步的返回值。