我在C中编写一个超级简单的基于命令行的程序。这只是一个小测试,代码非常简单。因此,它要做的是询问用户他们的名字,数学成绩,英语成绩,计算成绩。然后它计算出他们的平均成绩,并告诉他们他们输入的名字。是的我知道这是一个非常简单的程序,但我仍然做错了。
问题是,我的代码的一部分将首先告诉用户输入他们的名字,然后一旦他们这样做并按下输入我的其余代码将立即运行然后停止工作。奇怪的是,我只是不明白出了什么问题。
#include <stdio.h>
int main(int argc, const char * argv[])
{
char chr;
char firstname;
int mathsmark, englishmark, computingmark, averagemark;
printf("What is your name?\n");
scanf("%c", &firstname);
printf("\n");
printf("What is your maths mark?\n");
scanf("%d", &mathsmark);
printf("\n");
printf("What is your english mark?\n");
scanf("%d", &englishmark);
printf("\n");
printf("What is your computing mark?\n");
scanf("%d", &computingmark);
printf("\n");
printf("Your name is: %c", firstname);
printf("\n");
averagemark = (mathsmark + englishmark + computingmark) / 3;
printf("%d", averagemark);
printf("\n");
chr = '\0';
while (chr != '\n') {
chr = getchar ();
}
return 0;
}
答案 0 :(得分:5)
一个主要问题是您已将firstname
声明为单个字符长,当您尝试从控制台读取名称时,您正在使用%c
转换说明符,从输入流中读取下一个单个字符并将其存储到firstname
。名称的其余部分留在输入流中,以填充剩余的scanf
次呼叫。
例如,如果您输入“Jacob”作为名字,则第一个scanf
来电会将J
分配给firstname
,并在输入流中保留"acob\n"
。
下一个scanf
调用尝试将"acob\n"
转换为整数值并将其保存到mathsmark
,但失败("acob\n"
不是有效的整数字符串)。接下来的两个scanf
调用也会发生同样的事情。
最后一个循环
while (chr != '\n')
{
chr = getchar();
}
最终消耗剩余的"acob\n"
,其中包含换行符(因为在键入名称后按Enter键),导致循环和程序退出。
你如何解决这个问题?
首先,您需要将firstname
声明为char
的数组:
char firstname[SOME_SIZE] = {0};
其中SOME_SIZE
足以处理所有情况。您需要将scanf
调用更改为
scanf("%s", firstname);
这告诉scanf
从输入流中读取字符直到下一个空白字符,并将结果存储到firstname
数组中。请注意,您无需在此处使用&
运算符;在大多数情况下,数组类型的表达式将被转换(“衰减”)为指针类型的表达式,表达式的值将是数组中第一个元素的地址。
请注意scanf
不是很安全,而且不是很强大。如果输入的字符多于缓冲区大小要容纳的字符数,scanf
将很乐意将这些额外字符存储到数组后面的内存中,这可能会破坏重要的内容。您可以通过在转换说明符中使用显式字段宽度来防范这种情况,例如
scanf(*%29s", firstname);
但总的来说这是一种痛苦。
scanf
在检测错误输入方面也不是很擅长。如果您输入“12er”作为其中一个标记,则scanf
将转换并分配"12"
,将"er"
留在流中以填充下一个读取。
scanf
返回成功分配的数量,因此防止错误输入的一种方法是检查返回值,如下所示:
if (scanf("%d", &mathmarks) != 1)
{
printf("Bad input detected for math marks\n");
}
不幸的是,scanf
不会从流中删除不良字符;你必须自己使用getchar
或类似的东西。
答案 1 :(得分:1)
这是较新的C / C ++开发人员的常见错误。 scanf
函数检测到你按ENTER/RETURN
键来表示输入的结束,但是它也会在输入字符串的末尾捕获\n
字符,所以你基本上得到两个RETURNS
被检测到。
请阅读使用fgets
和sscanf
的示例:
http://www.linuxforums.org/forum/programming-scripting/67560-problem-scanf.html
它会很快为您解决此问题。与此同时,我强烈建议你查看这本书:
http://www.amazon.com/Primer-Plus-5th-Stephen-Prata/dp/0672326965
它是北美高中和大学中最常用的C编程书,并且有大量的例子供您学习,包括您在上面演示的这个特定程序。印刷版有比电子书更多的例子,所以我只需要花30.00美元购买印刷版。
答案 2 :(得分:1)
您可能希望查看一些教程。可能是Format specifiers上的一个和strings in C上的一个
scanf()
从stdin
读取数据并按格式说明符的指定存储它们。在这种情况下:
char firstname;
scanf("%c", &firstname);
从stdin
中读取1个字符并将其存储到firstname
:
>> What is your first name?
Mike
现在firstname == 'M'
因为scanf()
按照我们的要求读取了1个字符。
你想要做的是读一个字符串(一堆字符):
char firstname[5]; // an array of characters
scanf("%s", firstname); // store as a string
firstname[4] = '\0'; // Truncate the result with a NULL to insure no overflow
>> What is your first name?
Mike
现在firstname
是[M] [i] [k] [e] [\ 0],因为scanf()
读取了1个字符串,正如我们所要求的那样。
请注意,对于printf()
同样如此,带有printf
的{{1}}将为您提供一个字符,其中%c
和printf()
将为您提供所有直到NULL终止符的字符。
答案 3 :(得分:1)
你有(至少)两个选择。
char firstname[number_big_enough_to_hold_long_name];
/*or */
char *firstname = malloc(sizeof(char) * number_big_enough_to_hold_long_name);
/* ... code ... */
free(firstname);
此外,最好限制读取的宽度。 scanf()
不知道firstname
的大小(可用空间)。
scanf("%number_big_enough_to_hold_long_names", ...
/* i.e. */
char firstname[32];
if(scanf("%31s", firstname) == EOF) {
perror("bad");
return 1;
}
此外,您应该在尝试下次阅读之前检查是否还有其他内容。即如果有人输入“我的姓名”,则只有“我的”会以firstname
结尾,“姓名”将保留在输入流中。
而getchar()
会返回int
而不是字符。