所以,我是C的新手,这是我第一个上课的项目。我基本上需要一个程序,询问用户他想要多少问题,然后检索一个正的最大8位数,可以是oct / dec / hex(它是随机的),然后要求用户将其转换为一个随机的基础。例如,如果我得到一个十进制数字它将随机要求我将其转换为十六进制或八进制。在每个问题的最后,它表示如果我的转换是对还是错,并且在程序结束时它会显示我正确的问题。
一切正常,直到我开始输入随机字母/字符时,它要求我转换为十六进制以外的字符。例如,如果它要求我将八进制转换为十进制,如果我输入单个字母,它有时会说它是正确的,它也会跳过问题并继续循环直到它变为十六进制。
我无法弄清楚我能做些什么。这是我的代码:
#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;
}
答案 0 :(得分:2)
继续我的评论。由于与scanf
相关的众多陷阱,scanf
(和家庭)给新的C程序员带来了用户输入的麻烦。主要是因为转换说明符在处理前导空白(例如space
,tab
,newline
等等)方面表现不同,并且程序员无法验证的返回即可。返回验证很关键,因为输入缓冲区中剩下的内容(例如stdin
)取决于是否成功进行转换。
scanf
返回成功转化的次数。 (例如scanf ("%s %d", strvar, &intvar)
)包含2
转化说明符(%s
&amp; %d
)。如果字符串和整数成功转换并存储在提供的变量中,则返回为2
。少一些表示匹配失败或输入失败或用户通过 Ctrl + D 手动生成EOF
来取消输入(在windoze上 Ctrl + Z 。
如果转换失败,可能是由于匹配失败(输入类型和转换说明符不匹配)或输入失败(没有足够的输入对于每个转换说明符),从stdin
停止读取,不会从输入缓冲区中读取更多字符并且所有字符都被保留 - 只是等待你下次调用{{1} }。
此外,在每次调用{{}后,您必须考虑输入缓冲区中留下的scanf
(由用户按 Enter 生成) 1}}。一些格式说明符将使用前导空格,而其他(例如字符格式说明符不会),实际上'\n'
将很高兴地使用scanf
在%c
中作为您的下一个输入。
具体来说,数字格式说明符(例如'\n'
)将忽略前导空格,因此您不必在下一次stdin
调用之前专门删除空格。对于其他所有人。对%d, %x, %o, %lf, ...
中剩余(或可能保留)的内容进行计算对于使用scanf
输入至关重要。否则你只是要求输出看起来被跳过或者......无限循环。 (您可以按照制作格式字符串的方式处理空格)
所有这些都是为什么像stdin
这样的面向行的输入函数是处理用户输入的推荐方法。它将读取并包括尾随scanf
,它可以简单地检查用户提供的所有输入字符是否正确读取。 (在验证用户输入之后,然后从fgets
(或POSIX '\n'
)填充的缓冲区中解析您需要的任何内容
但是,由于您会多次遇到fgets
,因此值得花时间阅读(并了解)getline
。是的它读起来有点干,但它是唯一可以解释scanf
陷阱的确切位置。
考虑到这一点,下面提供了两个示例(对代码的修改),向您展示如何使用man scanf
处理输入。 scanf
的第一个读数只显示了使用scanf
验证整数输入的一般方法,检查返回,处理用户取消(生成nquestions
),最后清空任何字符保留一个帮助函数scanf
,只需从EOF
读取,直到找到empty_stdin()
(由用户按 Enter 生成)或stdin
。
用户输入的其余部分由辅助函数'\n'
处理(因为您不想在代码中反复复制验证代码)。 EOF
将提示显示并将格式字符串作为参数显示,但基本上只是对函数中的getintvalue
执行相同的操作。
其他变化。您不需要getintvalue
的数组。一个简单的nquestions
值就可以了。您不需要在questions[]
的每个int
中复制代码。 (已经移到了最后)。其余的变更和问题将在下面的评论中解决:
case
以下是各种验证运行,显示在程序的各个阶段正确处理错误输入或用户取消。
示例一切顺利
switch
用户取消问题数量时的示例
#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!
开头的任意字符串会发生什么?)
仔细看看,如果您有其他问题,请告诉我。
答案 1 :(得分:1)
如果scanf
遇到无法分配的内容,它会停止并返回。您尝试分配的变量不会更改 - 但您检查其值是否已被读取。它将简单地包含之前的内容或随机内容。
要使用scanf,您需要始终检查其返回值,它会告诉您已分配了多少变量。只有当此计数显示您的变量 真正已分配时,才应访问其内容。
请注意,扫描失败后,“不可读”的数据仍然在输入流中,下一个scanf将尝试再次读取相同的数据。