基础定义与陷阱
int ungetc(int c, FILE *stream);
- 核心功能:将字符c回退到输入流stream,后续读取顺序为后进先出
- 隐藏规则:
- 仅保证至少1次成功回退(C99标准)
- 多次回退后读取顺序:
- ungetc('A', fp); ungetc('B', fp); → 读取顺序为B→A
- 文件结束符EOF处理:调用ungetc后清除流的EOF状态
实战场景
场景1:动态类型解析器
void parse_value(FILE *fp) {
int c = fgetc(fp);
if (isdigit(c)) {
ungetc(c, fp); // 回退数字字符
int num;
fscanf(fp, "%d", &num);
printf("Parsed number: %d\n", num);
} else if (c == '"') {
parse_string(fp); // 进入字符串解析
} else {
ungetc(c, fp); // 回退未知字符
parse_identifier(fp);
}
}
设计亮点:通过预读+回退实现多类型解析分支
场景2:二进制流标记检测
int find_magic(FILE *fp, const char *magic) {
int match = 1;
long pos = ftell(fp);
for (const char *p = magic; *p; ++p) {
int c = fgetc(fp);
if (c != *p) {
match = 0;
// 关键点:精准回退已读取字节
fseek(fp, pos, SEEK_SET);
break;
}
}
return match;
}
对比优势:相比单纯使用ungetc,fseek+ftell组合更适合二进制流回溯
高级技巧:缓冲区冲突测试
char buf;
FILE *fp = fopen("test.txt", "r");
setvbuf(fp, buf, _IOFBF, sizeof(buf)); // 设置全缓冲
int c1 = fgetc(fp); // 触发缓冲填充
ungetc('X', fp); // 向缓冲区插入字符
// 验证缓冲区修改
printf("Current buffer: [");
for (int i=0; i<sizeof(buf); i++) {
printf("%c ", (buf[i] == 0) ? '_' : buf[i]);
}
printf("]\n");
运行结果:缓冲区首字符被修改为X,展示库函数内部缓冲区操作机制
流状态劫持:动态修改输入内容
// 运行时替换输入流中的敏感词
void filter_swear_words(FILE *fp) {
int c;
while ((c = fgetc(fp)) != EOF) {
if (c == 'F') { // 检测敏感词起始字符
ungetc('?', fp); // 替换为无害字符
ungetc('?', fp); // 双写保持字符数一致
break;
}
putchar(c);
}
}
独特价值:
实现「字符级输入流动态重写」,适用于实时过滤系统(如聊天室脏话过滤),相比传统先读后处理的方案节省内存。
跨平台差异警告表
行为 | Linux (glibc) | Windows (MSVCRT) |
最大回退次数 | 4 (默认缓冲大小) | 1 (严格遵守C99) |
回退EOF | 允许 | 导致未定义行为 |
混合fseek后的有效性 | 失效 | 部分版本保留回退字符 |