apue2 阅读笔记 --第五章

标准I/O库

1.

当打开一个流时,标准I / O函数f o p e n返回一个指向F I L E对象的指针。该对象通常是一个结构,它包含了I / O库为管理该流所需要的所有信息:用于实际 I / O的文件描述符,指向流缓存的指针,缓存的长度,当前在缓存中的字符数,出错标志等等。

文件描述符S T D I N _ F I L E N O , S T D O U T _ F I L E N O和S T D E R R _ F I L E N O分别对应这三个标准I / O流通过预定义文件指针s t d i n , s t d o u t和s t d e r r。

2.

三种缓存类型:

标准I / O提供了三种类型的缓存:
(1) 全缓存。在这种情况下,当填满标准I / O缓存后才进行实际I / O操作。对于驻在磁盘上的
文件通常是由标准I / O库实施全缓存的。在一个流上执行第一次I / O操作时,相关标准I / O函数通
常调用m a l l o c(见7 . 8节)获得需使用的缓存。
术语刷新(f l u s h)说明标准I / O缓存的写操作。缓存可由标准I / O例程自动地刷新(例如当
填满一个缓存时),或者可以调用函数 ff l u s h刷新一个流。值得引起注意的是在 U N I X环境中,
刷新有两种意思。在标准 I / O库方面,刷新意味着将缓存中的内容写到磁盘上(该缓存可以只
是局部填写的)。在终端驱动程序方面(例如在第11章中所述的t c f l u s h函数),刷新表示丢弃已
存在缓存中的数据。
(2) 行缓存。在这种情况下,当在输入和输出中遇到新行符时,标准 I / O库执行I / O操作。这
允许我们一次输出一个字符(用标准I/O fputc函数),但只有在写了一行之后才进行实际I / O操
作。当流涉及一个终端时(例如标准输入和标准输出),典型地使用行缓存。
对于行缓存有两个限制。第一个是:因为标准 I / O库用来收集每一行的缓存的长度是固定
的,所以只要填满了缓存,那么即使还没有写一个新行符,也进行 I / O操作。第二个是:任何
时候只要通过标准输入输出库要求从 ( a )一个不带缓存的流,或者( b )一个行缓存的流(它预先
要求从内核得到数据)得到输入数据,那么就会造成刷新所有行缓存输出流。在 ( b )中带了一
个在括号中的说明的理由是,所需的数据可能已在该缓存中,它并不要求内核在需要该数据时
才进行该操作。很明显,从不带缓存的一个流中进行输入(( a )项)要求当时从内核得到数据。
(3)  不带缓存。标准I / O库不对字符进行缓存。如果用标准 I / O函数写若干字符到不带缓存
的流中,则相当于用 w r i t e系统调用函数将这些字符写至相关联的打开文件上。标准出错流
s t d e r r通常是不带缓存的,这就使得出错信息可以尽快显示出来,而不管它们是否含有一个新
行字符。

两个更改默认缓存的函数:

#include <stdio.h>
void setbuf(FILE *restrict fp, char *restrict buf);
int setvbuf(FILE *restrict fp, char *restrict buf,
            int mode, size_t size);

函数 int fflush(FILE *fp);使流fp的所有缓存输出,如果fp是NULL,所有流的缓存输出。
3.

打开流:

ILE *fopen(const char *restrict pathname, const
char *restrict type);
FILE *freopen(const char *restrict pathname, const
char *restrict type, FILE *restrict fp);
FILE *fdopen(int filedes, const char *type);

All three return: file pointer if OK, NULL on error

这三个函数的区别是:
(1) fopen 打开路径名由pathname 指示的一个文件。
(2) freopen在一个特定的流上(由f p指示)打开一个指定的文件(其路径名由pathname  指示),
如若该流已经打开,则先关闭该流。此函数一般用于将一个指定的文件打开为一个预定义的流:
标准输入、标准输出或标准出错。
(3) fdopen取一个现存的文件描述符(我们可能从 o p e n , d u p , d u p 2 , f c n t l或p i p e函数得到此文
件描述符),并使一个标准的I / O流与该描述符相结合。此函数常用于由创建管道和网络通信通
道函数获得的插述符。因为这些特殊类型的文件不能用标准 I/O fopen函数打开,首先必须先调
用设备专用函数以获得一个文件描述符,然后用f d o p e n使一个标准I / O流与该描述符相结合。

