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

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

一切顺利,直到我开始输入随机字母/字符,当它要求我转换为hex以外。 例如,如果它要求我将八进制转换为十进制,如果我输入单个字母,它有时会说它是正确的,它也会跳过问题并继续循环直到它变为hex。

我无法弄清楚我能做些什么。 这是我的代码:

#include  #include  #include  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相关的众多陷阱。 主要是因为转换说明符在处理前导space (例如spacetabnewline等)方面表现不同,并且程序员无法validation返回 。 返回validation很关键,因为输入缓冲区中剩下的内容(例如stdin )取决于是否成功进行转换。

scanf返回成功转换的次数。 (例如scanf ("%s %d", strvar, &intvar) )包含2 转换说明符%s%d )。 如果字符串和整数成功转换并存储在提供的变量中,则返回值为2 。 任何较少表示匹配失败输入失败或用户通过Ctrl + D (或windoze上的Ctrl + Z )手动生成EOF取消输入。

如果转换失败,或者由于匹配失败 (输入类型和转换说明符不匹配)或输入失败 (每个转换说明符没有足够的输入),从stdin读取停止,则不会从输入读取其他字符缓冲区和所有字符都留下了 – 只是等待你下次调用scanf

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

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

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

但是,由于您将多次遇到scanf ,因此非常值得花时间阅读(并理解) man scanf 。 是的,它读起来有点干,但它是唯一可以准确解释scanf陷阱的位置。

考虑到这一点,下面提供了两个示例(对代码的修改),向您展示如何使用scanf处理输入。 nquestions的第一个读数只显示了使用scanfvalidation整数输入,检查返回,处理用户取消(生成EOF ),最后清空任何保留有帮助函数empty_stdin()字符的一般方法stdin直到'\n' (由用户按Enter键生成)或找到EOF

用户输入的其余部分由辅助函数getintvalue处理(因为您不希望在代码中反复复制validation代码)。 getintvalue将提示显示并将格式字符串作为参数,但基本上只是为nquestions中的nquestions做了同样的事情。

其他变化。 questions[]不需要数组。 一个简单的int值就可以了。 在每种case ,您都不需要重复代码。 (已经移到了最后)。 其余的变化和问题在下面的评论中得到了解决:

 #include  #include  #include  #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); } 

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

示例当一切顺利

 $ ./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需要hex值并且用户输入以af开头的任意字符串会发生什么?)

仔细看看,如果您有其他问题,请告诉我。

如果scanf遇到无法分配的内容,它会停止并返回。 您尝试分配的变量不会更改 – 但您检查其值是否已被读取。 它将简单地包含之前的任何内容或随机内容。

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

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