C - 尝试扫描十六进制/十进制/十进制值以检查它们是否等于用户输入



所以,我是C的新手,这是我的第一个类项目。我基本上需要一个程序,询问用户他想要多少问题,然后检索一个正的最大8位数字,可以是oct/dec/xex(这是随机的),然后要求用户将其转换为随机基数。例如,如果我得到一个十进制数,它会随机要求我将其转换为十六进制或八进制。在每一个问题的结尾,它都会说我的转换是对是错,在程序的最后,它会显示我答对了多少问题。

所有操作都很好,直到我开始键入随机字母/字符时,它要求我转换为十六进制以外的字符。例如,如果它要求我将八进制转换为十进制,如果我输入一个字母,它有时会说它是对的,它还会跳过问题并继续循环,直到得到十六进制。

我真的不知道该怎么办。这是我的代码:

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
int main()
{
int rightanswers = 0;
int answer;
int nquestions;
printf("Number of questions:");
scanf("%d", &nquestions);
srand((unsigned int) time(NULL));
unsigned char questions[nquestions];
for (int i=1; i<=nquestions; i++)
{
questions[i] = (rand()%255)+1;
int randomnumb = (rand()%6)+1;
switch(randomnumb)
{
case 1:
printf("nConvert 0%o to base 10:", questions[i]);
scanf("%d", &answer);
if (answer == questions[i])
{
rightanswers++;
printf("Right!");                                       
}
else
{
printf("Wrong!");                       
}
break;
case 2:
printf("nConvert 0%o to base 16:", questions[i]);
scanf("%x", &answer);
if (answer == questions[i])
{
rightanswers++;
printf("Right!");                                       
}
else
{
printf("Wrong!");                       
}
break;
case 3:
printf("nConvert %d to base 8:", questions[i]);
scanf("%o", &answer);
if (answer == questions[i])
{
rightanswers++;
printf("Right!");                                       
}
else
{
printf("Wrong!");                       
}
break;
case 4:
printf("nConvert %d to base 16:", questions[i]);
scanf("%x", &answer);
if (answer == questions[i])
{
rightanswers++;
printf("Right!");                                       
}
else
{
printf("Wrong!");                       
}
break;
case 5:
printf("nConvert 0x%x to base 8:", questions[i]);
scanf("%o", &answer);
if (answer == questions[i])
{
rightanswers++;
printf("Right!");                                       
}
else
{
printf("Wrong!");                       
}
break;
case 6:
printf("nConvert 0x%x to base 10:", questions[i]);
scanf("%d", &answer);
if (answer == questions[i])
{
rightanswers++;
printf("Right!");                                       
}
else
{
printf("Wrong!");                       
}
break;  
}
}
printf("nYou got %d conversions right!", rightanswers);
return 0;
}

继续我的评论。scanf(及其系列)给新的C程序员带来了用户输入方面的无尽麻烦,因为scanf存在许多陷阱。主要是因为转换说明符在处理前导空白(例如spacetabnewline等)方面表现不同,并且程序员未能验证返回。返回验证至关重要,因为输入缓冲区中剩余的内容(例如stdin)取决于是否成功转换。

