8wDlpd.png
8wDFp9.png
8wDEOx.png
8wDMfH.png
8wDKte.png

scanf() 将换行符留在缓冲区中

Michael Foster 1月前

91 0

我有以下程序:int main(int argc,char *argv []){int a,b; char c1,c2; printf(\'输入一些内容:\'); scanf(\'%d \',&a); //第 1 行 printf(&q ...

我有以下程序:

int main(int argc, char *argv[])
{
    int a, b;
    char c1, c2;
    printf("Enter something: ");
    scanf("%d", &a); // line 1
    printf("Enter other something: ");
    scanf("%d", &b); // line 2

    printf("Enter a char: ");
    scanf("%c", &c1); // line 3
    printf("Enter another char: ");
    scanf("%c", &c2); // line 4

    printf("Done"); // line 5

    system("PAUSE");

    return 0;
}

正如我在 C 书中读到的,作者说 scanf() 在缓冲区中留下了一个换行符,因此,程序不会在第 4 行停止让用户输入数据,而是存储换行符 c2 并移动到第 5 行。

是吗?

但是,这种情况只发生在 char 数据类型上吗?因为我没有 int 在第 1、2、3 行中看到数据类型的这个问题。对吗?

帖子版权声明 1、本帖标题:scanf() 将换行符留在缓冲区中
    本站网址:http://xjnalaquan.com/
2、本网站的资源部分来源于网络,如有侵权,请联系站长进行删除处理。
3、会员发帖仅代表会员个人观点,并不代表本站赞同其观点和对其真实性负责。
4、本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
5、站长邮箱:yeweds@126.com 除非注明,本帖由Michael Foster在本站《c》版块原创发布, 转载请注明出处!
最新回复 (0)
  • 两种解决方法。一种是巧妙地捕获多余的空格。

    getchar(); // (1) add `getchar()`
    scanf("%c", &c1);
    
    scanf(" %c", &c1); // (2) add whitespace before %c
    

    另一种是使用 fgets() to 代替 scanf() 。注意: gets() 是不安全的,请参阅 为什么 gets 是危险的

    相比之下,我更推荐第二种方式。因为它更 易读 、更 易于维护 。例如,在第一种方式中,你必须在添加/移动一些 scanf() .

  • @john:可以是一行、两行、三行或更多行 — 编译器不在乎。如果由我自己决定,它将是三行:声明、循环控制和循环主体。表示空循环主体的分号将单独一行,以强调它是故意的(如 K&R 所建议的那样)。

  • @JonathanLeffler 你的意思是写在两行上吗? int c; while ((c = getchar()) != EOF && c != '\n') ;

  • 只要用户没有输入任何其他内容(例如,尾随空格),这种方法就有效。但它不如扫描到下一个换行符的循环好:int c; while ((c = getchar()) != EOF && c != '\n') ;(不在注释中时,写在三行上)。它通常就足够了;但它并非万无一失(你必须记住,傻瓜们非常擅长破坏事物)。

  • 在调用第二个之前 getchar() 使用 scanf() .

    scanf("%c", &c1);
    getchar();  // <== remove newline
    scanf("%c", &c2);
    
  • 有关 scanf 替代品的更多信息:我可以用什么来代替 scanf 进行输入转换?此外,本指南可能对您有所帮助:远离 scanf() 的初学者指南

  • fgets()/getline() 然后使用 sscanf() 或 strtol() 等,与直接使用 scanf() 相比,还有另一个巨大的优势。当程序遇到意外输入时,输入流不会处于未知状态,无法在不丢失数据的情况下进行恢复。

  • 另一个关于 C++ 的答案 中发布的内容 :我建议扔掉 scanf() 它,永远不要使用它,而是使用 fgets() sscanf() .

    原因在于,至少在类 Unix 系统中,默认情况下,CLI 程序运行的终端会在程序看到用户输入之前对其进行一些处理。它会缓冲输入,直到输入换行符,并允许进行一些基本的行编辑,例如使退格键起作用。

    因此,您永远无法一次获得单个字符,或几个单个字符,而只能获得整行。但这不是 eg scanf("%d") 处理的内容,相反,它只处理数字, 然后就此停止 ,其余部分则缓冲在 C 库中,以供将来的 stdio 函数使用。如果您的程序有 eg

    printf("Enter a number: ");
    scanf("%d", &a);
    
    printf("Enter a word: ");
    scanf("%s", word);
    

    并且您输入行 123 abcd ,它会 同时 scanf() 两个 scanf() 当用户按下空格时,第一个不会返回,即使数字结束于此(因为此时行仍然在终端的行缓冲区中);第二个 scanf() 不会等待您输入另一行(因为输入缓冲区已经包含足够的内容来填充转换 %s )。

    这不是用户通常期望的!

    相反,他们期望按下回车键即可完成输入,并且如果按下回车键,您会得到一个默认值,或者一个错误,可能还会建议您直接给出答案。

    您实际上无法使用 来做到这一点 scanf("%d") 。如果用户只是按下 Enter 键,则什么也不会发生。因为 scanf() 仍在等待数字。终端继续发送该行,但您的程序看不到它,因为 scanf() 吃掉了它。您没有机会对用户的错误做出反应。

    这也不是很有用。

    因此,我建议使用 fgets() getline() 一次读取一整行输入。这与终端给出的内容完全匹配,并且始终在用户输入一行后将控制权交给程序。您对输入行的操作取决于您,如果您想要一个数字,您可以使用 atoi() , strtol() ,甚至 sscanf(buf, "%d", &a) 解析数字。 sscanf() 与 不同,它不存在不匹配的情况 scanf() ,因为它读取的缓冲区大小有限,并且当它结束时,它就结束了——该函数不能等待更多。

    (如果文件格式支持如何像任何空格一样略过换行符,则在常规文件上使用fscanf()也可以。对于面向行的数据,我仍然会使用fgets()sscanf() 。)


    因此,不要使用上面提到的方法,而是使用如下方法:

    printf("Enter a number: ");
    
    fgets(buf, bufsize, stdin);
    sscanf(buf, "%d", &a);
    

    或者,实际上,也检查返回值 sscanf() ,这样您就可以检测到空行和其他无效数据:

    #include <stdio.h>
    
    int main(void)
    {
        const int bufsize = 100;
        char buf[bufsize];
        int a;
        int ret;
        char word[bufsize];
    
        printf("Enter a number: ");
        fgets(buf, bufsize, stdin);
    
        ret = sscanf(buf, "%d", &a);
    
        if (ret != 1) {
            fprintf(stderr, "Ok, you don't have to.\n");
            return 1;
        }
    
        printf("Enter a word: ");
        fgets(buf, bufsize, stdin);
    
        ret = sscanf(buf, "%s", word);
        if (ret != 1) {
            fprintf(stderr, "You make me sad.\n");
            return 1;
        }
    
        printf("You entered %d and %s\n", a, word);
    }
    

    当然,如果您希望程序坚持下去,您可以创建一个简单的函数来循环执行, fgets() 直到 sscanf() 用户愿意按照他们所要求的去做;或者直接退出并出现错误。这取决于您认为如果用户不想玩球,您的程序应该做什么。


    您可以执行类似操作,例如,通过循环 getchar() 读取字符直到 scanf("%d") 返回后出现换行符,从而清除缓冲区中剩余的任何垃圾,但这对用户在空行上按 Enter 键的情况没有任何作用。无论如何, fgets() 都会读取到换行符,因此您不必自己执行此操作。

  • @WeatherVane:对于像 123\n 这样的行,\'%d %*c\' 将使格式字符串中的空格占用空格,然后 %*c 占用下一行的第一个非空格字节。(格式字符串中的尾随空格将等待,直到出现非空格字符,如果从文件读取,这可能是可以的,但对于交互式输入则不行。尾随 \' %*c\' 会执行此操作,然后丢弃第一个非空格字符,因此如果现在可以接受等待下一行,则只需使用尾随空格。)

  • 引用 11

    这只会丢弃一个字符。在 % 之前放置一个空格将丢弃任意数量的前导空格(包括无)。

  • 这种技术的问题在于,如果用户输入 a,然后输入空格,然后输入换行符,则抑制字符转换会读取空格,但仍然保留换行符。如果用户在您期望 a 时输入了 supercalifragilisticexpialidocious,则您需要处理很多额外的字符。您永远无法判断尾随抑制转换是否成功——它们不计入 scanf() 的返回值中。

  • 另一个选项(我从 这里 赋值抑制选项 读取并丢弃换行符 。为此,我们只需设置一个格式来读取在 % c :

    scanf("%d%*c",&a); // line 1
    scanf("%c%*c",&c1); // line 3
    

    scanf 然后将读取下一个字符(即换行符)但不会将其分配给任何指针。

    但最后,我还是同意 常见问题解答中的最后一个选项 :

    或者,根据您的要求,您也可以忘记 scanf()/getchar(),使用 fgets() 从用户那里获取一行文本并自己解析。

  • 使用 scanf(" %c", &c2); 。这将解决您的问题。

  • 这个答案很好地解释了 scanf() 读取然后格式化,而不仅仅是根据格式读取。

  • 有关格式字符串中尾随空格的讨论,请参阅 scanf() 格式字符串中的尾随空格和 scanf() 要求输入两次而我期望它只要求输入一次。这是一个坏主意 — 如果您期望人机交互,那么这非常糟糕,对于程序交互来说也不好。

  • NVG 1月前 0 只看Ta
    引用 17

    @chux 在其他情况下,scanf 会清除缓冲区之前的所有空格或忽略它们的输入,但它们仍然存在?

  • scanf () 函数在尝试解析除字符以外的转换之前会自动跳过前导空格。字符格式(主要是 %c ;还有扫描集 %[…] — 和 %n )是例外;它们不会跳过空格。

    使用 " %c" 前导空格可跳过可选的空格。请勿在 scanf() 格式字符串中使用尾随空格。

    请注意,这仍然不会消耗输入流中剩余的任何尾随空格,甚至不会消耗到行尾,因此如果在同一输入流上使用 getchar() or fgets() 跳过空格 其他非字符转换 %d 所做的


    请注意,除转换之外的非空白“指令”(使用 POSIX scanf 术语 ),例如中的文字文本 scanf("order = %d", &order); 也不会跳过空格。文字 order 必须与要读取的下一个字符匹配。

    ,如果您想跳过上一行的换行符但仍然需要在固定字符串上进行文字匹配,那么 " order = %d" 您可能需 就像这个问题一样 .

返回
作者最近主题: