输入输出缓冲机制
1. 概述
缓冲区又称为缓存,它是内存空间的一部分。在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或者输出的数据,这部分预留的空间叫做缓冲区。缓冲区根据其对应的是输入设备还是输出设备,分为输入缓冲区和输出缓冲区。
2. 为什么要有缓冲区
- 减少IO设备的操作
- 提供计算机运行速度
举个例子:从硬盘读取数据时,一次性读取的所需的数据放置在缓冲区,计算机再从缓冲区选择所需要的数据进行处理,而完成本次读取工作的硬盘便可以执行下一次读取工作,能大大提高计算机的运行速度。
3. 缓冲区的类型
-
全缓冲
- 当填满缓冲区后,才会进行实际IO操作。比如对硬盘文件的读写。
- 默认用于磁盘文件(如
fopen
打开的文件流)。
-
行缓冲
- 遇到换行符
\n
时,自自动刷新缓冲区。 - 默认用于终端交互(如
stdout
连接到终端时)。
- 遇到换行符
-
不带缓冲
- 数据直接读写,不经过缓冲区。
- 默认用于错误输出(如
stderr
),确保错误信息即时显示。
系统在启动的时候会默认打开,三个输入输出流(文件)
- stdin:键盘文件
- stdout:显示器文件
- stderr:显示器文件
4. 引发缓冲区的刷新
- 输出缓冲区(stdout)是行缓冲,当遇到换行符的时候,系统会将缓冲区的数据冲刷到标准输出设备上。
- 缓冲区满(溢出)
- 手动刷新执行
fflush(stdout)
缓冲区 - 程序结束,也会冲刷到屏幕终端
#include <stdio.h>
// Linux sleep() 秒
#include <unistd.h>
// window Sleep() 毫秒
// #include <windows.h>
int main()
{
printf("hello world");
sleep(2);
printf("hello world");
fflush(stdout);
printf("hello,直接刷新");
sleep(2);
printf("hello,直接刷新\n");
printf("BUFSIZ: %d\n",BUFSIZ);
pid_t pid = fork();
if (pid < 0)
{
}
else if (pid == 0)
{
printf("hello");
sleep(3);
return 0;
}
else
{
while(1)
{
printf("父线程,PID = %d,子线程PID = %d\n",getpid(),pid);
sleep(1);
}
}
return 0;
输出结果
Linux系统:这个系统下缓冲区表现的明显,会更直观看到是由于换行符\n
,溢出或手动刷新。
window系统:这个系统下缓冲区表现不明显,以上代码是无法看出区别。
C 标准库提供了
BUFSIZ
宏(定义在<stdio.h>
),它表示默认缓冲区的大小(通常是 512 或 4096 字节,取决于系统)。
5. printf
概念
头文件
#include <stdio.h>
函数作用
将指定格式的数据输出到屏幕终端上(输出设备)
函数原型
int printf ( const char * format, ... );
格式符 | 类型 | 输出形式示例 | 说明 |
---|---|---|---|
%d | 有符号十进制整数 | -42 | 普通整数输出 |
%u | 无符号十进制整数 | 42 | 无符号整数(如 unsigned int ) |
%f | 十进制浮点数 | 3.140000 | 默认保留 6 位小数 |
%e | 科学计数法(小写) | 1.234560e+02 | 指数形式(尾数 + e±xx ),默认 6 位小数 |
%E | 科学计数法(大写) | 1.234560E+02 | 同 %e ,但指数符号 E 大写 |
%g | 自动选择 %e /%f | 3.14 或 1.23e+05 | 根据数值大小自动选择更紧凑的格式(短格式优先) |
%c | 单个字符 | A | 输出 ASCII 字符 |
%s | 字符串 | Hello | 输出字符串(以 \0 结尾) |
%p | 指针地址 | 0x7ffd3456 | 以十六进制输出指针地址 |
%x | 十六进制(小写) | ff | 无符号整数的十六进制(字母小写) |
%X | 十六进制(大写) | FF | 无符号整数的十六进制(字母大写) |
%% | 百分号 | % | 输出 % 本身 |
5.1 字段宽度
用户没有明确指定左对齐,则默认采用右对齐输出。
5.1.1 静态指定宽度
场景 | 示例代码 | 输出结果(假设值为 42 或 "AB" ) |
---|---|---|
固定宽度(右对齐) | printf("%5d", 42); | ∙∙∙42 (∙ 代表空格,下同) |
固定宽度(左对齐) | printf("%-5d", 42); | 42∙∙∙ |
字符串宽度控制 | printf("%5s", "AB"); | ∙∙∙AB |
补零代替空格 | printf("%05d", 42); | 00042 |
字符串宽度精确控制 | `printf(" | %.3s |
%Nd
定义的宽度,是最小宽度。所以如果实际数据宽度超过预定的值,会按照实际宽度输出,不会发生截断。
5.1.2 动态指定宽度
可以用 *
占位符,通过参数动态传入宽度值:
int width = 8;
printf("%*d", width, 42); // 等价于 "%8d"
6. scanf
概念
头文件
#include <stdio.h>
函数作用
从屏幕终端上获取指定格式的数据,存储到变量的内存空间上
函数原型
int scanf ( const char * format, ... );
函数返回值
- 成功
- 返回成功匹配并赋值的参数个数
- 失败
- 返回EOF,通常是-1。
格式字符串
(1) 格式字符串中显式添加空格
scanf("%d %d", &a, &b); // 空格分隔两个 %d
读取第一个整数后,跳过任意数量的空白字符(包括换行),再读取第二个整数。
(2) 无空格(紧邻占位符)
scanf("%d%d", &a, &b); // 无空格分隔
与 "%d %d"
效果相同!因为 %d
本身会跳过前导空白字符。
(3) 其他占位符(如 %c
)的特殊情况
char c;
scanf("%c", &c); // %c 不会跳过空白字符!
%c
会读取 任意字符(包括空格、换行符),特别是出现在多字符读取的时候,可能会出现意想不到的效果。
如下:
char a,b;
scanf("%c", &a);
printf("%c",a);
scanf("%c", &b);
printf("%c",b);
//输入 1回车2
会发现我2都没来得及输入,程序就结束了。
其实当输入1 、【回车】时,它两都被写入缓冲区中,而1用于a,【回车】就放在缓冲区,等下一次读取时,直接就读取缓冲区剩下的【回车】,程序就直接结束了。
所以在面对多字符读取时,应当重视缓冲区,避免发生误读。
解决方案:
- 可用上述格式字符串中显式添加空格,可跳过任意数量的空白字符(包括换行)。
- 手动刷新缓冲区
while (getchar() != '\n'); // 清空缓冲区直到换行符
注:以上均为学习笔记。