image

4.

输入输出函数:

A: 单个字符

int getc(FILE *fp);
int fgetc(FILE *fp);
int getchar(void);

All three return: next character if OK, EOF on end of file or error

(1)  getc的参数不应当是具有副作用的表达式。
(2)  因为f g e t c一定是个函数,所以可以得到其地址。这就允许将 f g e t c的地址作为一个参数
传送给另一个函数。

判断结束:

int ferror(FILE *fp);
int feof(FILE *fp);

放回一个已经读的字符(EOF不能放回)

int ungetc(int c, FILE *fp);

B: 行IO

应当总是使用fgets和fputs。

注意fgets会缓存’\n’ , 而fputs是遇到NULL结束输出,不管前面有没有’\n’;

tips:

(1) 程序中没有显式地关闭标准I / O流。我们知道e x i t函数将会刷新任何未写的数据,然后关闭所有打开的流。

(2) 使用标准I / O例程的一个优点是无需考虑缓存及最佳 I / O长度的选择。如果自己不进行缓存处理,多次循环进行IO系统调用,效果肯定不如使用标准IO。

参考标准IO的效率小结。

C: 二进制IO

size_t fread(void *restrict ptr, size_t size, size_t nobj,  FILE *restrict fp);
size_t fwrite(const void *restrict ptr, size_t size, size_t nobj, FILE *restrict fp);

Both return: number of objects read or written

注意用于网络数据时的字节对齐问题。

5.

定为流:

有两种方法定位标准I / O流。
(1) ftell和f s e e k。这两个函数自V 7以来就存在了,但是它们都假定文件的位置可以存放在一个长整型中。
(2) fgetpos和f s e t p o s。这两个函数是新由ANSI C引入的。它们引进了一个新的抽象数据类型f p o s _ t,它记录文件的位置。

在非U N I X系统中,这种数据类型可以定义为记录一个文件的位置所需的长度。

6.

格式化输入输出:

格式化输出:

int printf(const char *restrict format, ...);
int fprintf(FILE *restrict fp, const char * restrict format, ...);

Both return: number of characters output if OK, negative value if output error

 

int sprintf(char *restrict buf, const char *restrict format, ...);
int snprintf(char *restrict buf, size_t n,  const char *restrict format, ...);

Both return: number of characters stored in array if OK, negative value if encoding error

注意:

s p r i n t f将格式化的字符送入数组b u f中。s p r i n t f在该数组的尾端自动加一个n u l l字节,但该字节不包括在返回值中。

sprintf可能造成溢出,保证不溢出是调用者的责任。

输入同理, 值得注意的是格式化说明符可以用简单的正则表达式,非常有用哦。

7.

实现细节:

为了了解你所使用的系统中标准 I / O库的实现,最好从头文件 < s t d i o . h >开始。从中可以看到:

F I L E对象是如何定义的,每个流标志的定义,定义为宏的各个标准 I / O例程(例如g e t c)。

函数 int fileno(FILE *fp);可以得到流对应的文件描述符,结合前面的fdopen可以知道,文件描述符和流之间可以方便的进行转换的。

8.

临时文件:

char *tmpnam(char * p t r) ; //返回唯一路径名,通常利用进程号和时间

FILE *tmpfile(void); //创建进程运行期有效的临时文件,通常利用ulink,确保程序运行后删除。

char   *tempnam(const char *  d i re c t o ry, const char *p re f i x);//允许指定目录和前缀

总结一下:

The standard I/O library is used by most UNIX applications. We have looked at all the functions provided by this library, as well as at some implementation details and efficiency considerations. Be aware of the buffering that takes place with this library, as this is the area that generates the most problems and confusion.

posted @ 2011-11-19 12:16  jialejiahi  阅读(292)  评论(0编辑  收藏  举报