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

问题描述

所以我有这段代码是为了读取应该在数字 1-9 之间的用户输入而编写的。在代码中有和 int "choice" 声明。但是程序员没有使用 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");
    }
}

解决方法

我们只能猜测编码人员的意图。

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

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,&x),因此没有理由使用 unsafe 函数。 sscanf 返回分配成功的次数,因此可以进行错误检查。

,

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

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

你问为什么这段代码没有“直接用简单的方法来做”。通过“简单的方法”,我假设您的意思是

scanf("%d",&choice);

这里的问题是,是的,如果用户键入一些非数字输入,则要正确进行操作会非常困难。

在尝试处理用户输入错误的可能性时,一般有两种方法:

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

在我看来,这里只有一种选择,那就是#2。如果我讨论所有原因,这个答案将变得太长,但底线是方法#1 是徒劳的。 scanf 函数有一个优点,只有一个优点,那就是像 scanf("%d",&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。然后就不会太糟糕了。