流和F I L E对象           理解:流是一个结构体,类似于FILE *fp,每个结构体里都有一个文件I/O号。每运行一个

                                                a.out(或者打开一个文件)系统会默认打开3个流,程序员是对流进行所以操作的,而                                                  不用关心流的另一头的数据流向

对于标准 I / O库,它们的操作则是围绕流
(s t r e a m)进行的(请勿将标准I / O术语流与系统V的STREAMS I/O系统相混淆)。当用标准I / O
库打开或创建一个文件时,我们已使一个流与一个文件相结合。

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

应用程序没有必要检验 F I L E对象。为了引用一个流,需将 F I L E指针作为参数传递给每个
标准I / O函数。在本书中,我们称指向F I L E对象的指针(类型为F I L E * )为文件指针。


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

我们曾用文件描述符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加以引用。这三个文件指针同样
定义在头文件< s t d i o . h >中。



缓存

标准I / O提供缓存的目的是尽可能减少使用 r e a d和w r i t e调用的数量。

标准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通常是不带缓存的,这就使得出错信息可以尽快显示出来,而不管它们是否含有一个新
行字符。


ANSI C要求下列缓存特征:

(1) 当且仅当标准输入和标准输出并不涉及交互作用设备时,它们才是全缓存的。
(2) 标准出错决不会是全缓存的。


对任何一个给定的流,如果我们并不喜欢这些系统默认,则可调用下列两个函数中的一个
更改缓存类型:
#include <stdio.h>
void setbuf(FILE * f p, char *b u f) ;
int setvbuf(FILE * f p, char *b u f, int m o d e, size_t s i z e) ;
返回:若成功则为0,若出错则为非0

标准I/O库


# i n c l u d e < s t d i o . h >
int fflush(FILE * f p) ;
返回:若成功则为0,若出错则为E O F
此函数使该流所有未写的数据都被传递至内核。作为一种特殊情形,如若 f p是N U L L,则此函数刷新所有输出流。



打开流

#include <stdio.h>
FILE *fopen(const char * p a t h n a m e, const char * t y p e) ;
FILE *freopen(const char * p a t h n a m e, const char * t y p e, FILE * f p) ;
FILE *fdopen(int  f i l e d e s, const char * t y p e) ;
三个函数的返回:若成功则为文件指针,若出错则为 N U L L


