天天看点

Linux c学习--从标准输入输出看流和缓冲区

      学习标准输入输出,我们都会遇到一个概念,流和缓冲区,但到底什么是流,什么是缓冲区呢?

      书《C Primer Plus》上说,C程序处理一个流而不是直接处理文件。后面的解释十分抽象:『流(stream)是一个理想化的数据流,实际输入或输出映射到这个数据流』。这个流具体是一个怎么样的东西呢?

      流这个定义非常的形象。我们可以这样理解:

     你声明一个FILE *fp ,并把fopen(某个文件)返回的值赋予fp这两个动作就相当于建立了一个水龙头,当你用getc(fp)之类的输入函数读取文件字符时就相当于拧开了水龙头,每读取一个字符,这个文件就像水一样的流动一下,fp所指的地址自然就向后移动了一位。

int ch;
while((ch=getc(fp))!=EOF)
	putchar(ch);
           

        你看这个循环,可以读取一个文件的所有字符。如果不是流的话,ch永远是第一个字符,不会更新。也可以理解为,fp自动++(一个字符的大小)。      但流的概念意味着什么呢?

--流是独立于设备之外而操纵外设一种逻辑手段。

--大多数外设都是互异的,所以(操纵)它们需要专门的编程技术。

--流对程序员隐藏这些不同点,而准许他们以同样的方式来处理大多数外设。

--考虑到一连串的字符需要一次读一个,流(相当于)是具有缓冲作用的接口。

--个人计算机都是基于流架构的。

各大权威对流的说法有些不一致,我认为流既是数据的源或目的地的抽象,也是源和目的地之间流动信息的表示。但流起码都暗含以下的几个方面:

1、流是一个抽象的概念,是对信息的一种表达;在程序中,流就是对某个对象输入输出信息的抽象。就像运输工具是对一切运动载体的抽象一样。

2、流是一种“动”的概念,静止存储在介质上的信息只有当它按一定的序列准备“运动”时才称为流。“从程序移进或移出字节”就是“动”的表现。静止的信息具有流的潜力,但不一定是流,就像没有汽油不能行走的汽车一样,它具有运输工具的潜力,但它还不是运输工具(因为它很有可能被当作房子来用了,我就在大街上看见有精明的商人用火车车厢来做酒吧)。

3、流有源头也有目的地;程序中各种移动的信息都有其源和目的,记得编程(特别是汇编)时,老是要确定好某个操作的源操作数和目的操作数。借用佛教一言也即是:“万物皆有因果”,这也就像长江一样,西自唐古拉,而东去太平洋。在高速公路上飞跑的汽车,它必有其出发地和目的地。

4、流一定带有某种信息,没有任何内容的流带着自身来表达“空”信息。就像运输工具一样,它不运货的时候就运着自己这一身的零件(包括驾驶员)并把一样东西运到目的地,那就是它自己和一个“跑空车”的信息。流有最小的信息单元就是二进制位,含有最小的信息包就是字节,C标准库提供两种类型的流:二进制流(binary stream)和文本流(text stream)。二进制流是有未经处理的字节构成的序列;文本流是由文本行组成的序列。而在著名的UNIX系统中,文本流和二进制流是相同的(identical)。

5、流有源头也有目的地,那么它必定与源头和目的地相关联。但人们操作流的时候,最关心的还是其目的地,也就是一个定向(orientation)的意思,就像司机运货一样,它首要关心的问题是目的地,而非起点(操作者都知道)。在C语言中,通过打开流来关联流及其目的地,使用的函数是fopen(),该函数返回一个指向文件的指针(FILE *),该指针包含了足够的可以控制流准确地到达目的地的信息。

FILE是一个结构体(摘自TC2.0中stdio.h文件)  

