13.3 无格式/有格式输入输出
本节把 <stdio.h> 的 I/O 分成两类:
- 无格式 I/O:按字符或按字节块读写,不进行解析与格式化;
- 有格式 I/O:按格式字符串解析/生成文本(例如
printf/scanf家族)。
1. 无格式 I/O
1.1 字符 I/O:fgetc / fputc
c
#include <stdio.h>
int fgetc(FILE* stream);
int fputc(int ch, FILE* stream);1
2
3
4
2
3
4
它们以 int 形式返回读到的字符(或写入结果),并使用 EOF 表示失败或文件结束(对读操作)。
标准输入输出的便捷版本是:
c
int getchar(void); /* 等价于 fgetc(stdin) */
int putchar(int ch);/* 等价于 fputc(ch, stdout) */1
2
2
这里只列出函数原型,没有函数调用,因此不会产生运行期输出。
1.2 行 I/O:fgets / fputs
c
#include <stdio.h>
char* fgets(char* s, int n, FILE* stream);
int fputs(const char* s, FILE* stream);1
2
3
4
2
3
4
这里只列出函数原型,没有函数调用,因此不会产生运行期输出。
fgets 最多读入 n - 1 个字符,并保证以空字符终止;若读取失败返回空指针。
1.3 块 I/O:fread / fwrite
c
#include <stdio.h>
size_t fread(void* ptr, size_t size, size_t nmemb, FILE* stream);
size_t fwrite(const void* ptr, size_t size, size_t nmemb, FILE* stream);1
2
3
4
2
3
4
它们以“元素个数”为单位返回成功读写的元素数量。若返回值小于 nmemb,需要结合 feof / ferror 判断是文件结束还是错误(见 13.5)。
2. 有格式 I/O
2.1 printf 家族(输出)
c
#include <stdio.h>
int printf(const char* format, ...);
int fprintf(FILE* stream, const char* format, ...);
int snprintf(char* s, size_t n, const char* format, ...);1
2
3
4
5
2
3
4
5
这里只列出函数原型,没有函数调用,因此不会产生运行期输出。
它们返回输出的字符数(失败时返回负值)。
snprintf 的位置
snprintf 是把格式化结果写入字符数组的接口。为了避免写越界,应优先使用 snprintf,并检查返回值。
2.2 scanf 家族(输入)
c
#include <stdio.h>
int scanf(const char* format, ...);
int fscanf(FILE* stream, const char* format, ...);
int sscanf(const char* s, const char* format, ...);1
2
3
4
5
2
3
4
5
它们返回成功匹配并赋值的项数;若在任何赋值发生之前就到达文件结束,返回 EOF。
2.3 最常见的风险点
%s默认读入到空白符为止,且不会自动检查目标数组大小,容易写越界。scanf的返回值必须检查,否则输入失败时对象会保留旧值,导致后续逻辑错误。
示例:为 %s 指定最大宽度:
c
char name[16];
if (scanf("%15s", name) != 1) {
return 1;
}1
2
3
4
2
3
4
3. 习题
#11306
⚡4⏳3
写一个程序:逐行读取标准输入,并在每行前输出行号(从 1 开始)。
要求:
- 使用
fgets; - 正确处理超长行(即一次
fgets读不完一行的情况)。
#11307
⚡5⏳3
写一个程序:从标准输入读入若干个 int,计算它们的和并输出。
要求:
- 使用
scanf; - 正确处理“输入到达 EOF”与“输入格式错误”两种结束条件。
#11308
⚡5⏳4选做
实现能够读取任意可表示长度文本行的函数:
c
#include <stddef.h>
#include <stdio.h>
typedef enum {
READ_LINE_OK,
READ_LINE_END,
READ_LINE_IO_ERROR,
READ_LINE_NO_MEMORY,
READ_LINE_INVALID
} ReadLineResult;
ReadLineResult read_line(
FILE *stream,
char **out_line,
size_t *out_length
);1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
要求:
- 从流的当前位置读取一行,消费行末的换行符,但不把它放入结果。
- 成功时返回以空字符结尾的动态字符数组,并通过
out_length返回换行符之前的字节数;结果中原有的零字节由长度字段区分。 - 空行也返回
READ_LINE_OK。尚未读取任何字节便遇到文件末尾时返回READ_LINE_END。 - 文件末尾前最后一行没有换行符时,仍应成功返回该行。
- 参数无效、读取失败、长度无法表示或分配失败时释放本次调用申请的存储,并保持两个输出对象不变。
- 调用方拥有成功返回的存储,并负责用
free释放。 - 不得假设流支持定位;存储容量采用几何增长。