使用缓冲区而不是直接对整数使用 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 > 0 && choice < 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", &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", &choice)
这样的调用确实很简单。但是错误处理几乎没有用。当你围绕它建立了合理数量的错误处理时,你必须做的工作量大约是方法 2 的三倍,你仍然不会得到完全令人满意的结果.
所以大多数有经验的 C 程序员都会同意 #2 从长远来看是唯一可行的方法。有一个 central question 建议使用 scanf
以外的其他方式进行输入的好方法。
IMO,您发布的代码的问题在于它设法结合了两全其美。它确实会尝试将输入行读取为文本,然后再对其进行处理,但是它读取输入行的方式是……可怕的scanf
!尽管尝试在其他几个方面小心,但这段代码甚至没有检查scanf
的返回值,因此这段代码仍然存在一些经典问题(如过早的 EOF)。
此代码还包含对getchar
的神秘额外调用,这是scanf
使用代码的典型,因为杂散的换行符几乎总是一个问题。
这段代码也使用了%[...]
,这是我最不喜欢的scanf
格式。正如我所说,scanf
的唯一优点是简单,但像"%[^\n]"
这样的语言绝非简单。是的,我知道它的作用,但 IMO 完全违背了使用 scanf
进行简单(如果不够稳健)用户输入的目的。
但是,是的,以这种方式编写代码的主要目的可能是“这样如果用户输入字符而不是数字,代码就不会出现故障”。该代码将一行文本作为文本读取,然后尝试将文本转换为数字。你问atoi
函数对字母输入做了什么,答案是(大多数时候,无论如何)它悄悄地返回 0。由于 0 不是有效输入,这段代码将拒绝它,所以从这个意义上说它有效。
要改进此功能,首先要做的是将对scanf
和getchar
的调用替换为fgets
。接下来要做的是将atoi
替换为strtol
。然后就不会太糟糕了。
【讨论】:
“我最不喜欢的 scanf 格式” - 这不仅仅是文字:D atoi 在失败时不需要返回 0 @klutt 感谢您提醒我atoi
的不确定性,这比我想象的还要糟糕。 (atoi("x")
确实应该定义行为,但你是对的,它没有。)答案已更新。以上是关于使用缓冲区而不是直接对整数使用 scanf 函数的原因的主要内容,如果未能解决你的问题,请参考以下文章