C 语言学习笔记:刷新或禁用输出缓冲区

2024 年 09 月 29 日 23:03:21 周日

问题 1

下面这段程序在 Clion 终端中运行时,会先要求用户输入 (scanf),然后再打印 (printf) 文本,执行顺序反了:

#include <stdio.h>
int main(void)
{
    float money;
    printf("输入金额: ");
    scanf("%f", &money);
    return 0;
}

原因分析

unix 上标准输入输出都是带有缓存的,一般是行缓存。

对于标准输出,需要输出的数据并不是直接输出到终端上,而是首先缓存到某个地方,当遇到行刷新标志或者该缓存已满的情况下,才会把缓存的数据显示到终端设备上。

ANSI C 中定义换行符 \n​ 可以认为是行刷新标志。所以,printf 函数没有带 \n​ 是不会自动刷新输出流的,直至缓存被填满。

在问题 1 的情况中,printf 函数的输出结果并不会立即输出到屏幕上,而是会先存储在缓冲区中。当程序执行到 scanf 函数时,会等待用户的输入,直到用户输入完成并按下回车键时,scanf 函数才会返回,此时缓冲区的内容才会被刷新并输出到屏幕上。

缓冲区刷新的条件:

  1. 进程结束。
  2. 遇到 \n​ 。
  3. 缓冲区满(printf()​ 函数的缓冲区大小为 1024 个字节,当超出缓冲区大小时缓冲区就会被刷新)。
  4. 手动刷新缓冲区 fflush(stdout)​ 。
  5. 调用 exit(0)​ ;但是还可以调用 _exit(0)​ ,不刷新缓冲区。

一般的解决办法

printf()​ 里加 \n​ 或新增一条语句输出 \n

printf("输入金额: ");
printf("\n");
scanf("%f", &money);

实测在问题 1 中这个方法无效,略。

使用 fflush(stdout);​ 强制刷新输出缓冲区

printf("输入金额: ");
fflush(stdout);
scanf("%f", &money);

使用 setvbuf(stdout,NULL,_IONBF,0);​ 或 setbuf(stdout, NULL);​ 禁用输出缓冲区

#include <stdio.h>
int main(void)
{
    setvbuf(stdout,NULL,_IONBF,0); //或 setbuf(stdout, NULL);
    float money;
    printf("输入金额: ");
    scanf("%f", &money);
    return 0;
}

setbuf(stdout, NULL);​ 用于将标准输出设置为无缓冲,这意味着每次调用 printf​ 时,输出会立即刷新,不会在内存中积累。它简单直接,但只支持无缓冲模式。

setvbuf(stdout, NULL, _IONBF, 0);​ 也是将标准输出设置为无缓冲,但提供了更多灵活性,允许用户自定义缓冲方式(如行缓冲或全缓冲)。因此,setvbuf​ 更适合需要控制缓冲行为的场景。

问题 2

参考:C 语言清空(刷新)缓冲区,从根本上消除那些奇怪的行为 - 朴素贝叶斯 - 博客园

运行以下程序时,在终端输入 a=3​ ,会立即输出 a=23, b=2​ 并结束程序,跳过了 b​ 的输入:

#include <stdio.h>
int main()
{
    int a = 1, b = 2;
    char c;
    scanf("a=%d", &a);
    scanf("b=%d", &b);
    printf("a=%d, b=%d\n", a, b);
    return 0;
}

如果改为要求直接输入数字的话是能正确执行的:

scanf("%d", &a);
scanf("%d", &b);

这种情况下即使在开头使用 setvbuf(stdout,NULL,_IONBF,0);​ 或者在中间使用 fflush(stdout);​ 都不能解决问题:

setvbuf(stdout,NULL,_IONBF,0);
scanf("a=%d", &a);
fflush(stdout);
scanf("b=%d", &b);

彻底的解决方法

使用 getchar()​ 清空缓冲区

使用 while((c = getchar()) != '\n' && c != EOF);​ :

关键之处在于,getchar() 是带有缓冲区的,并且一切字符通吃,或者说一切字符都会读取,不会忽略。

该代码不停地使用 getchar()​ 获取缓冲区中的字符,直到遇见换行符 \n​ 或者到达文件结尾才停止。

scanf("a=%d", &a);
while((c = getchar()) != '\n' && c != EOF); //在下次读取前清空缓冲区
scanf("b=%d", &b);

使用 scanf()​ 清空缓冲区

使用 scanf("%*[^\n]"); scanf("%*c");​ :

scanf()​ 有一种高级用法,就是使用类似于正则表达式的通配符,这样它就可以读取所有的字符了,包括空格、换行符、制表符等空白符,不会忽略它们。并且,scanf()​ 还允许把读取到的数据直接丢弃,不用赋值给变量。

第一个 scanf()​ 将逐个读取缓冲区中 \n​ 之前的其它字符,%​ 后面的 *​ 表示将读取的这些字符丢弃,遇到 \n​ 字符时便停止读取。
此时,缓冲区中尚有一个 \n​ 遗留,第二个 scanf()​ 再将这个 \n​ 读取并丢弃,这里的星号和第一个 scanf()​ 的星号作用相同。

由于所有从键盘的输入都是以回车结束的,而回车会产生一个 \n​ 字符,所以将 \n​ 连同它之前的字符全部读取并丢弃之后,也就相当于清除了输入缓冲区。

scanf("a=%d", &a);
scanf("%*[^\n]"); scanf("%*c"); //在下次读取前清空缓冲区
scanf("b=%d", &b);
  • C

    C 语言是一门通用计算机编程语言,应用广泛。C 语言的设计目标是提供一种能以简易的方式编译、处理低级存储器、产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。

    85 引用 • 165 回帖 • 1 关注
  • JCLearnC

    Jeffrey Chen 的 C 语言学习笔记

    2 引用
1 操作
JeffreyChen 在 2024-10-02 16:25:52 更新了该帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...