C语言:多个scanf时,我输入一个scanf的值时跳过了第二个scanf。

26

我有一段代码(函数被省略了,因为逻辑是作业的一部分):

#include <stdio.h>

int main()
{
    char c = 'q';
    int size; 

    printf("\nShape (l/s/t):");
    scanf("%c",&c);

    printf("Length:"); 
    scanf("%d",&size);

    while(c!='q')
    {
        switch(c)
        {
            case 'l': line(size); break; 
            case 's': square(size); break;
            case 't': triangle(size); break; 
        }


        printf("\nShape (l/s/t):");
        scanf("%c",&c);

        printf("\nLength:"); 
        scanf("%d",&size);
    }

    return 0; 
}

前两个Scanf很好运行,没有问题。进入while循环后,当应输入一个新的形状字符时,它会跳转到长度的printf并等待从那里获取一个字符,然后在下一次循环迭代中等待获取一个小数。

预循环迭代:

Scanf:形状。非常好运行
Scanf:长度。没有问题

第一次循环:

Scanf:形状。跳过此项
Scanf:长度。有问题,这个scanf映射到了形状字符。

第二次循环:

Scanf:形状。跳过此项
Scanf:长度。有问题,这个scanf现在将大小int映射到形状字符上。

为什么会这样呢?

7个回答

46

scanf("%c")函数会从ENTER键读取换行符。

当你输入比如说15时,你会先输入一个1,再输入一个5,然后按下ENTER键。这样输入缓冲区中就有三个字符。scanf("%d")函数会将15读取出来,并将它们解释为数字15,但是换行符还留在输入缓冲区中。接着,scanf("%c")函数会立即读取该换行符,然后程序会继续执行下一个scanf("%d")函数,等待你输入一个数字。

通常的建议是使用fgets函数读取整行输入,然后将每行内容分别解释处理。一个更简单的解决方案是在每个scanf("%d")函数后添加一个getchar()函数。


4
如果用户在数字后面不小心输入了空格(或其他垃圾字符),则scanf("%d"); getchar();这个组合会失败。 - ilkkachu

25
基本问题在于scanf()在读取数字后会将换行符留在缓冲区中,并在下一次使用%c读取它。 实际上,这是我不使用scanf()的好示例; 我使用行读取器(例如fgets())和sscanf(),更容易控制。
你可以通过在格式字符串中使用" %c"而不是"%c"来挽救它。 空格使scanf()在读取字符之前跳过空格(包括换行符)。
但从长远来看,放弃scanf()fscanf()并使用fgets()或等效方法再加上sscanf()将更容易。 除此之外,当你有整个字符串可用于处理时,错误报告要容易得多,而不是在scanf()失败后留下的零星残留物。
总是要检查是否成功从scanf()转换值。 输入通常失败且非常恐怖。 不要因为您没有检查而毁掉程序。

7
尝试在scanf中添加一个空格。
scanf(" %d", &var);
//     ^
//   there

这将导致scanf()在匹配整数之前丢弃所有空格。

2
根据section 7.21.6.2p8的规定,scanf通常会在匹配整数之前丢弃所有空格,因此此格式字符串中的空格是无意义的:"输入空格字符(由isspace函数指定)将被跳过,除非规范包括一个[, c或n说明符。" - autistic
2
这不会解决问题。%d 已经跳过了前导空格字符。 - Spikatrix

6

使用函数

void seek_to_next_line( void )
{
    int c;
    while( (c = fgetc( stdin )) != EOF && c != '\n' );
}

清空输入缓冲区。

1
这是一个很好的函数。为了可读性,我会把空语句(单个分号)放在单独的一行上。 - Thomas Padron-McCarthy
1
我也喜欢这个,但我认为最好不要使用“flush”这个词,因为它在标准中有一个已经确定的定义,与此处的用法相矛盾。也许seek_to_next_line会是一个更具描述性的名称? - autistic
实施 @Sebivor 建议的更改。 - Mathieu K.
由于我无法添加自己的答案,并且这是我看到的最好的答案,因此我也将其放在这里:scanf("%*[^\n]"); getchar();。如果您喜欢,可以将其包装成一个函数。与该答案相比,没有任何优势; 从技术上讲,两者都是可以接受的,尽管该答案更容易维护,这在某种程度上是一把双刃剑;通常我们倾向于编写更易于维护的代码,但是考虑到某些“白痴”,简化版本可能更容易被修改为不正确的东西……你知道我在说什么……某种优化。选择正确的工具完成任务,对吧? - autistic

2

'\n'字符在第一次调用scanf后仍留在输入流中,因此第二次调用scanf()会将其读入。请使用getchar()


1
当您输入形状并按下ENTER键时,该形状将被第一个scanf消耗掉,但ENTER键不会!第二个scanf期望一个数字,因此ENTER键被跳过,因为它被视为空格,并且scanf等待有效输入(数字),再次由ENTER键终止。好的,数字被消耗了,但ENTER键没有被消耗,因此while循环中的第一个scanf使用它,您的形状提示被跳过...这个过程重复。您必须在scanfs中添加另一个%c来处理ENTER键。希望这可以帮助您!

0
你还可以使用scanf("%c%*c", &c);来读取两个字符并忽略最后一个字符(在这种情况下是'\n')。

人们总是做些奇怪的事情——比如在主输入后面加上空格。这样的单字符扫描器在最好的情况下也很脆弱。 - Jonathan Leffler

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接