对你们大多数人来说,这似乎是一个愚蠢的问题,但我仍然在努力确定最终答案。几个小时前,我决定用fgets()替换项目中的所有scanf()函数,以获得更强大的代码。 我了解到fgets()自动结束插入的输入字符串,其中包含'\ n'和NUL字符,但是.. 让我们说我有这样的事情:
char user[16];
一个包含16个字符的数组,用于存储用户名(最多15个字符,我为NUL终结符保留最后一个)。 问题是:如果我插入一个15个字符的字符串,那么'\ n'将最终出现在数组的最后一个单元格中,但是NUL终止符呢? '\ 0'是否存储在以下内存块中? (调用printf()函数时没有分段错误意味着插入的字符串实际上是NUL终止的,对吧?)。
答案 0 :(得分:6)
作为5gon12eder答案的补充。我假设你有类似的东西:
char user[16];
fgets(user, 16, stdin);
,您的输入为abcdefghijklmno\n
,即15个字符和换行符。
fgets
将输入user
输入的15(16-1)个第一个字符后跟一个空格,您将有效地获得"abcdefghijklmno"
,这就是您想要的1} p>
但是...... \n
仍然保留在流缓冲区中,实际上可以在同一个文件上进行下一次读取(可以是fgets
或其他任何内容)。更确切地说,在您执行另一个fgets
之前,您无法知道o
之后是否还有其他字符。
答案 1 :(得分:5)
正如@ 5gon12eder建议的那样,使用:
char user[16];
fgets(user, sizeof user, stdin);
// Function prototype for reference
#include <stdio.h>
char *fgets(char * restrict s, int n, FILE * restrict stream);
现在了解详情:
自动附加'\n'
和'\0'
不。仅自动附加'\0'
。 fgets()
一旦获得'\n'
就会停止阅读,但由于其他原因也会停止,包括完整的缓冲区。在这些情况下,'\n'
之前没有'\0'
。
fgets()
不读取C字符串,而是读取一行。输入流通常处于文本模式,然后发生行尾转换。在某些系统上,'\r'
,'\n'
对将转换为'\n'
。在其他人,它不会。通常,正在读取的文件与此转换匹配,但会发生异常。在二进制模式下,不会发生翻译。
fgets()
读入'\0'
。并继续阅读。因此,使用strlen(buf)
并不总是反映char
读取的真实数量。当char
位于中间位置时,可能会有一个完整的方法来确定'\0'
读取的真实数量,但使用fread()
或fgetc()
进行编码可能更容易
在EOF条件(并且没有数据读取)或IO错误时,fgets()
返回NULL。发生I / O错误时,未定义缓冲区的内容。
迂腐问题:C标准使用int
类型作为缓冲区的大小,但通常代码传递类型为size_t
的变量。大小n
小于1或大于INT_MAX
可能是个问题。大小为1 应该只能填充buf[0] = '\0'
,但有些系统的行为会有所不同,尤其是在EOF条件接近或通过时。但只要2 <= n <= INT_MAX
,就可以预期终止'\0'
。注意:fgets()
可能会在尺寸过小时返回NULL
。
代码通常喜欢使用可能导致问题的内容删除终止'\n'
。建议:
char buf[80];
if (fgets(buf, sizeof buf, stdin) == NULL) Handle_IOError_or_EOF();
// IMO potential UB and undesired behavior
// buf[strlen(buf)-1] = '\0';
// Suggested end-of-line deleter
size_t len = strlen(buf);
if (len > 0 && buf[len - 1] == '\n') buf[--len] = '\0';
强大的代码会检查fgets()
的返回值。以下方法有缺点。 1)如果发生IO错误,则不定义缓冲区内容。检查缓冲区内容不会提供可靠的结果。 2)'\0'
可能是第一次char
读取且文件未处于EOF状态。
// Following is weak code.
buf[0] = '\0';
fgets(buf, sizeof buf, stdin);
if (strlen(buf) == 0) Handle_EOF();
// Robust, but too much for code snippets
if (fgets(buf, sizeof buf, stdin) == NULL) {
if (ferror(stdin)) Handle_IOError();
else if (feof(stdin)) Handle_EOF();
else if (sizeof buf <= 1) Handle_too_small_buffer(); // pedantic check
else Hmmmmmmm();
}
答案 2 :(得分:4)
C99标准(N1256)中fgets
的文档
7.19.7.2 fgets功能
<强>概要强>
#include <stdio.h>
char *fgets(char * restrict s, int n,
FILE * restrict stream);
<强>描述强>
fgets
函数最多读取的数字少于n
指定的字符数 从stream
指向的流进入s
指向的数组。没有额外的 在换行符(保留)或文件结束后读取字符。一个 在读入数组的最后一个字符后立即写入空字符。
来到你的岗位,你说:
一个包含16个字符的数组,用于存储用户名(最多15个字符,我为NUL终结符保留最后一个)。问题是:如果我插入一个15个字符的字符串,那么'\ n'将最终出现在数组的最后一个单元格中,但是NUL终结符呢?
对于这种情况,在下次调用fgets
或从流中读取任何其他调用之前,不会读取换行符。
'\ 0'是否存储在以下内存块中? (调用printf()函数时没有分段错误意味着插入的字符串实际上是NUL终止的,对吧?)。
始终设置终止空字符。在您的情况下,第16个字符将是终止空字符。
答案 3 :(得分:2)
来自fgets
的{{3}}:
char *fgets(char *s, int size, FILE *stream);
fgets()
最多从 stream 中读取一个 size 字符,并将它们存储到 s 指向的缓冲区中。读数在EOF或换行符后停止。如果读取换行符,则将其存储到缓冲区中。终止空字节('\0'
)存储在缓冲区中的最后一个字符之后。
我认为这很清楚,不是吗?