为什么 scanf("%*[^\n]\n"); 和 scanf("%*[^\n]%*c"); 不能清除挂起的换行符?

5
在调用 scanf("%d", &variable); 后,我们会在 stdin 中留下一个未处理的换行符,需要在调用 fgets 之前清除它,否则会导致它提前返回并读取到换行符。
我发现有些答案建议在第一次调用 scanf 后使用 scanf("%*[^\n]%*c"); 来丢弃换行符,而另一些建议使用 scanf("%*[^\n]\n");。理论上,两种方法都可以:第一种方法会消耗除了换行符以外的任何字符(但不包括换行符本身),然后消耗 恰好 一个字符(即换行符)。第二种方法会消耗除了换行符以外的任何字符(不包括它本身),然后 \n 这个空白字符会指示 scanf 读取所有的空白字符,直到遇到第一个非空白字符。
然而,尽管我已经看到有些答案中这些方法起作用,但我无法在此处使它们起作用(如下所示的代码)。 为什么这两种 scanf 方法都不起作用? 测试环境: Ubuntu Linux - gcc 5.4.0

scanf("%*[^\n]\n"); 方法:

#include <stdio.h>

int main(int argc, char **argv){
    int number;
    char buffer[1024];

    printf("Write number: \n");
    scanf("%d", &number);

    //Clearing stdin?
    scanf("%*[^\n]\n");

    printf("Write phrase: \n");
    fgets(buffer, 1024, stdin);

    printf("\n\nYou wrote:%u and \"%s\"\n", number, buffer);

    return 0;
}

输出:

$ ./bug 
Write number: 
2
Write phrase: 


You wrote:2 and "
"

scanf("%*[^\n]%*c"); approach:

#include <stdio.h>

int main(int argc, char **argv){
    int number;
    char buffer[1024];

    printf("Write number: \n");
    scanf("%d", &number);

    //Clearing stdin?
    scanf("%*[^\n]%*c");

    printf("Write phrase: \n");
    fgets(buffer, 1024, stdin);

    printf("\n\nYou wrote:%u and \"%s\"\n", number, buffer);

    return 0;
}

输出:

$ ./bug2 
Write number: 
3
Write phrase: 


You wrote:3 and "
"

以下方法是唯一能按预期工作的方法:

有效方法:

#include <stdio.h>

int main(int argc, char **argv){
    int number;
    char buffer[1024];

    printf("Write number: \n");
    scanf("%d", &number);

    //Clearing stdin!
    int c;
    while((c = getchar()) != '\n' && c != EOF){
        //Discard up to (and including) newline
    }

    printf("Write phrase: \n");
    fgets(buffer, 1024, stdin);

    printf("\n\nYou wrote:%u and \"%s\"\n", number, buffer);

    return 0;
}

输出:

$ ./notbug 
Write number: 
4
Write phrase: 
phrase :D


You wrote:4 and "phrase :D
"

你可以使用fgets(或gets)将输入读入缓冲区,然后使用sscanf()获取数字。 - Shiping
@Shiping 当然,有其他方法可以使这段代码正常工作。但问题的重点是为什么使用 scanf 清除挂起的换行符的方法不能像预期的那样工作。 - IanC
1
@Shiping:绝对不要使用gets。它是危险的,并且已经不再是标准的一部分了。 - too honest for this site
1
@IanC:那么你是否注意到有经验的 C 程序员为什么建议使用 fgets 而不是使用 scanf - too honest for this site
@Olaf 是的!fgets更加直接,不会在stdin缓冲区上产生挂起字符的问题。我实际上在回答一个使用scanf的问题时遇到了这个疑问。 - IanC
显示剩余2条评论
1个回答

4
基本问题是 scanf 模式 %[^\n] 匹配的是一个或多个非换行符字符。因此,如果下一个字符是换行符,则该模式将失败,scanf 将立即返回而不读取任何内容。添加 * 也不能改变这个基本事实。因此,你需要进行两次调用才能完成此操作。
scanf("%*[^\n]"); scanf("%*c");

请注意,在扫描模式中加入裸换行符几乎没有用处 -- 它会导致 scanf 读取并丢弃所有的空白字符,直到它看到一个非空白字符(该字符将留在输入缓冲区中)。特别是如果您尝试在交互式程序中使用它,它会一直挂起,直到您输入了一个非空白行。

非常感谢!现在我明白为什么 scanf 似乎根本没有从 stdin 中丢弃任何内容,实际上它在一开始就失败了。我可以在 [ 转换说明符描述的第一行(“匹配指定集合中的非空字符序列...”)和“返回值”部分确认您所说的内容 :) - IanC
边缘情况:scanf("%*[^\n]"); 会返回,因为它遇到了 '\n'、文件结尾或输入错误。在这三种情况中的前两种,调用 scanf("%*c"); 是可以的。在最后一种罕见情况下,scanf("%*c"); 可能会消耗一个非 '\n' 字符。建议检查 scanf() 的返回值。 - chux - Reinstate Monica
@chux:如果第一个 scanf 函数出现输入错误,那么第二个函数也会出错,因为它们之间没有 clearerr 调用。如果遇到 EOF 也是同样的情况。 - Chris Dodd
C明确表示:“如果流的文件结束指示器被设置,或者流已经到达文件末尾,则设置流的文件结束指示器并且fgetc函数返回EOF。”但是错误指示器没有相应的“或”:“如果发生读取错误,则设置流的错误指示器并且fgetc函数返回EOF”。因此,第二个scanf()可能会返回非EOF,即使第一个由于输入错误而返回EOF - 无论是否有中间的clearerr() - chux - Reinstate Monica

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