mirror of
https://github.com/Aexiar/c.git
synced 2024-10-22 14:05:45 +02:00
c
This commit is contained in:
parent
799ca02c75
commit
d56079cccf
BIN
docs/notes/01_c-basic/07_xdx/assets/10.gif
Normal file
BIN
docs/notes/01_c-basic/07_xdx/assets/10.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 234 KiB |
BIN
docs/notes/01_c-basic/07_xdx/assets/9.gif
Normal file
BIN
docs/notes/01_c-basic/07_xdx/assets/9.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 237 KiB |
@ -163,3 +163,105 @@
|
||||
|
||||
### 1.3.3 缓冲区的好处
|
||||
|
||||
* 使用缓冲区的好处就是`减少了 I/O 操作的频率,降低了系统资源的消耗,提高了系统的性能,提升了用户的使用体验`。
|
||||
|
||||
### 1.3.4 缓冲区是如何提高 IO 操作的频率?
|
||||
|
||||
* 对于 C 语言中的 `printf` 函数和 scanf 函数,其功能如下:
|
||||
* `printf` 函数:将程序中的数据输出到外部设备(如:显示器)中。
|
||||
* `scanf` 函数:从外部设备(如:键盘)中读取数据到程序中。
|
||||
* 这些都是非常典型的 IO 操作,并且 IO 的过程效率也是很低的。除了硬件性能本身的差异外,IO 操作的复杂性也是非常重要的因素,每次 IO 操作都会带来一些固定的开销,如:
|
||||
* ① 每次 IO 操作都需要设备初始化和响应等待。
|
||||
* ② 操作系统管理 IO 请求,涉及中断处理和上下文切换,这些都消耗了大量时间。
|
||||
* ③ 应用从用户态切换到内核态的系统调用也会带来额外的时间开销。(IO 操作普遍涉及系统调用)
|
||||
* ④ ...
|
||||
* 如果每输入一个字符或每输出一个字符都需要进行一次完整的 IO 操作,那么这些固定的开销会迅速积累,进而导致系统的性格显著下降。
|
||||
* 硬件层面的效率低下,我们没有办法通过软件层面的优化去解决。但对于这些大量的固定开销,我们可以通过`缓冲区`来进行效率优化。
|
||||
|
||||
> [!IMPORTANT]
|
||||
>
|
||||
> * ① 缓冲区的主要目的是暂时存储数据,然后在适当的时机一次性进行大量的 IO 操作。
|
||||
> * ② 这样,多个小的 I/O 请求可以被组合成一个大的请求,有效地分摊了固定开销,并显著提高了总体性能。
|
||||
|
||||
* 对于 `scanf` 函数而言,当用户通过键盘输入字符的时候,这些输入的字符首先被保存在 `stdin` 的缓冲区中,,`当满足某个触发条件后`,才传递给程序处理。这样就减少了总的 IO 次数,提高了效率。
|
||||
* 对于 `printf` 函数而言,输出的内容首先会保存到 `stdout` 的缓冲区中,`当满足某个触发条件后`,这些内容会一次性写入并显示到屏幕,降低了与显示设备的交互频率。
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> * ① 如果你还不能理解,就可以将 IO 操作想象是搬家。对于搬家而言,需要搬运东西的总量是固定的的,搬一趟的时间也是差不多的。我们当然希望:一次性搬得东西尽量多,搬运的次数尽量少,这样总耗时就少。
|
||||
> * ② 不使用缓冲区,就类似每次搬家只能手提一个东西,频繁的往返。而使用缓冲区,就好比我们使用一个小推车,可以一次性的搬运多个东西,极大的提高了效率。
|
||||
|
||||
### 1.3.5 缓冲区的分类
|
||||
|
||||
* 从上述的内容中,我们可以明确到看到缓冲区有一个显著的特点:`当满足某个触发条件后,程序会开始对缓冲区的数据执行输入或输出操作`。而这种`满足某个条件,就触发数据传输`的行为,就称为`缓冲区的自动刷新`机制。
|
||||
* 基于这种自动刷新的触发条件的不同,我们可以将缓冲区划分为以下三种类型:
|
||||
* ① `全缓冲(满缓冲)`:仅当缓冲区达到容量上限时,缓冲区才会自动刷新,并开始处理数据。否则,数据会持续积累在缓冲区中直到缓冲区满触发自动刷新。`文件操作`的输出缓冲区便是这种类型的经典例子。
|
||||
* ② `行缓冲`:缓冲区一旦遇到换行符,缓冲区就会自动刷新,所有数据都会被传输。`stdout` 缓冲区就是典型的行缓冲区。
|
||||
* ③ `无缓冲(不缓冲)`:在此模式下,数据不经过中间的缓冲步骤,每次的输入或输出操作都会直接执行。这种方法适用于需要快速、实时响应的场合。`stderr`(标准错误输出)就是这种方式,它经常被用来即时上报错误信息。
|
||||
|
||||
* 之前,我们经常会在代码中,会加入以下的代码,其实就是为了让行缓冲变为无缓冲,如下所示:
|
||||
|
||||
```c {6}
|
||||
#include <stdio.h>
|
||||
|
||||
int main() {
|
||||
|
||||
// 禁用 stdout 缓冲区
|
||||
setbuf(stdout, nullptr);
|
||||
|
||||
int num = 0;
|
||||
printf("请输入一个整数:");
|
||||
scanf("%d", &num);
|
||||
printf("你输入的整数是:%d\n", num);
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
* 如果不加入上述的代码,将会这样显示:
|
||||
|
||||
![](./assets/9.gif)
|
||||
|
||||
* 但是,一旦我们加入了上述的代码,将会这样显示:
|
||||
|
||||
![](./assets/10.gif)
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> * ① setbuf 是 C 语言标准库中的一个函数,用于设置文件流的缓冲区。它允许程序员控制 I/O 操作的缓冲行为,从而影响文件流(如 `stdin`、`stdout` 或文件指针 `FILE *` 类型)的效率和顺序。
|
||||
> * ② 其定义,如下所示:
|
||||
>
|
||||
> ```c
|
||||
> /**
|
||||
> * @param stream 缓冲区的文件流
|
||||
> * @param buf 用户提供的缓冲区,如果为 NULL,就是禁用缓冲
|
||||
> */
|
||||
> void setbuf(FILE *stream, char *buf);
|
||||
> ```
|
||||
> * ③ 不同的编译器和开发环境可能会对输出缓冲进行特殊设置,尤其是在调试模式下,以便提供更好的调试体验,例如:微软的 MSVC 在 debug 模式下,即使没有换行符,printf 函数的输出通常也会立即显示在控制台上。这种行为是为了帮助程序员更有效地调试程序,即时看到他们的输出,而不需要固定等待缓冲区刷新条件。但是,遗憾的是,GCC 在 debug 模式中,并没有这么做!!!
|
||||
|
||||
> [!IMPORTANT]
|
||||
>
|
||||
> * ① 无论是哪种类型的缓冲区,当缓冲区满了时,都会触发自动刷新。
|
||||
>
|
||||
> * 全缓冲区:唯一的自动刷新条件是缓冲区满。
|
||||
>
|
||||
> * 行缓冲区:除了缓冲区满导致的自动刷新,还有遇到换行符的自动刷新机制。
|
||||
>
|
||||
> * ② 手动刷新:大多数缓冲区提供了手动刷新的机制,如:使用 `fflush` 函数来刷新 stdout 缓冲区,也可以使用 `setbuf` 函数来禁用缓冲区。
|
||||
> * ③ `输出缓冲区中的数据需要刷新才能输出到目的地,但输入缓冲区通常不需要刷新,强制刷新输入缓冲区往往会引发未定义行为。`
|
||||
> * ④ 当程序执行完毕(如:main函数返回)时,缓冲区通常会自动刷新,除此之外,还有一些独特的机制也可以刷新缓冲区。但这些机制可能因不同的编译器或平台而异,不能作为常规手段。`强烈建议依赖手动或者常规自动刷新的机制来完成缓冲区的刷新。`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# 第二章:printf 函数
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# 第三章:scanf 函数
|
||||
|
Loading…
Reference in New Issue
Block a user