3.5 标准I/O库

标准I/O库(stdio)及其头文件stdio.h为底层I/O系统调用提供了一个通用的接口。这个库现在已经成为ANSI标准C的一部分,而你前面见到的系统调用却还不是。标准I/O库提供了许多复杂的函数用于格式化输出和扫描输入。它还负责满足设备的缓冲需求。

在很多方面,你使用标准I/O库的方式和使用底层文件描述符一样。你需要先打开一个文件以建立一个访问路径。这个操作的返回值将作为其他I/O库函数的参数。在标准I/O库中,与底层文件描述符对应的是(stream),它被实现为指向结构FILE的指针。


注意,不要把这里的文件流与C++语言中的输入输出流(iostream)以及AT&T UNIX System V Release 3中引入的进程间通信中的STREAMS模型相混淆,STREAMS模型不在本书的讨论范围之内。要想进一步了解STREAMS,请查阅X/Open规范(http://www.opengroup.org)和随System V版本一起提供的AT&T STREAMS Programming Guide(《AT&T STREAMS程序设计指南》)。


在启动程序时,有3个文件流是自动打开的。它们是stdin、stdout和stderr。它们都是在stdio.h头文件里定义的,分别代表着标准输入、标准输出和标准错误输出,与底层文件描述符0、1和2相对应。

在本节里,我们将介绍标准I/O库中的下列库函数:

❑ fopen、fclose

❑ fread、fwrite

❑ fflush-

❑ fseek-

❑ fgetc、getc、getchar

❑ fputc、putc、putchar

❑ fgets、gets

❑ printf、fprintf和sprintf

❑ scanf、fscanf和sscanf

3.5.1 fopen函数

fopen库函数类似于底层的open系统调用。它主要用于文件和终端的输入输出。如果你需要对设备进行明确的控制,那最好使用底层系统调用,因为这可以避免用库函数带来的一些潜在问题,如输入/输出缓冲。

该函数原型如下:

fopen打开由filename参数指定的文件,并把它与一个文件流关联起来。mode参数指定文件的打开方式,它取下列字符串中的值。

❑ "r"或"rb":以只读方式打开。

❑ "w"或"wb":以写方式打开,并把文件长度截短为零。

❑ "a"或"ab":以写方式打开,新内容追加在文件尾。

❑ "r+"或"rb+"或"r+b":以更新方式打开(读和写)。

❑ "w+"或"wb+"或"w+b":以更新方式打开,并把文件长度截短为零。

❑ "a+"或"ab+"或"a+b":以更新方式打开,新内容追加在文件尾。

字母b表示文件是一个二进制文件而不是文本文件。


请注意,UNIX和Linux并不像MS-DOS那样区分文本文件和二进制文件。和把所有文件都看作为二进制文件。另一个需要注意的地方是mode参数,它必须是一个字符串,而不是一个字符。所以总是应该使用双引号,而不是单引号。


fopen在成功时返回一个非空的FILE *指针,失败时返回NULL值,NULL值在头文件stdio.h里定义。

可用的文件流数量和文件描述符一样,都是有限制的。实际的限制是由头文件stdio.h中定义的FOPEN_MAX来定义的,它的值至少为8,在Linux系统中,通常是16。

3.5.2 fread函数

fread库函数用于从一个文件流里读取数据。数据从文件流stream读到由ptr指向的数据缓冲区里。fread和fwrite都是对数据记录进行操作,size参数指定每个数据记录的长度,计数器nitems给出要传输的记录个数。它的返回值是成功读到数据缓冲区里的记录个数(而不是字节数)。当到达文件尾时,它的返回值可能会小于nitems,甚至可以是零。

该函数原型如下:

对所有向缓冲区里写数据的标准I/O函数来说,为数据分配空间和检查错误是程序员的责任。请参见本章后面对ferror和feof函数的介绍。

3.5.3 fwrite函数

fwrite库函数与fread有相似的接口。它从指定的数据缓冲区里取出数据记录,并把它们写到输出流中。它的返回值是成功写入的记录个数。

该函数原型如下:

请注意,我们不推荐把fread和fwrite用于结构化数据。部分原因在于用fwrite写的文件在不同的计算机体系结构之间可能不具备可移植性。

3.5.4 fclose函数

fclose库函数关闭指定的文件流stream,使所有尚未写出的数据都写出。因为stdio库会对数据进行缓冲,所以使用fclose是很重要的。如果程序需要确保数据已经全部写出,就应该调用fclose函数。虽然当程序正常结束时,会自动对所有还打开的文件流调用fclose函数,但这样做你就没有机会检查由fclose报告的错误了。

该函数原型如下:

3.5.5 fflush函数

fflush库函数的作用是把文件流里的所有未写出数据立刻写出。例如,你可以用这个函数来确保在试图读入一个用户响应之前,先向终端送出一个交互提示符。使用这个函数还可以确保在程序继续执行之前重要的数据都已经被写到磁盘上。有时在调试程序时,你还可以用它来确认程序是正在写数据而不是被挂起了。注意,调用fclose函数隐含执行了一次flush操作,所以你不必在调用fclose之前调用fflush。

该函数原型如下:

3.5.6 fseek函数

fseek函数是与lseek系统调用对应的文件流函数。它在文件流里为下一次读写操作指定位置。offset和whence参数的含义和取值与前面的lseek系统调用完全一样。但lseek返回的是一个off_t数值,而fseek返回的是一个整数:0表示成功,-1表示失败并设置errno指出错误。

该函数原型如下:

3.5.7 fgetc、getc和getchar函数

fgetc函数从文件流里取出下一个字节并把它作为一个字符返回。当它到达文件尾或出现错误时,它返回EOF。你必须通过ferror或feof来区分这两种情况。

这些函数的原型如下:

getc函数的作用和fgetc一样,但它有可能被实现为一个宏,如果是这样,stream参数就可能被计算不止一次,所以它不能有副作用(例如,它不能影响变量)。此外,你也不能保证能够使用getc的地址作为一个函数指针。

getchar函数的作用相当于getc(stdin),它从标准输入里读取下一个字符。

3.5.8 fputc、putc和putchar函数

fputc函数把一个字符写到一个输出文件流中。它返回写入的值,如果失败,则返回EOF。

类似于fgetc和getc之间的关系,putc函数的作用也相当于fputc,但它可能被实现为一个宏。

putchar函数相当于putc(c , stdout),它把单个字符写到标准输出。注意,putchar和getchar都是把字符当作int类型而不是char类型来使用的。这就允许文件尾(EOF)标识取值-1,这是一个超出字符数字编码范围的值。

3.5.9 fgets和gets函数

fgets函数从输入文件流stream里读取一个字符串。

fgets把读到的字符写到s指向的字符串里,直到出现下面某种情况:遇到换行符,已经传输了n-1个字符,或者到达文件尾。它会把遇到的换行符也传递到接收字符串里,再加上一个表示结尾的空字节\0。一次调用最多只能传输n-1个字符,因为它必须把空字节加上以结束字符串。

当成功完成时,fgets返回一个指向字符串s的指针。如果文件流已经到达文件尾,fgets会设置这个文件流的EOF标识并返回一个空指针。如果出现读错误,fgets返回一个空指针并设置errno以指出错误的类型。

gets函数类似于fgets,只不过它从标准输入读取数据并丢弃遇到的换行符。它在接收字符串的尾部加上一个null字节。


注意:gets对传输字符的个数并没有限制,所以它可能会溢出自己的传输缓冲区。因此,你应该避免使用它并用fgets来代替。许多安全问题都可以追溯到在程序中使用了可能造成各种缓冲区溢出的函数,gets就是一个这样的函数,所以千万要小心!