fgets()函数的行为、换行符以及其在内存中的存储方式。

3

我有下面这段代码,如果我输入 "Hi" 按下回车,则会在内存中存储 'H'、'i'、'\n'、'\0'

如果我输入 "Hello" 按下回车,则会在内存中存储 'H'、'e'、'l'、'l'、'\0',然后 'o'、'\n' 会在缓冲区中

最后,如果我输入 "Hell" 按下回车,则会在内存中存储 'H'、'e'、'l'、'l'、'\0',然后 '\n' 会在缓冲区中

char str [5];

fgets(str, 5, stdin);
printf("%s", str);

2
在C规范中明确提到,使用输入流(如stdin)调用fflush未定义行为。一些实现将其作为非可移植扩展添加。 - Some programmer dude
1
是的。在内存中将存储'H'、'i'、'\n'和'\0'。 - chux - Reinstate Monica
@chux-ReinstateMonica,Hell和Hello怎么样?我对它们的理解正确吗? - Janjan
1
是的,除了 fflush(stdin) 部分。是谁或什么文本建议使用 fflush(stdin) - chux - Reinstate Monica
1
很不幸,有些教授并不了解良好的编程实践。 - chux - Reinstate Monica
显示剩余6条评论
3个回答

3

fgets(buf,n,stream)读取输入并将其保存到buf中,直到以下4种情况之一发生。

  • 缓冲区快满了。 一旦读取并保存了n-1个字符,“\0”将被附加到buf。函数返回buf。可能还有剩余的字符需要从stream读取。1

  • stream读取了“\n”。 “\n”被附加到buf中。“\0”被附加到buf。函数返回buf。该已完全读取。

  • 出现文件结尾。如果之前已经读取了一些字符,则将“\0”附加到buf中。函数返回buf。否则返回NULL

  • 输入错误(罕见)。返回NULLbuf状态不确定。

读取'\n'与读取其他字符唯一的不同之处在于它通知fgets()停止读取。


1如果读取一个满的缓冲区而没有'\n',则读取和忽略其余的

int ch;
while ((ch = fgetc(stream)) != '\n' && c != EOF) {
  ;
}

1
@Janjan,“换行符替换为空字节”是不正确的。请参见上面的“The only thing different about reading '\n'....”。回车键生成'\n'-这也是一个字符。'\n'是你问题中的“n-1个字符”之一吗? - chux - Reinstate Monica
2
@Janjan 那个评论是不正确的。使用短缓冲区时,就好像 \n 被替换为 \0,\n 仍在流中,但更好的看法是超过 n-1 的字符没有被读取(包括 \n),然后会追加一个 \0,fgets() 就像本回答的第一条提到的那样返回。 - chux - Reinstate Monica
1
看起来正确。第一个 fgets(buf, 5, stdin) 读取了前4个字符 H,e,l,l 和 \n。\n 仍留在 stdin 中。第二个 fgets(buf, 5, stdin) 读取了 \n 并停止。 - chux - Reinstate Monica
2
@anastaciu 在第一种情况下,'\n' 仍然留在 stdin 中。也许可以这样解释:fgets 函数从指向流的指针 stream 指向的流中最多读取 n-1 个字符到指向数组 s 的指针中。在新行字符(保留)或文件结束后,不会再读取任何其他字符。空字符将立即写入到最后一个读取的字符之后的数组中。C17 § 7.21.7.2 2 - chux - Reinstate Monica
1
@chux-ReinstateMonica,感谢您的回复,这解释了很多问题。 - anastaciu
显示剩余11条评论

2

给定fgets()的定义:

char *fgets( char *str, int count, FILE *stream ); (直到 C99)

char *fgets( char *restrict str, int count, FILE *restrict stream ); (自 C99 起)

从给定的文件流中最多读取 count - 1 个字符,并将它们存储在由 str 指向的字符数组中。如果找到一个 newline 字符,则解析停止,此时 str 将包含该 newline 字符;或者如果发生了 end-of-file,则解析停止。如果读取了字节并且没有发生错误,则在写入到 str 的最后一个字符的位置立即写入一个 null character

如果 count 小于 1,则行为未定义。也未指定是否在 count==1 时写入 null 字符。

fgets 会将输入行中的最大长度(大小)置为5,并将其与 null 终止符一起存储在 str 变量中。

由于您提供的大小是5,如果您输入了 "Hello",它将存储为 H e l l \0,替换掉了 'o''o' 不被存储,而且通常情况下,'o''\n' 将继续留在 stdin 缓冲区中,尽管这不是标准规定的。

如果您输入了 "Hell",则 stdin 缓冲区将有 H e l l \n,因此当其被存储时,'\n' 将被替换为 '\0',而'\n' 将留在缓冲区中。

同样,如果行比 5 - 1 小,则不替换任何内容,char 数组 将被空终止,即 "Hel" 将被存储为 H e l \n \0,而stdin 缓冲区将被清空。

这就是为什么你通常在声明char array时要比实际预期的最大大小多一个字符,并且将其大小传递给fgets函数的原因。
fgets(str, sizeof(str), stdin);

请注意不要使用fflush(stdin)


1
是的,当您输入“Hell”并按下回车键时,缓冲区将包含“H e l l \n”,因此在存储时,'\n'将被替换为'\0'。 - anastaciu
1
@Janjan,不会的,只有当字符串大于5时才会发生这种情况。如果您输入“Hello”,则“o”将被替换,而“\n”将保留在缓冲区中。 - anastaciu
1
据我所知,Samuel Peter是正确的,标准并没有强制要求,但这通常是发生的情况。 - anastaciu
1
@Janjan 是的,就是这样。 - anastaciu
1
非常感谢,这对我们帮助很大 :) - Janjan
显示剩余8条评论

1

从我的机器上的 man fgets 中得知:

char * fgets(char * restrict str, int size, FILE * restrict stream);

fgets() 函数从给定的 stream 中最多读取 size 个字符减一,并将它们存储在字符串 str 中。当发现换行符、文件末尾或错误时,读取就会停止。如果有任何换行符,则保留该换行符。如果读取了任何字符且没有错误,则会附加一个 `\0' 字符来结束字符串。

这意味着你关于将存储在 str 缓冲区中的内容是正确的。未读取的字符可能可以通过后续从标准输入流读取来读取,但这将取决于操作系统和你正在读取的位置。 C 标准并不强制要求。


换行符和它在内存中的存储方式怎么样?我的理解正确吗? - Janjan

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