分析
有多个问题:
fgetc() 返回 int,而不是 char;它必须返回每个有效的char 值加上一个单独的值 EOF。如前所述,您无法可靠地检测 EOF。如果char 是无符号类型,您将永远找不到EOF;如果 char 是有符号类型,您会将某些有效字符(通常是 ÿ、y-umlaut、U+00FF、带分音符号的拉丁小写字母 Y)误识别为 EOF。
如果在为更新模式打开的文件上切换输入和输出,必须在读写之间使用文件定位操作(fseek(),rewind(),名义上fsetpos());并且必须在读写之间使用定位操作或fflush()。
关闭您打开的内容是个好主意(现在已在代码中修复)。
如果您的写入有效,您将用a 覆盖i 之后的字符。
合成
这些变化导致:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE *ft;
char const *name = "abc.txt";
int ch;
ft = fopen(name, "r+");
if (ft == NULL)
{
fprintf(stderr, "cannot open target file %s\n", name);
exit(1);
}
while ((ch = fgetc(ft)) != EOF)
{
if (ch == 'i')
{
fseek(ft, -1, SEEK_CUR);
fputc('a',ft);
fseek(ft, 0, SEEK_CUR);
}
}
fclose(ft);
return 0;
}
还有更多的错误检查空间。
释经
输入后输出需要查找
fseek(ft, 0, SEEK_CUR); 语句是 C 标准所要求的。
ISO/IEC 9899:2011 §7.21.5.3 fopen 函数
¶7 当文件以更新模式打开时('+' 作为第二个或第三个字符
以上模式参数值列表),输入和输出都可以在
关联的流。 但是,输出后面不能直接跟没有输入的输入
干预对 fflush 函数或文件定位函数的调用 (fseek,
fsetpos, or rewind), 输入后面不能直接跟输出
干预对文件定位函数的调用,除非输入操作遇到 end-of-
使用更新模式打开(或创建)文本文件可能会打开(或创建)一个
一些实现中的二进制流。
(已添加重点。)
fgetc() 返回 int
引自 ISO/IEC 9899:2011,当前的 C 标准。
§7.21 输入/输出<stdio.h>
§7.21.1 简介
EOF 扩展为一个整数常量表达式,类型为 int 和一个负值,即
由几个函数返回以指示文件结束,即不再有来自
流;
§7.21.7.1 fgetc 函数
int fgetc(FILE *stream);
¶2 如果未设置 stream 指向的输入流的文件结束指示符并且
如果出现下一个字符,fgetc 函数会将该字符作为 unsigned char 转换为 int 并推进相关的文件位置指示符
流(如果已定义)。
退货
¶3 如果设置了流的文件结束指示符,或者如果流处于文件结束位置,则设置流的文件结束指示符并且fgetc 函数返回EOF。否则,该
fgetc 函数从流指向的输入流中返回下一个字符。
如果发生读取错误,则设置流的错误指示器并使用 fgetc 函数
返回 EOF。289)
289) 使用feof 和ferror 函数可以区分文件结束和读取错误。
所以,EOF 是一个负整数(通常是 -1,但标准不要求这样做)。 fgetc() 函数返回 EOF 或作为 unsigned char 的字符值(在 0..UCHAR_MAX 范围内,通常为 0..255)。
§6.2.5 类型
¶3 声明为 char 类型的对象大到足以存储基本的任何成员
执行字符集。如果基本执行字符集的成员存储在
char 对象,它的值保证为非负数。如果任何其他字符存储在
char 对象,结果值是实现定义的,但应在范围内
可以用该类型表示的值。
¶5 声明为 signed char 类型的对象占用与
“普通”char 对象。
§6 对于每个有符号整数类型,都有一个对应的(但不同的)无符号的
使用相同数量的整数类型(用关键字unsigned 指定)
存储(包括标志信息)并具有相同的对齐要求。
§15char、signed char、unsigned char这三种类型统称为
字符类型。实现应定义char 具有相同的范围,
表示和行为为signed char 或unsigned char。45)
45)CHAR_MIN,在<limits.h> 中定义,将具有0 或SCHAR_MIN 值之一,这可以是
用于区分这两个选项。无论做出何种选择,char 都是与
其他两个不兼容。
这证明了我的断言,即普通的 char 可以是有符号或无符号类型。
现在考虑:
char c = fgetc(fp);
if (c == EOF)
…
假设fgetc() 返回EOF,而普通char 是无符号(8 位)类型,EOF 是-1。该赋值将值 0xFF 放入 c,这是一个正整数。进行比较时,c 被提升为 int(并因此提升为值 255),并且 255 不是负数,因此比较失败。
相反,假设普通 char 是有符号(8 位)类型,字符集是 ISO 8859-15。如果fgetc()返回ÿ,则分配的值将是位模式0b11111111,与-1相同,所以在比较中,c将转换为-1,比较c == EOF将返回true即使读取了有效字符。
您可以调整细节,但基本参数在sizeof(char) < sizeof(int) 期间仍然有效。有些 DSP 芯片不适用;你必须重新考虑规则。即便如此,基本点仍然存在; fgetc() 返回 int,而不是 char。
如果您的数据是真正的 ASCII(7 位数据),那么所有字符都在 0..127 范围内,您不会遇到对 ÿ 问题的误解。但是,如果您的char 类型是无符号的,您仍然会遇到“无法检测到EOF”问题,因此您的程序将运行很长时间。如果你需要考虑可移植性,你会考虑到这一点。这些是您作为 C 程序员需要处理的专业级问题。您可以相对轻松地使用在您的系统上为您的数据工作的程序,而无需考虑所有这些细微差别。但是您的程序无法在其他人的系统上运行。