/* Definition of the control structure for streams
*/
typedef struct  {
        short           level;          /* fill/empty level of buffer */
        unsigned        flags;          /* File status flags    */
        char            fd;             /* File descriptor      */
        unsigned char   hold;           /* Ungetc char if no buffer */
        short           bsize;          /* Buffer size          */
        unsigned char   *buffer;        /* Data transfer buffer */
        unsigned char   *curp;          /* Current active pointer */
        unsigned        istemp;         /* Temporary file indicator */
        short           token;          /* Used for validity checking */
}       FILE;                           /* This is the FILE object */

           

     将它称为流控制结构体(control structure for streams)真好表现出其功能来。举个例子就好像一卡车司机要把货物运到X公司,公司主管就会给他一张地图及X公司的基本信息,这些材料所提供的信息如果足够的话,那么它就能指导着司机准确地将货物送达了。C中FILE这个结构体所起的作用就好像是运输公司把一切有用的指导信息封装起来的档案袋一样。而已有关联的流要终止这种关联,就必须关闭流,使用的函数是fclose(),就像运货公司若不再给X公司运货了,那么他们就必须要终止合作协议了。

    这里要注意的是:C语言中stdin、stdout、stderr分别是标准输入流、标准输出流及标准出错流的逻辑目的,他们都默认对应相应的物理终端。在程序运行伊始,不需要进行open()操作,流自动打开。

  那缓冲区又是什么意思呢? 缓冲区(Buffer):

    为了匹配计算机快速设备和慢速设备间的通信步伐,计算机中大量使用硬件缓冲区(如CPU中的Cache,内存相对于硬盘和CPU),流是传输信息的一种逻辑表示,对流的各种不同操作也可能存在使用缓冲的需求。但是这里的buffer只是一种逻辑概念,不是物理设备。缓冲区存在于流与具体的设备终端或者存储介质上的文件之间。就好像运货到一个公司里一样,合同上的要求是运到X公司,但是实际上是真的把货物运到X公司的总部大楼吗?不是。应该是运到X公司的仓库中。这里的仓库就有点像我们所说的缓冲区了。也可以这么说,流运动到目的,先经过的是缓存区。   以scanf() printf()为例:                      •   缓冲区(流)负责在输入/输出设备和程序之间建立联系。 –输入设备->内存缓冲区(stdin)->程序 –程序->内存缓冲区(stdout)->输出设备 •   是一块临时的存储区域,或在内存中,或在设备的控制卡上     . 缓冲类型。

标准库提供缓冲是为了减少对read和write的调用。提供的缓冲有三种类型(整理自APUE):

  • 全缓冲。

在这种情况下,实际的I/O操作只有在缓冲区被填满了之后才会进行。对驻留在磁盘上的文件的操作一般是有标准I/O库提供全缓冲。缓冲区一般是在第一次对流进行I/O操作时,由标准I/O函数调用malloc函数分配得到的。

术语flush描述了标准I/O缓冲的写操作。缓冲区可以由标准I/O函数自动flush(例如缓冲区满的时候);或者我们对流调用fflush函数。

  • 行缓冲
在这种情况下,只有在输入/输出中遇到换行符的时候,才会执行实际的I/O操作。这允许我们一次写一个字符,但是只有在写完一行之后才做I/O操作。一般的,涉及到终端的流--例如标注输入(stdin)和标准输出(stdout)--是行缓冲的。
  • 无缓冲
标准I/O库不缓存字符。需要注意的是,标准库不缓存并不意味着操作系统或者设备驱动不缓存。

当然,我们常用的scanf()  与  printf() 属于行缓冲,下面我们来看个例子,可以帮助我们理解缓冲区在标准输入输出中的作用:

<pre class="cpp" name="code">#include <stdio.h>

int main()
while(1);
{
	printf("hello world");
	while(1);
}
           

我们看看输出结果:

[email protected]:~/qiang/char1$ gcc -o 1 1.c
[email protected]:~/qiang/char1$ ./1

           

打出是个空的,为什么呢?

我们上面提到标准输入输出是行缓冲,即一行满了才会刷新,那什么是刷新呢?刷新就是将数据从缓冲区取出来,真正能刷新,要满足什么条件呢?

1、满刷新,即一行满了(1024个字节)才会刷新;

2、遇到'\n'会刷新;

3、调用fflush()函数;

4、程序结束 fclose();

我们可以看到上面的程序,应为有while(1),程序一直没有结束,没有'\n',没有满行,没有fflush(),所以并不会输出;

这样理解的话,我们可以改动一下了,就写一个吧,加'\n':

#include <stdio.h>

int main()
{
	printf("helloworld\n");
	while(1);
}
           

执行结果如下:

[email protected]:~/qiang/char1$ gcc -o 1 1.c
[email protected]:~/qiang/char1$ ./1
helloworld

           

可以看到打印出来了,其他方法在这就不写了,大家可有从这个简单的例子中看到缓冲区与流的概念,上面写得可能比较乱,晚上时会进行再整理;

继续阅读