apue:标准I/O库

流和FILE对象

    在之前的文件I/O中,所有I/O函数都是针对文件描述符的。当打开一个文件时,即返回一个文件描述符,然后该文件描述符就用于后读的I/O操作。而对于标准I/O库,它们的操作则是围绕流(stream)进行的。当用标准I/O库打开或创建一个文件时,我们已使一个流与一个文件相结合。当打开一个流时,标准I/O函数fopen返回一个指向FILE对象的指针。该对象通常是一个结构,它包含了I/O库为管理该流所需要的所有信息:用于实际I/O的文件描述符,指向流缓存的指针,缓存的长度,当前在缓存中的字符数,出错标志等等。

标准输入、标准输出和标准错误

    对一个进程预定义了三个流,它们自动地可为进程使用:标准输入、标准输出和标准出错。文件I/O中用文件描述符STDIN_FILENO,STDOUT_FILENOSTDERR_FILENO分别表示它们。标准I/O流通过预定义文件指针stdin,stdout和stderr加以引用,这三个文件指针定义在头文件stdio.h中。

缓存

    标准I/O提供缓存的目的是尽可能减少使用read和write调用的数量,标准I/O提供了三种类型的缓存:
- 全缓存。当填满标准I/O缓存后才进行实际I/O操作。缓存可由标准I/O例程自动地刷新(例如当填满一个缓存时),或者可以调用函数fflush刷新一个流。
- 行缓存。当在输入和输出中遇到新行符时,标准I/O库执行I/O操作。对于行缓存有两个限制,第一个是:因为标准I/O库用来收集每一行的缓存的长度是固定的,只要填满了缓存,那么即使还没有写一个新行符,也进行I/O操作。第二个是:任何时候只要通过标准输入输出库要求从个不带缓存的流,或者一个行缓存的流得到输入数据,那么就会造成刷新所有行缓存输出流。
- 不带缓存。标准I/O库不对字符进行缓存。如果用标准I/O函数写若干字符到不带缓存的流中,则相当于用write系统调用函数将这些字符写至相关联的打开文件上。
    对任何一个给定的流,如果我们并不喜欢这些系统默认,则可调用下列两个函数中的一个更改缓存类型:

#include 
void setbuf(FILE* fp, char* buf);
int setvbuf(FILE* fp, char* buf, int mode, size_t size);
/*返回:若成功则为0,若出错则为非0*/

    这些函数一定要在流已被打开后调用,而且也应在对该流执行任何一个其他操作之前调用。使用setbuf函数打开或关闭缓存机制。为了带缓存进行I/O,参数buf必须指向一个长度为BUFSIZ的缓存。通常在此之后该流就是全缓存的,但是如果该流与一个终端设备相关,那么某些系统也可将其设置为行缓存的。为了关闭缓存,将buf设置为NULL。使用setvbuf,我们可以精确地说明所需的缓存类型。这是依靠mode参数实现的:_IOFBF表示全缓存,_IOLBF表示行缓存,_IONBF表示不带缓存。如果指定一个不带缓存的流,则忽略buf和size参数。如果指定全缓存或行缓存,则buf和size可以可选择地指定一个缓存及其长度。如果该流是带缓存的,而buf是NULL,则标准I/O库将自动地为该流分配适当长度的缓存。如果系统不能为该流决定此值,则分配长度为BUFSIZ的缓存。
    任何时候,我们都可强制刷新一个流。

#include 
int fflush(FILE* fp);
/*返回:若成功则为0,若出错则为EOF*/

    此函数使该流所有未写的数据都被传递至内核。作为一种特殊情形,如若fp是NULL,则此函数刷新所有输出流。

打开流

    下列三个函数可用于打开一个标准I/O流。

#include 
FILE* fopen(const char* pathname, const char* type);
FILE* freopen(const char* pathname,const char* type, FILEf* p);
FILE* fdopen(int filedes, const char* type);
/*三个函数的返回:若成功则为文件指针,若出错则为NULL*/

    fopen打开路径名由pathname指示的一个文件。freopen在一个特定的流上(由fp指示)打开一个指定的文件(其路径名由pathname指示),如若该流已经打开,则先关闭该流。此函数一般用于将一个指定的文件打开为一个预定义的流:标准输入、标准输出或标准出错。fdopen取一个现存的文件描述符,并使一个标准的I/O流与该描述符相结合。此函数常用于由创建管道和网络通信通道函数获得的插述符。
    type参数指定对该I/O流的读、写方式,ANSIC规定type参数可以有15种不同的值:

type 说明
r或rb 为读而打开
w或wb 使文件成为0长,或为写而创建
a或ab 添加;为在文件尾写而打开,或为写而创建
r+或r+b或rb+ 为读和写而打开
w+或w+b或wb+ 使文件为0长,或为读和写而打开
a+或a+b或ab+ 为在文件尾读和写而打开或创建

    当以读和写类型打开一文件时(type中+号),具有下列限制:
- 如果中间没有fflush、fseek、fsetpos或rewind,则在输出的后面不能直接跟随输入。
- 如果中间没有fseek、fsetpos或rewind,或者一个输出操作没有到达文件尾端,则在输入操作之后不能直接跟随输出。
    调用fclose关闭一个打开的流。

#include 
int fclose(FILE* fp);
/*返回:若成功则为0,若出错则为EOF*/

    在该文件被关闭之前,刷新缓存中的输出数据。缓存中的输入数据被丢弃。如果标准I/O库已经为该流自动分配了一个缓存,则释放此缓存。当一个进程正常终止时(直接调用exit函数,或从main函数返回),则所有带未写缓存数据的标准I/O流都被刷新,所有打开的标准I/O流都被关闭。

读和写流

    一旦打开了流,则可在三种不同类型的非格式化I/O中进行选择,对其进行读、写操作。
- 每次一个字符的I/O。一次读或写一个字符,如果流是带缓存的,则标准I/O函数处理所有缓存。
- 每次一行的I/O。使用fgets和fputs一次读或写一行。每行都以一个新行符终止。当调用fgets时,应说明能处理的最大行长。
- 直接I/O。fread和fwrite函数支持这种类型的I/O。每次I/O操作读或写某种数量的对象,而每个对象具有指定的长度。这两个函数常用于从二进制文件中读或写一个结构。

输入函数

    以下三个函数可用于一次读一个字符。

#include 
int getc(FILE* fp);
int fgetc(FILE* fp);
int getchar(void);
/*三个函数的返回:若成功则为下一个字符,若已处文件尾端或出错则为EOF*/

    函数getchar等同于getc(stdin)。前两个函数的区别是getc可被实现为宏,而fgetc则不能实现为宏。这三个函数以unsignedchar类型转换为int的方式返回下一个字符。不管是出错还是到达文件尾端,这三个函数都返回同样的值。
    为了区分这不同的情况,必须调用ferror或feof。

#include 
int ferror(FILE* fp);
int feof(FILE* fp);
/*两个函数返回:若条件为真则为非0(真),否则为0(假)*/
void clearerr(FILE* fp);

    在大多数实现的FILE对象中,为每个流保持了两个标志:出错标志和文件结束标志,调用clearerr则清除这两个标志。
    从一个流读之后,可以调用ungetc将字符再送回流中。

#include 
int ungetc(int c, FILE* fp);
/*返回:若成功则为C,若出错则为EOF*/

    送回到流中的字符以后又可从流中读出,但读出字符的顺序与送回的顺序相反。

输出函数

    对应于每个输入函数都有一个输出函数。

#include 
int putc(int c, FILE* fp);
int fputc(int c, FILEf* p);
int putchar(int c);
/*三个函数返回:若成功则为C,若出错则为EOF*/

    与输入函数一样,putchar(c)等同于putc(c,stdout),putc可被实现为宏,而fputc则不能实现
为宏。

每次一行I/O

    下面两个函数提供每次输入一行的功能。

#include 
char* fgets(char* buf, int n, FILE* fp);
char* gets(char* buf);
/*两个函数返回:若成功则为buf,若已处文件尾端或出错则为NULL*/

    这两个函数都指定了缓存地址,读入的行将送入其中。gets从标准输入读,而fgets则从指定的流读。对于fgets,必须指定缓存的长度n。该缓存以null字符结尾。如若该行,包括最后一个新行符的字符数超过n-1,则只返回一个不完整的行,而且缓存总是以null字符结尾。对fgets的下一次调用会继续读该行。在使用gets时不能指定缓存的长度,可能造成缓存越界(如若该行长于缓存长度),写到缓存之后的存储空间中,从而产生不可预料的后果。
    fputs和puts提供每次输出一行的功能。

#include 
int fputs(const char* str, FILE* fp);
int puts(const char* str);
/*两个函数返回:若成功则为非负值,若出错则为EOF*/

    函数fputs将一个以null符终止的字符串写到指定的流,终止符null不写出。puts将一个以null符终止的字符串写到标准输出,终止符不写出。但是,puts然后又将一个新行符写到标准输出。

二进制I/O

    提供了下列两个函数以执行二进制I/O操作。

