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对象的指针。