fgets函数的回车符处理方式

9
我正在运行以下代码:
#include<stdio.h>
#include<string.h>
#include<io.h>

int main(){
    FILE *fp;
    if((fp=fopen("test.txt","r"))==NULL){
        printf("File can't be read\n");
        exit(1);
    }
    char str[50];
    fgets(str,50,fp);
    printf("%s",str);
    return 0;
}

text.txt 包含: I am a boy\r\n

由于我在使用Windows系统,它将 \r\n 视为换行符,所以如果我从文件中读取这个字符串,应该将"I am a boy\n\0"存储到str中,但是实际上我得到的是"I am a boy\r\n"。我正在使用mingw编译器。


你是如何确定 str 的内容的?你的程序似乎从未调查过它。 - Kerrek SB
通过打印,它不应该显示 '\r',对吧? - Vaibhav Agarwal
1
fgets将包括换行符和空终止符,因此您得到的输出是完全正确的。 - netcoder
@netcoder但是我也得到了回车,而我不应该得到。 - Vaibhav Agarwal
不,你应该得到回车符,这是正确的行为。"它将\r\n视为换行符"是不正确的,它将\n视为换行符。 - cdarke
3个回答

10
行为取决于C库的实现方式以及您向fopen传递的模式。请参考MSDN关于fopen的文档中的以下引用:(MSDN上的fopen)

b - 以二进制(未翻译)模式打开,不进行包括回车和换行符在内的转换。

这意味着,如果您使用Microsoft C库并打开文件时省略了“b”,则回车符将从流中删除。
由于您正在使用mingw,您的编译器可能链接到遵循POSIX标准的GNU C库。这是GNU文档(gnu.org上的fopen)中对fopen的解释:

opentype中的字符“b”具有标准含义;它请求二进制流而不是文本流。但在POSIX系统(包括GNU系统)中没有区别。

总之,您省略了“b”模式字符,以文本模式打开流。尽管您在Windows上工作,但使用的是不区分文本和二进制模式的GNU C库。这就是为什么fgets读取回车符和换行符的原因。

5
由于我使用的是Windows系统,所以我把\r\n当作一个新行字符...
这个假设是错误的。C标准将回车和换行视为两个不同的字符,如C99 §5.2.1/3(字符集)所示:
[...] 在基本执行字符集中,应该有代表警报、退格、回车和换行的控制字符。[...]
在C99 §7.19.7.2/2中,fgets函数的描述如下:
fgets函数从指向流的指针读取最多比n少一个字符的内容,并将其写入指向数组的指针s中。在读取到一个新行字符(保留该字符)或文件结尾之后,不会再读取任何其他字符。空字符会被立即写入到数组中读取的最后一个字符之后。
因此,当遇到字符串"I am a boy\r\n"时,符合规范的实现应该读取到\n字符。没有任何可能合理的原因可以让实现根据平台丢弃\r。

好的,谢谢@netcoder。我读到Windows把\r\n作为一个新行字符,所以我有点困惑。 - Vaibhav Agarwal

1
C标准对文本流有以下规定(以及其他内容):
字符可能需要在输入和输出时添加、更改或删除,以符合主机环境中表示文本的不同约定。因此,在流中的字符与外部表示中的字符之间可能没有一一对应关系。从文本流中读取的数据只有在满足以下条件时才能与先前写入该流的数据相等:数据仅由打印字符和控制字符水平制表符和换行符组成;没有换行符直接前面是空格字符;最后一个字符是换行符。
换句话说,如果以文本模式打开文件,则实现可以自由地添加、删除和修改控制字符,以便从磁盘读取和写入。这似乎是微软实现使用回车符进行的,但GNU实现则不会。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接