最最最详细的C语言教程笔记零起步(10)进阶必备 同笔者一起学习

二十二. 输入输出缓存

1. 探究printf的现象

我们分别在windows系统和linux系统上使用代码做以下操作: 输出"HelloWorld"十次,每次输出后暂停500毫秒。

1.1 windows系统上的代码

//  windows系统 
#include <stdio.h>
#include <windows.h> 
int main() 
{ 
    for (int i = 0; i < 10; i++)     
    {     
        printf("Hello World %d", i);         
        Sleep(500); //  使用Sleep函数休眠500毫秒     
     }     
return 0; 
}

在windows系统上,我们使用 windows.h 头文件中提供的 Sleep 函数,每次输出后休眠500毫秒。

1.2 linux系统上的代码

//  linux系统 
#include <stdio.h> 
#include <unistd.h> 
int main() 
{ 
    for(int i = 0; i < 10; i++)     
    {    
         printf("Hello World %d", i);         
         usleep(1000 * 500); //  使用usleep函数休眠500毫秒     
     }     
return 0; 
}

在linux系统上,我们使用 unistd.h 头文件中提供的 usleep 函数,每次输出后休眠500毫秒。 usleep 的单位为1微秒,1000微秒为1毫秒。代码中给 usleep 传入1000 * 500,表示500毫秒。

两份代码除了休眠使用的函数不同,其他都是一致的,按理说效果也应当一致。我们来看看运行后的具体情况。

1.3 windows系统上的表现

在这里插入图片描述

windows系统上打印出一个 HelloWorld 后,休眠500毫秒,再打印下一个。

1.4 linux系统上的表现

在这里插入图片描述
linux系统上,休眠5000毫秒,打印出所有 HelloWorld 。

为什么呢?
计算机为了减少不必要的操作开销。为了解释这一现象,所以要学习输入输出缓存区、

2. 输入输出缓存区

2.1 输出缓存区

在向控制台打印字符时,程序会先将需要打印的字符串放在输出缓存区中,到特定时刻,再一起显示到控制台。

缓存区就类似于存放脏衣服的桶子,里面存放了一定量的数据,等待批量处理。

在计算机中,需要将显示在屏幕上的数据发送至显卡,再由显卡进行显示。显然,累积一串字符再批量处理发送,比起单个单个发送更有效率。

2.1.1 何时刷新输出缓存

我们将缓存中的数据发送至目的地并清空缓存,这一行为称之为 刷新缓存 。
在windows系统下,使用printf后,数据被写入到输出缓存区。随后,立即刷新缓存区
在linux系统下,使用printf后,数据被写入到输出缓存区。后续的printf会在缓存区中累积数据。直到程序结束才刷新缓存区。

2.1.2 行(háng)缓存的刷新时机

输入输出缓存属于行缓存,即一行结束后必须刷新缓存。
另外,还有一种缓存形式为完全缓存,这种缓存的形式需要等到整个缓存区被填满,才会刷新缓存。
对于行缓存,如果我们想要刷新缓存,只要将一行结束即可。那么文本中如何表示一行结束呢,当然是换行符 ‘\n’ 了。

//  linux系统 
#include <stdio.h> 
#include <unistd.h> 
int main() 
{ 
    for(int i = 0; i < 10; i++)     
    {    
         printf("Hello World %d\n", i);  //  字符串末尾加上‘\n’       
         usleep(1000 * 500);             //  使用usleep函数休眠500毫秒     
     }     
return 0; 
}

我们将printf输出的字符串末尾加上’\n’,再将其运行在linux系统上,观察程序执行的行为。
在这里插入图片描述

程序的运行效果为每一行HelloWorld逐行显示了。
在windows系统中似乎不那么在乎一行字符是否结束。但是在linux下却严格遵循一行字符结束才刷新缓存。
注意输出缓存是一个系统特性,而不是函数特性。所有输出函数,包括printf,putchar等,均存在输出缓存。

2.2 输入缓存区

类似于输出函数(如printf,putchar)存在输出缓存,输入函数(如scanf,getchar)也存在输入缓存。

并且这些输入函数属于阻塞函数,当输入缓存区没有内容时。程序将阻塞在输入函数中,等待用户从键盘键入字符,并按回车确认。

当我们按下了回车键(即换行,存储为’\n’),输入的字符串将进入输入缓存区。

接下来,输入函数将从输入缓存区获取字符,删除缓存区中已获取的字符,并解除阻塞状态继续执行代码。

2.2.1 输入缓存区仍有数据 getchar不阻塞
#include <stdio.h> 
 int main() 
 { 
     char c1, c2;     
     c1 = getchar();     
     putchar(c1);     
     c2 = getchar();     
     putchar(c2);     
     return 0; 
}

上面的代码期望输入一个字符,程序打印这个字符。再输入第二个字符,程序再打印第二个字符。 我们来看一下它的执行效果。

在这里插入图片描述程序运行到第一个getchar,由于输入缓存区没有数据,getchar进入阻塞状态,等待用户输入。
在输入’A’并按回车后,"A\n"进入了输入缓存区。
第一个getchar获取了字符’A’,解除阻塞状态,继续执行。接着用putchar打印c1。
执行到第二个getchar时,由于缓存区内仍有字符’\n’。getchar无需阻塞等待输入,直接获取了字符’\n’, 接着用putchar打印c2。
换行符’\n’被当做了第二个getchar的输入,显然,我们不希望这样。我们可以用一个getchar来吸收第一 次输入的’\n’。