scanf返回转换成功的次数。(例如scanf ("%s %d", strvar, &intvar))包含2转换说明符(%s&%d)。如果字符串和整数成功转换并存储在提供的变量中,则返回2。任何小于的值都表示匹配失败输入失败trl+D(或windoze上的

Ctrl+Z如果转换失败,无论是由于匹配失败(输入类型和转换说明符之间不匹配)还是输入失败入缓冲区读取字符,所有字符都将保留,只是等待破坏对scanf的下一次调用。

此外,您必须考虑每次调用scanf后留在输入缓冲区中的'n'(由用户按Enter生成)。一些格式说明符将使用前导的空白,而其他格式说明符(如字符格式说明符)则不会使用,事实上%c会很乐意将stdin中剩下的'n'作为您的下一个输入

具体来说,数字格式说明符(例如%d, %x, %o, %lf, ...)都将忽略前导空格,因此在下一次scanf调用之前不必专门删除空格。对于所有其他人,你可以这样做。这种对stdin中剩余(或可能剩余)内容的说明对于使用scanf进行输入至关重要。否则,您只是要求输入看起来被跳过或。。。无限循环。(您可以按照格式字符串的方式处理空白)

所有这些都是为什么像fgets这样面向行的输入函数是处理用户输入的推荐方法。它将读取,并包括后面的'n',它提供了一个简单的检查,以检查用户提供的所有输入字符是否正确读取。(在验证用户输入后,您可以从fgets(或POSIXgetline)填充的缓冲区中解析您需要的任何内容

但由于你会多次遇到scanf,所以花时间阅读(和理解)man scanf是非常值得的。是的,这本书读起来有点枯燥,但它是唯一能准确解释scanf陷阱所在的东西。

考虑到这一点,下面提供了两个示例(对代码的修改),向您展示如何使用scanf处理输入。nquestions的第一次读取只是显示了一种通用方法,用于验证scanf的整数输入,检查返回,处理用户取消(生成EOF),并最终清空助手函数empty_stdin()中剩余的任何字符,该函数只是从stdin读取,直到找到'n'(由用户按Enter生成)或EOF

用户输入的其余部分由助手函数getintvalue处理(因为您不想在代码中重复验证代码)。getintvalue将显示提示和格式字符串作为参数,但本质上只是执行与函数中nquestions相同的操作。

其他变更。questions[]不需要数组。一个简单的int值就可以了。您不需要在switch的每个case中重复代码。(已移至末尾)。剩余的更改和问题在下面的评论中得到了解决:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define PRMTSZ 128  /* if you need a constant define one */
/* or use an enum to define several */
enum { NSWITCH = 6, QMAX = 256 };
/* function to get integer value from user.
* prompt user with 'prompt', read value with format 'fmt'.
* returns int value or EOF on user cancelation of input.
*/
int getintvalue (const char *prompt, const char *fmt);
/* simple function to empty remaining chars from stdin */
void empty_stdin();
int main (void) {
int rightanswers = 0,
nquestions = 0;
srand (time(NULL));
/* example input loop -- loop continually until valid input or EOF */
for (;;) {
int rtn = 0;
printf ("Number of questions: ");
if ((rtn = scanf ("%d", &nquestions)) == EOF) {
fprintf (stderr, "warning: user canceled input.n");
return 1;
}
empty_stdin();  /* remove all remaining chars from stdin */
if (rtn == 1)   /* good input */
break;
/* handle matching or input failure */
fprintf (stderr, "error: invalid input.n");
}
/* loops are ZERO based in C */
for (int i = 0; i < nquestions; i++)
{
/* declarations 1st in each block, for C89 portability - Win7, etc. */
char prompt[PRMTSZ] = "";               /* buffer for prompt */
int randomnumb = rand() % NSWITCH,      /* values 0 - 5 */
question = rand() % QMAX,           /* values 0 - 255 */
answer = 0;
switch (randomnumb)
{
case 0:
sprintf (prompt, "nConvert 0%o to base 10: ", question);
/* let's use a getintvalue to validate user int input */
if ((answer = getintvalue (prompt, "%d")) == EOF)
return 1;
break;
case 1:
sprintf (prompt, "nConvert 0%o to base 16: ", question);
if ((answer = getintvalue (prompt, "%x")) == EOF)
return 1;
break;
case 2:
sprintf (prompt, "nConvert %d to base 8: ", question);
if ((answer = getintvalue (prompt, "%o")) == EOF)
return 1;
break;
case 3:
sprintf (prompt, "nConvert %d to base 16: ", question);
if ((answer = getintvalue (prompt, "%x")) == EOF)
return 1;
break;
case 4:
sprintf (prompt, "nConvert 0x%x to base 8: ", question);
if ((answer = getintvalue (prompt, "%o")) == EOF)
return 1;
break;
case 5:
sprintf (prompt, "nConvert 0x%x to base 10: ", question);
if ((answer = getintvalue (prompt, "%d")) == EOF)
return 1;
break;
default:
fprintf (stderr, "error: something went wrong in switch.n");
goto badswitch;
break;
}
if (answer == question) {
rightanswers++;
printf ("Right!n");                                       
}
else
printf ("Wrong!n");
badswitch:;
}
/* always end with 'n' for POSIX compiant EOF */
printf("nYou got %d conversions right!n", rightanswers);
return 0;
}
int getintvalue (const char *prompt, const char *fmt)
{
int value = 0;
/* input loop -- loop continually until valid input or EOF */
for (;;) {
int rtn = 0;
printf ("%s: ", prompt);
if ((rtn = scanf (fmt, &value)) == EOF) {
fprintf (stderr, "warning: user canceled input.n");
return rtn;
}
empty_stdin();  /* remove all remaining chars from stdin */
if (rtn == 1)   /* good input */
break;
/* handle matching or input failure */
fprintf (stderr, "error: invalid input.n");
}
return value;
}
void empty_stdin()
{
int c;
do
c = getchar();
while (c != 'n' && c != EOF);
}

以下是各种验证运行,显示了在程序的各个阶段对错误输入或用户取消的正确处理。

当一切顺利时的示例

$ ./bin/inttestscanf
Number of questions: 4
Convert 218 to base 16: : da
Right!
Convert 0325 to base 10: : 213
Right!
Convert 0xe to base 10: : 14
Right!
Convert 0x39 to base 8: : 71
Right!
You got 4 conversions right!

用户取消问题数量的示例

$ ./bin/inttestscanf
Number of questions: foo
error: invalid input.
Number of questions: bar
error: invalid input.
Number of questions: warning: user canceled input.

用户输入无效输入的示例

$ ./bin/inttestscanf
Number of questions: 4
Convert 023 to base 16: : no good
error: invalid input.
Convert 023 to base 16: : 13
Right!
Convert 0xc7 to base 8: : foo
error: invalid input.
Convert 0xc7 to base 8: : 307
Right!
Convert 0353 to base 16: : eb
Right!
Convert 0x76 to base 10: : f8
error: invalid input.
Convert 0x76 to base 10: : 118
Right!
You got 4 conversions right!

(注意:想想如果scanf期望一个十六进制值,而用户输入了一个以a-f开头的任意字符串,会发生什么?)

仔细看看,如果你还有问题,请告诉我。

如果scanf遇到无法分配的东西,它会停止并返回。您试图分配的变量不会被更改,但您检查它的值就像读取它一样。它只会包含以前的内容,或者随机的东西。

要使用scanf,您需要始终检查其返回值,它会告诉您分配了多少变量。只有当此计数显示您的变量真正分配的时,您才应该访问其内容。

请注意,扫描失败后,"不可读"数据仍将在输入流中,下一次扫描将尝试再次读取相同的数据。

最新更新