5.2 流与FILE对象

open、lseek、read、write和close等函数在标准I/O库中都有对应的函数。为什么还要建立这些看似重复的标准I/O库?原因有二:

一、标准库属于C标准内容之一,不专属于某个操作系统。有了标准I/O库,C语言就具有更好的可移植性。

二、标准库提供了更多标准的I/O操作,使得I/O操作更加便利和简单。

使用read和write读写文件时,一般要经过这么个过程:

1、定义一个文件描述符变量作为操作对象。例如:

int filedes;

2、定义一个数组作为操作空间,所谓的缓冲区。例如:

char buf[BUFSIZ];

3、使用open打开文件获得一个文件描述符。例如:

filedes = open(pathname, O_RDWR | O_CREAT);


4、用read从指定的文件描述符所代表的文件上把数据读到指定的缓冲区。例如:

read(filedes, &buf,BUFSIZ);

5、或者用write从指定的缓冲区把数据写入指定的文件描述符所代表的文件里。例如:

write(filedes, &buf, BUFSIZ);

6、使用close关闭文件。例如:

close(fieldes);

这个过程有这么几个特点(也可以看作是一个缺点):

1、任何一个函数都不会自动给你分配空间,必须自己提供空间buf。

2、要合理的规划空间大小BUFSIZ,这个涉及I/O效率的问题。

3、read和write是直接把数据送往内核的,每调用一次就会产生一次系统调用,系统调用的代价是很高很高的,需要通过一些机制先把要写入内核的数据先存到一些变量中,然后在适当的时候在一次调用write写进去,或调用read一次性读出来。

4、当要向显示器显示数据时,我们需要做很多转换工作。例如要显示整数65。下面代码绝对不是我们所期待的“65”这两个字符,而是显示一个大写字母“A”。

int mynumber = 65;
write(STDOUT_FILENO, &mynumber, 1);

更不能这么写:

write(STDOUT_FILENO, 65, 1);
要调用write输出一个整数65。我们先要构造10个数值(1,2,3,4,5,6,7,8,9,0)和10个字符('1','2','3','4','5','6','7','8','9','0')的ASCII值对应表,还要把“6”和“5”逐位“拆”下来。如果只是写一个函数显示一个整数变量,可能还没那么复杂。要是写一个通用函数,既要显示整数、小数、整数、负数、字符、数组,还要考虑是十进制还是十六进制格式,那就非常繁杂了。

标准I/O库函数就是解决这些问题而提出的。其中:

(1)流:解决上述问题1和问题2,即空间分配问题。

(2)缓冲:解决上述问题3,即I/O效率问题。

(3)格式化输出函数fprintf等解决上述问题4,输入和输出转换问题。


很多书把“流”这个概念解释得像魔鬼那样的抽象而难以琢磨。其实可以这么理解:流是一个FILE类型的数据对象。FILE是一个struct类型的数据类型。大致模样是这样:

typedef struct
{
    int           fd;        /*文件描述符*/
    char          *buffer;   /*缓冲区指针*/
    short         bsize      /*缓冲区大小*/
    char          *curp      /*当前缓冲区位置*/
    ...                      /*其它辅助性定义(在此省略)*/
}FILE;

也就是把前面的分散定义的文件描述符和缓冲区等定义“集成”到一个FILE结构中。这样,我们就可以构造一系列针对FILE结构进行操作的函数,其中包括对FILE对象初始化等等。

所以说FILE是流的数据类型,而流是FILE类型的数据对象。所有标准I/O都是针对流对象展开的。其中:

(1)fopen函数创建了一个流对象,并返回该对象的指针。相当于open函数,不过open函数返回的是文件描述符。fopen函数内部自动创建了一个buffer缓冲区和文件描述符,并自动选择了一个合适的bsize。

(2)fwrite函数向流写入数据,数据实际上是把数据写到FILE对象中的buffer缓冲区,而非系统内核的缓冲区。除非缓冲区已经满了(即FILE对象中curp=buffer+bsize)。如果缓冲区满了,函数内部会自动调用read函数把数据写到内核。

(3)fread函数是从流对象中取出数据,实际上是从FILE对象的buffer缓冲区取得数据,而非直接从内核的缓冲区取得数据。除非缓冲区已经没有数据可以取了(即FILE对象中curp=buffer)。如果缓冲区空了,函数内部会自动调用write函数一次性取出很多数据,直到把缓冲区填满。

(4)如果不需要等到缓冲区填满,而是要马上写到内核,就调用冲洗函数fflush,强制把数据写到内核并清空缓冲区。当然了,写到内核的数据也未必就马上写到磁盘上,内核也有缓冲,甚至一直到磁盘的过程还有很多级缓冲,例如DMA缓冲,还有磁盘设备本身也有缓冲机制。

一句话:流是一个初始化了的FILE对象。而文件指针是FILE对象的指针。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值