scanf(" %[^\n]",line);
我的一个朋友建议,使用fgets()
读取一行作为输入会比使用scanf()
上述语句中的 as 更好。他有道理吗?
char * fgets ( char * str, int num, FILE * stream );
使用安全,因为它避免了缓冲区溢出问题,它只扫描num-1
字符数。
从流中读取字符并将它们作为 C 字符串存储到 str 中,直到读取 (num-1) 个字符或到达换行符或文件结尾,以先发生者为准。
这里第二个参数num
是要复制到 str 中的最大字符数(包括终止的空字符)。
例如,假设在您的代码中,字符串数组的容量只是5
字符长,如下所示。
char str[5];
fgets (str, 5, fp); //5 =you have provision to avoid buffer overrun
使用上面的代码,如果输入 from 比字符fp
长4
,fgets()
将只读取第一个4
字符然后附加\0
( ,并丢弃其他额外的输入字符,只在 中存储五个字符str[]
)。
而scanf(" %[^\n]",str);
将读取直到\n
找不到,并且如果输入字符串较长,则4
charsscanf()
将导致缓冲区溢出(因为scanf
将尝试访问超出最大索引4
in 的内存str[]
)。
C FAQ有一些关于scanf
's 问题的详细解释:
更一般地说,
scanf
它是为相对结构化的格式化输入而设计的(它的名字实际上来源于“扫描格式化”)。如果你注意,它会告诉你它是成功还是失败,但它只能告诉你它失败的大致位置,而不是如何或为什么失败。您几乎没有机会进行任何错误恢复。
请参阅此处了解详细信息。
fgets
会比这更好scanf
。scanf
OP中给出的可能存在以下问题
1)@Grijesh 建议的缓冲区溢出
2)可能scanf
在这之后的下一个将不起作用,因为换行符留在输入流中。(如果你错过了一个空格)
简单地说:是的,fgets
是更好的选择。
我看了你的scanf
格式说明符,我很困惑。准确理解它的作用需要一些时间阅读这些man
页面。
此外,您的scanf
代码容易受到缓冲区溢出的影响。
保持简单,您将降低维护成本并避免难以发现的错误!
是的 fgets 是从标准输入读取一行的更好和安全的方法。
此外,代码将具有更高的可读性。看你给的scanf语句。
任何第二个人看到它都会彻底糊涂。但是 fgets 会有更多的可读性,而且很容易理解。
使用 scanf() 读取一行不好?
主要的反对意见scanf(some_format, buffer)
是缺乏缓冲区溢出保护,如
scanf(" %[^\n]",line); // Bad - no buffer overflow protection.
另一种方法可以使用
char buffer[100];
scanf(" %99[^\n]",line); // a little better
然而,它可以读取多条前导行(如果仅由空白组成),删除前导空白,缺乏处理非常量缓冲区宽度的简单方法,并且它将行的其余部分(可能只有'\n'
. stdin
如果第一个字符是 ,则不会立即返回'\n'
。
fgets()
更好。
但它仍然存在问题:超过(示例)99 的行仍然存在,并且很难检测到stdin
读取嵌入的空字符。
char buffer[100];
if (fgets(buffer, sizeof buffer, stdin)) {
buffer[strcspn(buffer, "\n")] = '\0'; // Lop off potential tailing \n
代码可以根据需要使用fgetc()
和制作代码来处理所有情况。然而,一次读取 1 个字符会导致显着的性能开销。
非标准 C 库getline()
相当流行。
一个缺点:它确实允许用户导致代码消耗过多的资源:过长的行会分配大量的内存。
char *line = NULL;
size_t len = 0;
ssize_t nread;
while ((nread = getline(&line, &len, stdin)) != -1) {
C 标准库缺少强大的get-line函数。最接近的是fgets()
.
scanf()
IMO,在你知道它为什么不好之前不要使用。
好吧,恕我直言,这是因为scanf
它不会强迫您限制输入大小,但fgets
确实如此。
如果您阅读了文档并scanf
正确使用,那么这里确实没有太大区别:
char line[256];
scanf("%255[^\n]%*c",line); // %*c to remove trailing \n
fgets(line, 256, stdin)
请注意,我从scanf
格式字符串中删除了前导空格。
我稍后会回到这个。
这两种情况都确保我们不会阅读更多内容。
但是,从安全的角度来看,我们需要考虑:
fgets
强制你指定尺寸scanf
你需要记住设置 array_capacity -
1fgets
为您执行此操作,因此您实际上传递了容量(或更少)很容易忘记 scanf 的细节,当你与一个拥有不同编程背景的庞大团队打交道时,有些人可能会更难以编写包含这些细节的代码,甚至可能不理解格式字符串。所以总的来说使用fgets
比较安全。
现在,关于我删除的前导空格。
当您使用 时fgets
,您将无法whitespace
在输入前忽略字符,因此我不得不删除该空格以使两个调用具有几乎相同的结果。
我想我们不能真的说一种方式比另一种方式“更好”,只有这种fgets
方式更具可读性,并且可以确保您记住传递大小。您还可以通过将scanf
调用封装到正确构建格式字符串的输入读取函数中来实现这一点。这也可以让您whitespace
在阅读前跳过前导字符。
编辑很清楚,我的观点是,从安全角度来看(如“不在字符数组之外写入”),这两种解决方案都是有效的,因为您可以限制在它们上读取的字符数。
我显示的代码纯粹是为了表明您可以将其限制为一定的大小,而不是它们具有完全相同的效果。
正如Andrew Henle所说,%*c
如果用户提供的字符多于我们说要读取的长度,则确实会丢弃一个输入字符。
但是话又说回来,这不是我的意思,也不是问题,恕我直言。我只是把它放在那里更接近什么fgets
,因为如果输入不大于您尝试读取的数量,则会从缓冲区fgets
中删除。\n
据我所知,这个问题是关于scanf
vsfgets
的,并没有特别的意图。
至少有一个问题没有被描述。
当然,您需要考虑许多额外的事情,具体取决于应用程序需要执行的操作。
一些注意事项fgets
:
size - 1
,\n
将留在缓冲区中< size
,\n
将被插入到字符串> size
,其余字符将留在缓冲区一些注意事项scanf
:
%*c
安全地执行此操作%*c
,但在读取字符时在格式字符串上有一个前导空格,因为您已经在问题的代码上使用了)capacity - 1
宽度说明符width
符%s
,更复杂的格式说明符%d
,因此团队中的其他人可能很难理解代码width
您指定的匹配,\n
将留在缓冲区width
,剩余的字符也会留在输入缓冲区无论如何,可能还有更多场景需要考虑,其中许多取决于您需要处理的确切情况。
这两种功能各有利弊。我不认为它们中的任何一个本质上都是“坏的”,它们都要求用户正确使用它们并自己处理一些错误,但是,fgets
肯定具有迫使您提供长度和更具可读性的优势。
我希望我的观点现在更清楚了。
不要使用 fgets(...) 而是使用以下代码段:
char _x[7000];
char* y;
while ( ! feof (_f) )
{
fscanf(_f,"%[^\n]\n",_x);
y=x;
}