#include 
size_t fread(void* ptr, size_t size, size_t nobj, FILE* fp);
size_t fwrite(const void* ptr, size_t size, size_t nobj, FILE* fp);
/*两个函数的返回:读或写的对象数*/

    这些函数有两个常见的用法:读或写一个二进制数组,读或写一个结构。

定位流

    有两种方法定位标准I/O流:(1)ftell和fseek。(2)fgetpos和fsetpos。需要移植到非UNIX系统上运行的应用程序应当使用fgetpos和fsetpos。

#include 
long ftell(FILE* fp);
/*返回:若成功则为当前文件位置指示,若出错则为-1L*/
int fseek(FILE* fp, long offset, int whence);
/*返回:若成功则为0,若出错则为非0*/
void rewind(FILE* fp);
int fgetpos(FILE* fp, fpos_tpo *s);
int fsetpos(FILE* fp, const fpos_t* pos);
/*两个函数返回:若成功则为0,若出错则为非0*/

    对于一个二进制文件,其位置指示器是从文件起始位置开始度量,并以字节为计量单位的。ftell用于二进制文件时,其返回值就是这种字节位置。为了用fseek定位一个二进制文件,必须指定一个字节offset,以及解释这种位移量的方式。whence的值与lseek函数的相同:SEEK_SET表示从文件的起始位置开始,SEEK_CUR表示从当前文件位置,SEEK_END表示从文件的尾端。fgetpos将文件位置指示器的当前值存入由pos指向的对象中。在以后调用fsetpos时,可以使用此值将流重新定位至该位置。

格式化I/O

格式化输出

    执行格式化输出处理的是三个printf函数。

#include 
int printf(const char* format, ...);
int fprintf(FILE* fp, const char* format, ...);
/*两个函数返回:若成功则为输出字符数,若输出出错则为负值*/
int sprintf(char* buf, const char* format, ...);
/*返回:存入数组的字符数*/

    printf将格式化数据写到标准输出,fprintf写至指定的流,sprintf将格式化的字符送入数组buf中。sprintf在该数组的尾端自动加一个null字节,但该字节不包括在返回值中。
    下列三种printf族的变体类似于上面的三种,但是可变参数表(…)代换成了arg。

#include 
#include 
int vprintf(const char* format, va_list arg);
int vfprintf(FILE* fp, const char* format, va_list arg);
/*两个函数返回:若成功则为输出字符数,若输出出错则为负值*/
int vsprintf(char* buf, const char* format, va_list arg);
/*返回:存入数组的字符数*/

格式化输入

    执行格式化输入处理的是三个scanf函数。

#include 
int scanf(const char* format, ...);
int fscanf(FILE* fp, const char* format, ...);
int sscanf(const char* buf, const char* format, ...);
/*三个函数返回:指定的输入项数,若输入出错,或在任意变换前已至文件尾端则为EOF*/

实现细节

    每个I/O流都有一个与其相关联的文件描述符,可以对一个流调用fileno以获得其描述符。

#include 
int fileno(FILE* fp);
/*返回:与该流相关联的文件描述符*/

    如果要调用dup或fcntl等函数,则需要此函数。

临时文件

    标准I/O库提供了两个函数以帮助创建临时文件。

#include 
char* tmpnam(char* ptr);
/*返回:指向一唯一路径名的指针*/
FILE* tmpfile(void);
/*返回:若成功则为文件指针,若出错则为NULL*/

    tmpnam产生一个与现在文件名不同的一个有效路径名字符串。每次调用它时,它都产生一个不同的路径名,最多调用次数是TMP_MAX。TMP_MAX定义在stdio.h中。若ptr是NULL,则所产生的路径名存放在一个静态区中,指向该静态区的指针作为函数值返回。下一次再调用tmpnam时,会重写该静态区。如若ptr不是NULL,则认为它指向长度至少是L_tmpnam个字符的数组。所产生的路径名存放在该数组中,ptr也作为函数值返回。tmpfile创建一个临时二进制文件(类型wb+),在关闭该文件或程序结束时将自动删除这种文件。
    tempnam是tmpnam的一个变体,它允许调用者为所产生的路径名指定目录和前缀。

#include 
char* tempnam(const char* directory, const char* prefix;
/*返回:指向一唯一路径名的指针*/

    对于目录有四种不同的选择,并且使用第一个为真的作为目录:
- 如果定义了环境变量TMPDIR,则用其作为目录。
- 如果参数directory非NULL,则用其作为目录。
- 将stdio.h中的字符串P_tmpdir用作为目录。
- 将本地目录,通常是/tmp,用作为目录。
    如果prefix非NULL,则它应该是最多包含5个字符的字符串,用其作为文件名的头几个字符

你可能感兴趣的:(UNIX编程,UNIX编程琐事)