使用缓冲区而不是直接对整数使用 scanf 函数的原因

Posted

技术标签:

【中文标题】使用缓冲区而不是直接对整数使用 scanf 函数的原因【英文标题】:Reason for use of a buffer instead of directly using the scanf function for the integer 【发布时间】:2021-07-06 08:55:47 【问题描述】:

所以我有这段代码是为了读取应该在数字 1-9 之间的用户输入而编写的。在代码中声明了 int “选择”。但是,程序员没有使用scanf("%d", choice); 直接扫描用户输入,而是使用了字符缓冲区并扫描了缓冲区,然后使用 atoi 函数将字符输入转换为整数。我很困惑为什么要这样做,而不是直接用简单的方法来做。我的假设是程序员已经这样做了,因此如果用户输入字符而不是数字,代码不会出现故障。但如果是这种情况,那么 atoi 将如何将字母转换为整数?代码如下:

int readMenuChoice() 
    while (1) 
        char buffer[50];
        size_t buffLen =  10;
        int choice;
        showMenu(); //another function that displays all options from 1 to 9
        printf("Choose a menu option: ");
        scanf("%[^\n]", buffer);
        getchar();
        choice = atoi(buffer);
        if (choice > 0 && choice < 9) 
            return choice;
        
        printf("Invalid input\n\n");
    

【问题讨论】:

"atoi 如何将字母转换为整数" - 不会 - 退出 while 循环 (if(choice &gt; 0 &amp;&amp; choice &lt; 9)) 的条件不会被满足所以它会再次showMenu() 并让用户尝试再次输入一个有效的选择。 我不会那样做的。作者还有size_t buffLen = 10;,这无关紧要。它也有潜在的缓冲区溢出。它不允许评论菜单调用邀请的选择 9。非常糟糕的编码,一个如何编码的例子。 永远不要使用atoi()。它绝对无法检测到任何错误或错误输入。 Why shouldn't I use atoi()? 如果用户输入“鱼!”在回答您的“选择菜单选项:”问题时,atoi() 返回一个有效的整数。 @WeatherVane 那么我应该直接使用 scanf 并读取整数而不是执行所有这些操作吗?或者还有其他方法可以继续进行吗? 有很多方法可以做到这一点。通过使用fgets 进行输入,然后使用strtol 进行转换,您可以获得很多控制、流程和检查选项。这个Get safe int input 是解决该主题的几个先前问题之一。 【参考方案1】:

我们只能从编码器那里猜测意图。

但一个很可能的原因是确保每个输入之间的输入流为空。我个人也做类似的事情。但我会这样做:

while (1) 
    char buffer[50];
    int choice;

    showMenu(); 
    printf("Choose a menu option: ");

    if(!fgets(buffer, sizeof buffer, stdin)) 
        /* Handle error */
    

    if(sscanf(buffer, "%d", &choice) != 1) 
        /* Handle error */
    

    if (choice > 0 && choice < 9) 
        return choice;
    

    printf("Invalid input\n\n");

atoi 是一个不安全的函数。如果参数无法解析为数字,则会调用未定义的行为。而且由于x = atoi(s) 完全等同于sscanf(s, "%d", &amp;x),所以没有理由使用unsafe 函数。 sscanf 返回成功分配的数量,因此可以进行错误检查。

【讨论】:

它在零和错误时返回零。它甚至比这更糟糕。 atoi() 可以返回任何错误信息 - If the value of the result cannot be represented, the behavior is undefined. @AndrewHenle 真的。固定。【参考方案2】:

安全读取用户输入、将输入限制为一组特定的“允许”输入,同时完全忽略“不允许”输入的问题可能是一个非常棘手的问题。

也许同样令人惊讶的是,scanf 函数在执行此任务时有多差,以及使用围绕 scanf 构建的任何算法完全解决问题是多么困难。

您问为什么这段代码没有“直接以简单的方式完成”。通过“简单的方法”,我假设您的意思是

scanf("%d", &choice);

这里的问题是,是的,如果用户键入一些非数字输入,则可能很难正确进行。

在尝试处理用户输入错误的可能性时,有两种通用途径:

    继续调用scanf("%d") 来读取输入,但是如果scanf 失败,请尝试修补。 (显然这里的第一步是检查scanf的返回值。) 使用除scanf 以外的其他内容以文本形式读取一行输入。然后尝试验证该行,并将其转换为所需的形式。

在我看来,这里只有一个选择,那就是#2。如果我讨论所有原因,这个答案将变得太长,但最重要的是方法#1是徒劳的。 scanf函数有一个优点,也只有一个优点,那就是像scanf("%d", &amp;choice)这样的调用确实很简单。但是错误处理几乎没有用。当你围绕它建立了合理数量的错误处理时,你必须做的工作量大约是方法 2 的三倍,你仍然不会得到完全令人满意的结果.

所以大多数有经验的 C 程序员都会同意 #2 从长远来看是唯一可行的方法。有一个 central question 建议使用 scanf 以外的其他方式进行输入的好方法。

IMO,您发布的代码的问题在于它设法结合了两全其美。它确实会尝试将输入行读取为文本,然后再对其进行处理,但是它读取输入行的方式是……可怕的scanf!尽管尝试在其他几个方面小心,但这段代码甚至没有检查scanf 的返回值,因此这段代码仍然存在一些经典问题(如过早的 EOF)。

此代码还包含对getchar 的神秘额外调用,这是scanf 使用代码的典型,因为杂散的换行符几乎总是一个问题。

这段代码也使用了%[...],这是我最不喜欢的scanf格式。正如我所说,scanf 的唯一优点是简单,但像"%[^\n]" 这样的语言绝非简单。是的,我知道它的作用,但 IMO 完全违背了使用 scanf 进行简单(如果不够稳健)用户输入的目的。

但是,是的,以这种方式编写代码的主要目的可能是“这样如果用户输入字符而不是数字,代码就不会出现故障”。该代码将一行文本作为文本读取,然后尝试将文本转换为数字。你问atoi 函数对字母输入做了什么,答案是(大多数时候,无论如何)它悄悄地返回 0。由于 0 不是有效输入,这段代码将拒绝它,所以从这个意义上说它有效。

要改进此功能,首先要做的是将对scanfgetchar 的调用替换为fgets。接下来要做的是将atoi 替换为strtol。然后就不会太糟糕了。

【讨论】:

“我最不喜欢的 scanf 格式” - 这不仅仅是文字:D atoi 在失败时不需要返回 0 @klutt 感谢您提醒我atoi 的不确定性,这比我想象的还要糟糕。 (atoi("x") 确实应该定义行为,但你是对的,它没有。)答案已更新。

以上是关于使用缓冲区而不是直接对整数使用 scanf 函数的原因的主要内容,如果未能解决你的问题,请参考以下文章

为啥对函数的 VLA 数组参数使用星号“[*]”而不是整数?

Text Reverse(hdu1062)

scanf的缓冲区问题

scanf的缓冲区问题

解决linux下fflush(stdin)无效

解决linux下fflush(stdin)无效