5.2流和FILE对象
对于标准IO库,它们的操作是围绕流(stream)进行的。当用标准IO库打开或者创建一个文件时,我们已经使一个流与一个文件箱关联
当打开一个流时,标准IO函数fopen返回一个指向FILE对象的指针。该对象通常是一个结构,它包含了标准IO库管理该流所需要的所有信息,包括:用于实际IO的文件描述符,指向用于该流缓冲区的指针,缓冲区长度,当前在缓冲区的字符数以及出错标志等等。
5.3标准输入,标准输出和标准出错
这三个IO经过预定义文件指针stdin,stdout和stderr加以引用。
5.4缓冲
标准IO库提供缓冲的目的是减少使用read和write调用的次数。它也对每个IO流自动地进行缓冲管理。
标准IO提供了三种类型的缓冲:
(1)全缓冲。这种情况下,在填满标准IO缓冲区后才进行实际的IO操作。
术语冲洗(flush)说明标准IO缓冲区的写操作。缓冲区可由标准IO例程自动冲洗(例如当填满一个缓冲区时),或者可以调用函数fflush冲洗一个流。
(2)行缓冲。当在输入或输出中遇到换行符时,标准IO库执行IO操作。
当流涉及一个终端时,通常使用行缓冲。
对于行缓冲有两个限制。第一,因为标准IO库用来收集每一行的缓冲区的长度是固定的,所以只要填满了缓冲区,那么即使还没有写一个换行符,也进行IO操作。第二,任何时候只要通过标准IO库要求从(a)一个不带缓冲的流,或者(b)一个行缓冲的流(它要求从内核得数据)得到输入数据,那么就会造成冲洗所有行缓冲输出流。在(b)中带了一个在括号中的说明,其理由是,所需的数据可能已经在缓冲区中,它并不要求在需要数据时才从内核读数据。
(3)不带缓冲。
标准出错流stderr通常是不带缓冲的。
ISO C 要求下列缓冲特征:
当且仅当标准输入和标准输出不涉及交互设备时,它们才是全缓冲的。
标准出错决不是全缓冲的
系统默认:
标准出错是不带缓冲的
如若是涉及终端设备的其他流,则它们是行缓冲的;否则是全缓冲的。
对于一个给定的流,如若我们不喜欢这些系统默认情况,则可以调用下面两个函数更改缓冲类型:
1: #include<stdio.h>2:3: void setbuf(FILE *fp,char *buf);4:5: int setvbuf(FILE *fp,char *buf,int mode,size_t size);6:7: //返回值:若成功则返回0,若出错则返回非0值
这些函数一定要在流已经被打开后调用。
使用setvbuf,我们可以精确地指定所需缓冲的类型。这是用mode参数实现的:
_IOFBF 全缓冲
_IOLBF 行缓冲
_IONBF 不带缓冲
如果指定一个不带缓冲的流,则忽略buf和size参数。如果指定全缓冲或行缓冲,则buf和size指定一个缓冲区及其长度。如果该流是带缓冲的,而buf是NULL,则标准IO自动的为该流分配适当长度的缓冲区。
下图:
要了解,如果在一个函数内分配一个自动变量类的标准I/O缓冲区,则从该函数返回之前,必须关闭该流。另外,有些实现将缓冲区的一部分用于存放它自己的管理操作信息,所以可存放在缓冲区的实际数据字节数少于size。
任何时候,我们可以强制冲刷一个流。
1: #include<stdio.h>2:3: int fflush(FILE *fp);
4: //若成功返回0,出错返回EOF;
5.6读和写流
一旦打开了流,则可在三种不同类型的非格式化IO中进行选择,对齐进行读写操作
(1)每次一个字符IO
(2)每次一行的IO
(3)直接IO 。有时被称作二进制IO,一次一个对象IO,面向记录的IO或者面向结构的IO。
1.输入函数
以下三个函数可用于一次读一个字符
1: #include<stdio.h>2: int getc(FILE *fp);
3: int fgetc(FILE *fp);
4: int getchar(void); //三个函数返回值:若成功则返回下一个字符,若已经达到文件尾或出错返回EOF5:
函数getchar等价于getc(stdin)。前两个函数的区别是getc可被实现为宏,而fgetc不能实现为宏。这意味着:
(1)getc的参数不应当是具有副作用的表达式
(2)因为fgetc一定是一个函数,所以可以取到其地址。这就允许将fgetc的地址作为一个参数传递给另一个函数。
(3)调用fgetc时间可能长于调用getc,因为调用函数通常所需时间长于调用宏。
不管是出错还是达到文件尾端,这三个函数返回同样的值,为了区分这两种不同的情况,必须调用ferror或feof
1: #include<stdio.h>2: int ferror(FILE *fp);
3: int feof(FILE *fp);
4: //两个函数返回值:若条件为真则返回非0值,否则返回0
5: void clearerr(FILE *fp);
大多数的实现中,为每个流在FILE对象中维持了两个标志:
出错标志
文件结束标志
调用clearerr则清除这两个标志
2.输出函数
对于上述的每个输入函数都有一个输出函数
1: #include<stdio.h>2: int putc(int c,FILE *fp);3: int fputc(int c,FILE *fp);4: int putchar(int c); //若成功返回c,出错返回EOF
5.7 每次一行IO
下面两个函数提供每次输入一行的功能。
1: #include<stdio.h>2: char *fgets(char *buf,int n,FILE *fp);3: char *gets(char *buf);4: //两个函数返回值:若成功返回buf,若已到达文件末尾或者出错则返回NULL
两个函数都指定了缓冲区地址,读入的行将送入其中。gets从标准输入读,而fgets从指定的流读。
gets与fgets的另一个区别是gets并不将换行符送入缓冲区中,而fgets则保持换行符
fputs和puts提供每次输出一行的功能。
1: #include<stdio.h>2: int fputs(const char *str,FILE *fp);3: int puts(const char *str);// 两个函数返回值:若成功返回非负值,若出错返回EOF
函数fputs将一个以NULL符终止的字符串写到指定的流,尾端的NULL不写出
5.9二进制IO
提供了下面函数执行二进制IO操作
1: #include<stdio.h>2: size_t fread(void *ptr,size_t size,size_t nobj,FILE *fp);
3: size_t fwrite(const void *ptr,size_t size,size_t nobj,FILE *fp);4: //两个函数返回值:读或写的对象数
这些函数有两种常见的用法:
(1)读或写一个二进制数组。例如,为了将一个浮点数组的2——5个元素写到一个文件上,可以编写如下程序
1: float data[10];
2: if(fwrite(&data[2],sizeof(float),4,fp)!=4)3: //其中,指定size为每个数组元素的长度,nobj为欲写的元素数。
4:
(2)读或写一个结构。例如,可以编写如下程序:
1: struct{
2: short count;
3: long total;
4: char name[NAMESIZE];
5: }item;6: if(fwrite(&item,sizeof(item),1,fp)!=1)
fread和fwrite返回读或写的对象数。对于读,如果出错或达到文件尾端,则此数字可以少于nobj。
5.12实现细节
每一个标准IO流都有一个与其相关联的文件描述符,可以对一个流调用fileno函数以获得其文件描述符。
1: #include<stdio.h>2: int fileno(FILE *fp);
3: //返回值:与流相关联的文件描述符