问题描述
所以我有这段代码是为了读取应该在数字 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);
这里的问题是,是的,如果用户键入一些非数字输入,则要正确进行操作会非常困难。
在尝试处理用户输入错误的可能性时,一般有两种方法:
- 继续调用
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
。然后就不会太糟糕了。