使用scanf()读取一行不好?

使用scanf()读取一行不好?,c,scanf,stdio,C,Scanf,Stdio,我的一位朋友建议,使用fgets()读取一行作为输入比使用上面的语句中的scanf()要好得多。他有理由吗?可以安全使用,因为它避免了问题,只扫描num-1字符数 从流中读取字符并将其作为C字符串存储到str中,直到读取(num-1)个字符或到达换行符或文件结尾,以先发生的为准 这里的第二个参数num是要复制到str中的最大字符数(包括终止的空字符) 例如,假设在您的代码中,字符串数组的容量仅为5chars,如下所示 scanf(" %[^\n]",line); 使用上述代码,如果从fp输入的

我的一位朋友建议,使用
fgets()
读取一行作为输入比使用上面的语句中的
scanf()
要好得多。他有理由吗?

可以安全使用,因为它避免了问题,只扫描
num-1
字符数

从流中读取字符并将其作为C字符串存储到str中,直到读取(num-1)个字符或到达换行符或文件结尾,以先发生的为准

这里的第二个参数
num
是要复制到str中的最大字符数(包括终止的空字符)

例如,假设在您的代码中,字符串数组的容量仅为
5
chars,如下所示

scanf(" %[^\n]",line);
使用上述代码,如果从
fp
输入的字符长于
4
字符,
fgets()
将首先读取
4
字符,然后追加
\0
(,并丢弃其他额外的输入字符,只在
str[]
中存储五个字符)


scanf(“%[^\n]”,str)
将一直读取,直到找不到
\n
为止,如果输入字符串较长,则
4
chars
scanf()
将导致(因为
scanf
将尝试访问
str[]
中超出最大索引
4
的内存)

简单地说:是的,
fgets
是一个更好的选择

我看了看你的
scanf
格式说明符,我很困惑。准确理解它的功能需要花一些时间阅读
man
页面

此外,您的
scanf
代码容易发生缓冲区溢出


保持简单,您将减少维护成本,避免难以发现的错误

C常见问题解答
scanf
的问题有一些详细的解释:

更一般地说,
scanf
是为相对结构化、格式化的输入而设计的(其名称实际上来源于“扫描格式化”)。如果你注意,它会告诉你它是成功了还是失败了,但它只能告诉你失败的大致位置,而不能告诉你失败的方式或原因。您几乎没有机会执行任何错误恢复


有关详细信息,请参见。

fgets
将比此
scanf
更好。 如OP中所述,
scanf
可能存在以下问题

1) @Grijesh建议的缓冲区溢出


2) 可能接下来的
scanf
将不起作用,因为换行符保留在输入流中。(如果缺少空格)

是的,fgets是从标准输入读取行的更好、更安全的方法

此外,代码的可读性会更好。请看您给出的scanf声明

任何第二个看到它的人都会被彻底弄糊涂。但是,尽管FGET的可读性更强,而且更容易理解。

不要使用FGET(…),而是使用以下代码段:

 char str[5];
 fgets (str, 5, fp);  //5 =you have provision to avoid buffer overrun 

嗯,这是因为
scanf
不会强制您限制输入大小,但是
fgets

如果您阅读了文档并正确使用了
scanf
,这里的差别其实不大:

字符行[256];
scanf(“%255[^\n]%*c”,第行);//%*c以删除尾随\n
fgets(第256行,标准输入)
请注意,我从
scanf
格式字符串中删除了前导空格。 我稍后再谈这个问题

这两种情况都确保了我们不会阅读过多

但是,从安全角度来看,我们需要考虑:

  • fgets
    强制您指定尺寸
  • scanf
    上,您需要记住将阵列容量设置为-1
    • fgets
      为您执行此操作,因此您实际上通过了容量(或更少)
  • “这个格式到底意味着什么?”
很容易忘记scanf的细节,当你与一个拥有不同编程背景的庞大团队打交道时,有些人可能更难用这些细节编写代码,甚至可能不理解格式字符串。 因此,通常使用
fgets
更安全


现在,关于我删除的前导空间

当您使用
fgets
时,您将无法在输入前忽略
空格
字符,因此我必须删除该空格,以使两个调用的结果几乎相同

我想我们不能真的说一种方法比另一种方法“更好”,只是
fgets
更具可读性,可以确保您记住传递大小。这也可以通过将
scanf
调用封装到正确构建格式字符串的输入读取函数中来实现。这还可以让您在阅读之前跳过前导的
空格
字符


编辑 很明显,我的观点是,从安全角度来看(如“不在字符数组外写入”),这两种解决方案都是有效的,因为您可以限制在这两种方法上读取的字符数

我显示的代码纯粹是为了表明可以将其限制在一定的大小,而不是它们具有完全相同的效果。
正如Andrew Henle所说,
%*c
确实会丢弃一个输入字符,如果用户提供的字符超过了我们所说的读取长度。
但话说回来,这不是我的重点,也不是问题所在。 我只是把它放在那里,让它更接近于
fgets
的功能,因为
fgets
会从缓冲区中删除
\n
,如果输入量不大于您试图读取的量

据我所知,这个问题问的是scanfvs
fgets
,没有特别的意图。
至少有一个问题没有被描述

当然有很多额外的事情需要考虑,这取决于应用程序需要做什么,当然。

fgets的一些注意事项

  • 如果你的意见
    char _x[7000];
        char* y;
    while ( ! feof (_f) )
    {
        fscanf(_f,"%[^\n]\n",_x);
                y=x;
    }
    
    scanf(" %[^\n]",line);  // Bad - no buffer overflow protection.
    
    char buffer[100];
    scanf(" %99[^\n]",line);  // a little better
    
    char buffer[100];
    if (fgets(buffer, sizeof buffer, stdin)) {
      buffer[strcspn(buffer, "\n")] = '\0';  // Lop off potential tailing \n
    
    char *line = NULL;
    size_t len = 0;
    ssize_t nread;
    
    while ((nread = getline(&line, &len, stdin)) != -1) {