#include <stdio.h> 
 int main() 
 { 
     char c1, c2;     
     c1 = getchar();     
     putchar(c1);     
     getchar();  //  用于吸收'\n'     
     c2 = getchar();     
     putchar(c2);     
     return 0; 
 }

在这里插入图片描述
由于输入缓存区仍有字符’\n’,第二个getchar将不进入阻塞状态,直接读取缓存区中的’\n’,并继续执行。

运行到第三个getchar时,缓存区已没有数据,因此第三个getchar将进入阻塞状态。 在我们输入了字符’B’并按回车后。字符’B’被第三个getchar获取,并被其后的putchar打印。

2.2.2 使用getchar读取输入字符串

除了用scanf读取一行字符串输入外,getchar也能做到同样的功能,并且更加灵活。

#include <stdio.h> 
int main() 
{ 
    char str[20];     
    int i = 0;     
    while(i < 20 - 1)     
    {    
         char c;         
         c = getchar();         
         str[i++] = c;         
         if (c == '\n')         
         {      
               break;         
         }     
      }     
      str[i] = '\0';     
      printf(str);     
      return 0; 
}

代码中声明了一个长度为20的字符数组,这个字符数值可以存储19个字符加上1个结束标记 ‘\0’ 。 接下来,我们用循环getchar来依次从输入缓存区中读取字符。最多读取19次,第20个元素要留给 ‘\0’ 。 此外,如果遇到了’\n’就说明已经是一行字符结束了,可以停止循环了。 停止循环后,记得在数组中存储的字符串最末尾加上 ‘\0’ ,以标记这个字符串结束。 我们输入超过19个字符试试看。

在这里插入图片描述

超过19个字符后,循环将退出且还剩"d\n"两个字符未读取。所以,输出的结果少了"d\n"。 既然这两个字符仍然留在输入缓存区中,我们再使用scanf读取完输入缓存区吧。

#include <stdio.h> 
 int main() 
 { 
     char str[20];     
     int i = 0;     
     while(i < 20 - 1)     
     {    
          char c;         
          c = getchar();         
          str[i++] = c;         
          if (c == '\n')         
          {       
               break;         
          }     
      }     
      str[i] = '\0';     
      printf(str);
    //  分割线     
      printf("\n--------------\n");
    //  继续读取循环中未读取完的字符     
      scanf("%s", str);     
      printf(str);     
      return 0; 
   }

在这里插入图片描述
此时,由于输入缓存区内仍有数据,我们将观察到scanf不进入阻塞状态,直接从缓存区中读取数据到 str 中。先将 ‘d’ 放到字符数组 str 中,其后遇到了 ‘\n’ ,scanf认为一行结束了,便将 ‘\0’ 附到字符数组中 的 ‘d’ 后。最后,str被printf打印在控制台上,内容为 “d”。

3. 不带缓存的输入函数

在前面我们介绍了带缓存的输入函数,只有当按下回车键后,输入的字符串才进入输入缓存区,等待程 序进行读取。
而下面即将介绍的不带缓存的输入函数,只要按下键盘,程序就立即能获取到输入的字符。

3.1 getch函数

#include <stdio.h> 
#include <conio.h> 
int main() 
{  
    while(1)     
    {    
         char c;         
         c = getch();         //  输入后,使用putchar打印在控制台上         
         putchar(c);         
         if (c == 'q')         
         {      
             break;         
         }     
     }     
 return 0; 
 }

输入"1234567890q",程序立刻显示"1234567890q"并退出。

在这里插入图片描述
getch 函数相当于无缓存的 getchar 。
程序运行到getch时函数将进入阻塞状态,并等待键盘直接输入一个字符。
按下一个键后(不需要回车送入输入缓存区), getch 函数立刻就能收到对应的字符,随后通过 putchar 打印在控制台上。

3.2 getche函数

#include <stdio.h> 
#include <conio.h> 
int main() 
{  
    while(1)     
    {     
        char c;         
        c = getche();         //  输入后,getche自己会将字符打印在控制台上。         
        if (c == 'q')         
        {       
             break;         
        }     
     }     
   return 0; 
}

getche 函数与 getch 函数类似,不过它会自己将输入的字符打印在控制台上。
getche 函数相当于无缓存有回显的 getchar 。
请注意这两个函数需要包含头文件 conio.h 才可以被使用。

在这里插入图片描述

conio.h 不是一个标准头文件,在windows下默认可以使用。近些年,为了区别平台实现函数与C语言标准函数。平台实现函数前会加上下划线。 getch , getche 这些平台实现函数,被更名为 _getch ,_getche 。
例如,在Visual Studio 2019\2022中,若使用 getch 函数,编译器会报错并提示改为 _getch 函数。

在这里插入图片描述

3.3 无缓存函数直接从键盘输入

#include <stdio.h> 
#include <conio.h> 
 int main() 
 {
     char c = getchar();     
     putchar(c);     
     c = getch();     
     putchar(c);     
     c = getchar();     
     putchar(c);     
     return 0; 
 }

在这里插入图片描述

第一个getchar将进入阻塞状态,等待用户输入并按下回车,将数据送到缓存区。 我们输入了字符串"123\n",第一个getchar将获取字符’1’,现在缓存区中的数据为"23\n"。 第一个getchar解除阻塞状态。随后’1’被putchar打印到控制台。 程序运行至getch,虽然输入缓存区中仍有数据,但是getch函数将阻塞等待键盘直接输入一个字符。

输入’A’后,getch解除阻塞状态。‘A’被putchar打印到控制台。 第二个getchar将不进入阻塞状态,读取缓存区中的字符’2’。随后putchar将打印’2’到控制台。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

没有余地 EliasJie

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值