代码改变世界

linux下的标准I/O库

2011-11-24 16:49  马哈鱼  阅读(1077)  评论(0编辑  收藏  举报

标准I/O库处理很多细节,以方便用户使用,但是如果不深入了解其操作,也会带来一些问题。

一 流和FILE对象
当使用标准I/O库打开或创建一个文件时,已经使一个流和一个文件相关联。流的定向决定了所读写的字符是单字节(ASCII字符集)还是多字节(国际字符集)。一个流被创建时,是没有定向的。如果在未定向的流上使用一个多字节IO函数(<wchar.h>),则将该流的定向设置为宽定向的;若在未定向的流上使用一个单字节IO函数,则将该流的定向设置为字节定向的。只有两个函数可以改变流的定向,freopen清除一个流的定向(稍后介绍),fwide设置一个流的定向。
#include<stdio.h>
#include<wchar.h>
int fwide( FILE *file_p, int mode );
返回值:若流是宽定向的返回正值,若流是字节定向的返回负值,若流是未定向的返回0。mode参数的值不同,fwide执行的动作也不同:
mode > 0,fwide将试图使流是宽定向的;
mode < 0,fwide将试图使流是字节定向的;
mode = 0,fwide将不设置流的定向,但返回标示流定向的值。
注意:fwide并不改变已定向流的定向。fwide没有出错返回,要在调用fwide之前清除errno,调用之后检查errno。

二 缓冲
缓冲的目的是为了减少read和write的次数,并自动管理I/O流的缓冲。标准I/O有三种缓冲:
1 全缓冲:在标准I/O的缓冲区满之后才进行实际的I/O操作。缓冲区可以由标准I/O例程自动冲洗,也可以使用函数fflush冲洗一个流(将缓冲区的内容写到磁盘上)。
2 行缓冲:在输入输出中遇到换行符时,标准I/O库执行实际的I/O操作。当流涉及终端时,使用行缓冲。
两个限制:(1)因为行缓冲区的长度是固定的,当行缓冲区满,即使还没有写入一个换行符,也要进行I/O操作;
(2)通过标准I/O库从一个不带缓冲的流或一个行缓冲的流读入数据时,将会冲洗所有的行缓冲输出流。
3 不带缓冲:标准I/O库不对字符进行缓冲。
很多系统默认使用下列类型的缓冲:
(1)标准出错是不带缓冲的;(2)如涉及终端设备的流是行缓冲的,否则是全缓冲的。

#include<stdio.h>
void setbuf( FILE *restrict file_p, char *restrict buf );
int setvbuf( FILE *restrict file_p, char *restrict buf, int mode, size_t size );
这两个函数应该在流被打开后调用,并且应该在对该流执行任何其他操作之前调用。
为了带缓冲I/O,setbuf函数的buf参数必须指向一个长度为BUFSIZE的缓冲区(该常量定义在<stdio.h>中),在此之后该流就是全缓冲的,但如果该流和一个终端设备关联,那么某些系统也可将其设置为行缓冲。当buf为NULL时,关闭缓冲。下表列出了setbuf和setvbuf的比较:

————————————————————————————————————————————
|函数      | mode参数  |        buf参数 |                           缓冲区及长度 | 缓冲类型          |
|————|——————|———————|————————————————|————————|
|setbuf  |                 | 非空             | 缓冲区buf的长度为BUFSIZE       | 全缓冲或行缓冲  |
|           |                 | NULL           | 无缓冲区                                 | 不带缓冲          |
|————|——————|———————|————————————————|————————|
|setvbuf |     _IOFBF |             非空 |               缓冲区buf的长度为size | 全缓冲            |
|           |                 |           NULL | 系统自动分配缓冲区(BUFSIZE)  |                     |
|           |      _IOLBF |             非空 |               缓冲区buf的长度为size | 行缓冲            |
|           |                 |            NULL |  系统自动分配缓冲区(BUFSIZE)|                     |
|           |     _IONBF |              忽略 |                                 无缓冲区 | 不带缓冲         |
————————————————————————————————————————————

一般而言,应该由系统选择缓冲区的长度,并自动分配缓冲区,这样在关闭流时,标准I/O库将自动释放缓冲区。

#include<stdio.h>
int fflush( FILE *file_p );
fflush强制使该流所有未写的数据都被写入磁盘。如果file_p == NULL,将导致所有输出流被冲洗。

三 打开流
#include<stdio.h>
FILE *fopen( const char *restrict path_name, const char *restrict type );
FILE *freopen( const char *restrict path_name, const char *restrict type, FILE *restrict file_p );
FILE *fdopen( int file_des, const char *type );
这些函数都打开一个标准I/O流,区别是:
(1)fopen打开一个指定的文件;
(2)freopen在一个指定的流上打开一个指定的文件,如果该流已经打开,则先关闭该流;如果该流已经定向,则freopen清除该定向。此函数用于将一个指定的文件打开为一个预定义的流:标准输入,标准输出或标准出错。
(3)fdopen返回一个与现有的文件描述符相关联的标准I/O流。常用于由管道和网络通信通道函数返回的文件描述符,因为特殊类型的文件不能用标准I/O库函数fopen打开。
#include<stdio.h>
int close( FILE *file_p );
关闭一个打开的文件流。在该流被关闭前,冲洗缓冲区中的输出数据,丢弃缓冲区中的任何输入数据,释放缓冲区。当一个进程正常终止时,所有带有未写数据的标准I/O流都会被冲洗,所有打开的标准I/O流都会被关闭。

四 读和写流
#include<stdio.h>
int getc( FILE *file_p );
int fgetc( FILE *file_p );
int getchar( void );
getc和fgetc的区别是:getc可以被实现为宏,fgetc不能被实现为宏。这意味着:
1 getc的参数不应当是具有副作用的表达式;
2 fgetc是一个函数,可以将fgetc的地址作为一个参数传递给另一个函数;
3 调用fgetc的时间通常长于调用getc。
为了区分出错还是到达文件尾端,必须调用ferror或feof。
#include<stdio.h>
int ferror( FILE *file_p );
int feof( FILE *file_p );
void clearerr( FILE *file_p );

#include<stdio.h>
int ungetc( int c, FILE *file_p );
ungetc将字符压送回流中,一次只送回一个字符。回送的字符不必一定是上次读到的字符。不能回送EOF。但是当到达文件尾时,仍可以回送一个字符,因为一次成功的ungetc调用会清除该流的文件结束符标志。

#include<stdio.h>
int putc( int c, FILE *file_p );
int fputc( int c, FILE *file_p );
int putchar( int c );
与输入函数一样,putc可以实现为宏,而fputc却不能。

五 读写一行I/O
#include<stdio.h>
char *fgets( char *restrict buf, int n, FILE *restrict file_p );
char *gets( char *buf );
gets从标准输入读取,fgets从指定的流读取。fgets函数一直读取直到下一个换行符,但是不超过n-1个字符,buf以NULL结束。不推荐gets,因为不能指定它的缓冲区buf的长度。
#include<stdio.h>
int fputs( const char *restrict str, FILE *restrict file_p );
int puts( const char *str );

六 二进制I/O
#include<stdio.h>
size_t fread( void *restrict ptr, size_t size, size_t nobj, FILE *restrict file_p );
size_t fwrite( const void *restrict ptr, size_t size, size_t nobj, FILE *restrict file_p );

七 定位流
#include<stdio.h>
long ftell( FILE *file_p );
int fseek( FILE *file_p, long off_set, int whence );
这两个函数假定文件的位置可以存放在一个长整型中。
void rewind( FILE *file_p );

off_t ftello( FILE *file_p );
int fseeko( FILE *file_p, off_t off_set, int whence );
这两个函数用off_t数据类型替代了长整型。

int fgetpos( FILE *restrict file_p, fpos_t *restrict pos );
int fsetpos( FILE * file_p, const fpos_t *pos );
这两个函数是ISO C引进的。

八 fileno函数
#include<stdio.h>
int fileno( FILE *file_p );
获取该文件流对应的文件描述符。

九 临时文件
#include<stdio.h>
char *tmpnam( char *ptr );
FILE *tmpfile( void );
tmpnam产生一个与现有文件名不同的有效路径名字符串,每次调用,都产生不同的路径名,最多调用次数是TMP_MAX(<stdio.h>)。若ptr为NULL,所产生的路径名存放在一个静态区中,下次再调用tmpnam时,会重写静态区。若ptr不是NULL,则它指向长度至少为L_tmpnam个字符的数组(常量L_tmpnam定义在<stdio.h>中)。
tmpfile产生一个临时二进制文件(w+),在关闭该文件或程序结束时将自动删除。
char *tempnam( const char *directory, const char *prefix );
tempnam函数是XSI的扩展,允许调用者为所产生的路径名指定目录和前缀(最多为5个字符)。对目录有四种不同的选择:
1 如果定义了环境变量TMPDIR,则用其作为目录;
2 如果directory参数非NULL,则用它作为目录;
3 将<stdio.h>中的字符串P_tmpdir作为目录;
4 将本地目录(/tmp)用作目录。
#include<stdlib.h>
ing mkstemp( char *template );
返回值是临时文件的打开文件描述符。mkstemp创建的临时文件不会自动被删除,需要自行unlink它。tmpnam和tempnam的不足之处是:在返回路径名和程序利用该路径名创建文件之间存在一个时间窗口,在该窗口期间,另一个程序可能创建一个同名文件,tmpfile和mkstemp函数则不会,可以使用它们替代tmpnam和tempnam。