这一章在干嘛?

stdio 的体系:错误报告与终止、流的概念与 fopen/fclose、字符与行 I/O、scanf/printf 的格式暗号、二进制 I/O 与随机定位。学会本章,程序就能与文件世界打交道。

15.1 错误报告与终止执行

15.2 流:打开、关闭与三种标准流

15.3 字符与行 I/O

15.4 格式化 I/O:scanf / printf 的暗号表

15.5 二进制 I/O 与随机定位

15.1 错误报告与终止执行

#include <stdio.h>
#include <stdlib.h>

FILE *fp = fopen("data.txt", "r");
if (fp == NULL) {
    perror("data.txt");     /* 打印 "data.txt: <errno描述>",自动带上原因 */
    exit(EXIT_FAILURE);     /* 立即终止;EXIT_SUCCESS 表示成功 */
}

perror 把「你给的前缀 + 全局 errno 对应的系统错误描述」一行打出来;exit 直接结束进程并刷新所有打开的流。错误处理的原则:每个可能失败的调用都检查,失败就带上下文报告——「函数名+文件名+errno」三件套让排错事半功倍。

15.2 流:打开、关闭与三种标准流

**流(stream)*是对「数据源/目的地」的抽象:文件、终端、管道在程序眼里都是 FILE。打开时选模式:

模式含义文件不存在已存在
”r”只读失败 NULL从头读
”w”只写新建清空!
“a”追加新建从尾写
”r+“读+写失败 NULL从头
”w+b” 等加 b二进制模式不换行翻译

程序启动自带三个流:stdin(标准输入)、stdout(标准输出)、stderr(标准错误),不用 fopen 就能用。stderr 不缓冲,错误信息优先于 stdout 显示——报告错误请用它。

fclose 别忘配对:

每个成功 fopen 都要 fclose,否则文件句柄泄漏。fclose 也会把缓冲区写完,关之前别丢 FILE*。

15.3 字符与行 I/O

家族撤销
字符fgetc(fp) / getc / getcharfputc(ch, fp) / putcharungetc(ch, fp)
fgets(buf, n, fp) / gets(禁用!无边界fputs(line, fp) / puts
/* 字符级复制:EOF 是「流结束」信号,类型必须是 int! */
int ch;
while ((ch = fgetc(src)) != EOF)
    fputc(ch, dst);

/* 行级读取的安全姿势:fgets 自带截断 + 保留换行 */
char line[256];
while (fgets(line, sizeof line, fp) != NULL) {
    line[strcspn(line, "\n")] = '\0';   /* 去掉末尾换行(若有) */
    process(line);
}

为什么 ch 用 int:

fgetc 返回「字符值或 EOF」。char 若无符号将永远等于不了 EOF(-1);用 int 接收才能正确区分「数据 0xFF」和「文件结束」。

15.4 格式化 I/O:scanf / printf 的暗号表

转换符printf 输出scanf 读取
%d / %u有符号 / 无符号十进制int* / unsigned*
%x / %o十六进制 / 八进制同上
%c / %s字符 / 字符串char*(s 要留 \0 空间!)
%f %e %g浮点三种风格float*;double 用 %lf
%ld %lld %zulong / long long / size_t对应指针类型
%p %%指针地址 / 百分号本身void** / —

printf 修饰符:%5d(宽度5右对齐)、%-5d(左对齐)、%05d(补零)、%.2f(两位小数)、%*d(宽度由参数给)——第 1 章程序里的 %.*s(精度取自参数的字符串截取)就是这套体系。

scanf 三要害:① 参数必须是指针(&n,字符串数组名本来就退化成指针不用 &);② %s 停在空白且不限长(用 %63s 指定域宽防溢出);③ 返回成功匹配的项目数,用它判断输入是否合法:

int n;
if (scanf("%d", &n) != 1) {     /* 输入 abc 时返回 0 */
    fprintf(stderr, "不是数字\n");
    exit(EXIT_FAILURE);
}

15.5 二进制 I/O 与随机定位

struct Rec { int id; double score; };
struct Rec r = { 7, 93.5 };

FILE *fp = fopen("rec.bin", "wb");           /* 二进制模式 */
fwrite(&r, sizeof r, 1, fp);                 /* 整块写入 */
fclose(fp);

fp = fopen("rec.bin", "rb");
fseek(fp, 1 * sizeof(struct Rec), SEEK_SET); /* 跳到第 2 条记录 */
fread(&r, sizeof r, 1, fp);                  /* 随机读取 */
long pos = ftell(fp);                        /* 当前偏移 */
rewind(fp);                                  /* 回到开头 */
fclose(fp);

文本 vs 二进制的取舍:文本可读、可移植、易调试,但要转换且占空间;二进制紧凑、免转换、可随机访问,但受字节序/结构填充影响,跨平台要小心。结构体数组、帧数据用二进制整块读写最快。

本章通关标准:

fopen 之后条件反射判 NULL;fgets + strcspn 去 \n 的模板随手能写;scanf 返回值必检查;理解 fseek 三种基准(SEEK_SET/CUR/END)。