这三个函数的区别是:
(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流与该描述符相结合。

标准I/O库


注意,在指定w或a类型创建一个新文件时,我们无法说明该文件的存取许可权位(第 3章
中所述的o p e n函数和c r e a t函数则能做到这一点)。P O S I X . 1要求以这种方式创建的文件具有下
列存取许可权:
S _ I R U S R|S _ I W U S R|S _ I R G R P|S _ I W G R P|S _ I R O T H|S _ I W O T H
除非流引用终端设备,否则按系统默认,它被打开时是全缓存的。若流引用终端设备,则
该流是行缓存的。一旦打开了流,那么在对该流执行任何操作之前,如果希望,则可使用前节
所述的s e t b u f和s e t v b u f改变缓存的类型。


调用f c l o s e关闭一个打开的流。
#include <stdio.h>
int fclose(FILE * f p) ;

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



读和写流

以下三个函数可用于一次读一个字符。
#include <stdio.h>
int getc(FILE * f p) ;
int fgetc(FILE * f p) ;
int getchar(void);
三个函数的返回:若成功则为下一个字符,若已处文件尾端或出错则为 E O F

函数g e t c h a r等同于g e t c ( s t d i n )。前两个函数的区别是g e t c可被实现为宏,而f g e t c则不能实现为宏。

这意味着:
(1) getc的参数不应当是具有副作用的表达式。
(2) 因为f g e t c一定是个函数,所以可以得到其地址。这就允许将 f g e t c的地址作为一个参数
传送给另一个函数。
(3) 调用f g e t c所需时间很可能长于调用g e t c ,因为调用函数通常所需的时间长于调用宏。检
验一下< s t d i o . h >头文件的大多数实现,从中可见g e t c是一个宏,其编码具有较高的工作效率。


注意,不管是出错还是到达文件尾端,这三个函数都返回同样的值。为了区分这两种不同
的情况,必须调用f e r r o r或f e o f。

#include <stdio.h>
int ferror(FILE * f p) ;
int feof(FILE * f p) ;
两个函数返回:若条件为真则为非0(真),否则为0(假)



在大多数实现的F I L E对象中,为每个流保持了两个标志:
• 出错标志。
• 文件结束标志。
调用c l e a r e r r则清除这两个标志。

void clearerr(FILE *



从一个流读之后,可以调用u n g e t c将字符再送回流中。
#include <stdio.h>
int ungetc(int  c, FILE *f p) ;
返回:若成功则为C,若出错则为E O F


输出函数

#include <stdio.h>
int putc(int  c, FILE *f p) ;
int fputc(int  c, FILE * f p);
int putchar(int  c) ;
三个函数返回:若成功则为C,若出错则为E O F
与输入函数一样,putchar(c) 等同于putc (c, stdout),putc 可被实现为宏,而fputc 则不能实现
为宏。



每次一行I / O

下面两个函数提供每次输入一行的功能。
#include <stdio.h>
char *fgets(char * b u f, int n,FILE *f p) ;
char *gets(char * b u f) ;
两个函数返回:若成功则为b u f,若已处文件尾端或出错则为N U L L
这两个函数都指定了缓存地址,读入的行将送入其中。 g e t s从标准输入读,而f g e t s则从指定的
流读。


f p u t s和p u t s提供每次输出一行的功能。
#include <stdio.h>
int fputs(const char * s t r, FILE * f p) ;
int puts(const char * s t r) ;
两个函数返回:若成功则为非负值,若出错则为 E O F

函数f p u t s将一个以n u l l符终止的字符串写到指定的流,终止符 n u l l不写出。注意,这并不一定
是每次输出一行,因为它并不要求在 n u l l符之前一定是新行符。通常,在 n u l l符之前是一个新
行符,但并不要求总是如此。
p u t s将一个以n u l l符终止的字符串写到标准输出,终止符不写出。但是, p u t s然后又将一个
新行符写到标准输出。



二进制I / O
前面的函数以一次一个字符或一次一行的方式进行操作。如果为二进制 I / O,那么我们
更愿意一次读或写整个结构。为了使用 g e t c或p u t c做到这一点,必须循环通过整个结构,一次
读或写一个字节。因为 f p u t s在遇到n u l l字节时就停止,而在结构中可能含有 n u l l字节,所以不
能使用每次一行函数实现这种要求。相类似,如果输入数据中包含有 n u l l字节或新行符,则
f g e t s也不能正确工作。

因此,提供了下列两个函数以执行二进制 I / O操作。
#include <stdio.h>
size_t fread(void * p t r, size_t s i z e, size_t n o b j, FILE *f p) ;
size_t fwrite(const void * p t r, size_t s i z e, size_t n o b j, FILE *f p) ;
两个函数的返回:读或写的对象数

这些函数有两个常见的用法:
(1) 读或写一个二进制数组。例如,将一个浮点数组的第 2至第5个元素写至一个文件上,
可以写作:
float data 〔1 0〕;
if (fwrite(&data 〔2〕, sizeof(float), 4, fp) != 4)
err_sys("fwrite error");
其中,指定s i z e为每个数组元素的长度,n o b j为欲写的元素数。

(2) 读或写一个结构。例如,可以写作:
struct {
short count;
long total;
char name [N A M E S I Z E];
} item;
if (fwrite(&item, sizeof(item), 1, fp) != 1)
err_sys("fwrite error");
其中,指定s i z e为结构的长度,n o b j为1(要写的对象数)。
将这两个例子结合起来就可读或写一个结构数组。为了做到这一点, s i z e应当是该结构的
s i z e o f ,n o b j应是该数组中的元素数。
f r e a d和f w r i t e返回读或写的对象数。对于读,如果出错或到达文件尾端,则此数字可以少
于n o b j。在这种情况,应调用f e r r o r或f e o f以判断究竟是那一种情况。对于写,如果返回值少于
所要求的n o b j,则出错。




相关文章: