Chapter 5 标准I/O库
1.流和FILE对象
对于国际字符集,一个字符可以由一个以上的字节来表示。标准I/O文件流可以用来操作单字节和多字节(宽,wide)字符集。一个流的方向(orientation)决定了字符是以单字节还是多字节的方式读取,当一个流被创建时,它没有方向。如一个多字节I/O函数(见<wchar.h>)用在了没有方向的流上,那么流的方向会设为面向宽字符的。如果一个字节I/O函数用在一个没有方向的流上,那么流的方向会设为面向字节的。只有两个函数可以在设置后改变这个方向。freopen函数(简单说明)将会清除一个流的方向,而 fwide函数用来设置一个流的方向。
#include <stdio.h> #include <wchar.h> int fwide(FILE *fp, int mode); //流面向宽字符返回正值、面向字节返回负值、没有方向则返回0。
根据mode参数的不同值,fwdie函数执行不同的任务:
1).如果mode参数为负值,fwide将会尝试让流面向字节;
2).如果mode参数为正值,fwide将会尝试让流面向宽字符;
3).如果mode参数为0,fwid将不会设置方向,而是返回一个表示流的方向的值。
注意fwide不会改变一个已经有方向的流的方向。同样注意它不会返回错误。
当打开一个流时,标准I/O函数fopen返回一个指向一个FILE对象的指针。这个对象通常是一个包含所有标准I/O库管理流所需的所有信息的结构体。
2.缓冲
三种缓冲类型:
1).全缓冲:在这种情况下,当标准I/O的缓冲被填满后,真实的I/O才会发生。在磁盘上的文件通常都被标准I/O库完全缓冲。这个被使用的缓冲通常被标准I/O库在第一次操作一个流时通过调用malloc得到.注:Stdin和stdout如果被重定位到文件上,则是完全缓冲
术语“冲洗(flush)”描述了标准I/O缓冲的写操作。一个缓冲可以被标准I/O函数自动冲洗,比如在缓冲满时,或者我们可以调用函数fflush来冲洗一个流。不幸的是,在UNIX环境里,冲洗有两个意思。在标准I/O库里,它表示缓冲的内容写出,它可能是部分填充的。
2).行缓冲:在这种情况下,标准I/O库在输入输出时碰到一个换行符时会执行I/O操作。这让我们可以一次输出单个字符(使用标准I/O的 fputc函数),因为我们知道只当我们完成了每行的写操作时真实I/O才会发生。行缓冲被典型用在终端的流上:比如标准输入、标准输出。
行缓冲伴随两个警告。第一,标准I/O库用来收集各行的缓冲区大小是固定的,所以当我们在写一个换行符之前填满这个缓冲I/O可能会发生。第二、不管何时通过标准I/O库从a、一个未缓冲流或b、(需要从内核被请求数据的)一个行缓冲流请求一个输入时,所有行缓冲输出流都会被冲洗。b情况括号内的补充条件的原因是,请求的数据可能已经在缓冲里了,这时不再需要从内核中读取数据。显然,a情况下任何从未缓冲流的输入,都会要求从内核获取数据。
3).无缓冲:标准I/O库不缓冲字符。例如,如果我们用标准I/O函数fputs写15个字符,我们会期望这15个字符尽快地输出,它很可能使用了3.8节的write函数。
标准错误流通常是无缓冲的。这是为了使任何错误消息都能尽快地显示,而不管它们是否包含一个换行符。
通过调用下面两个函数的其中一个来改变缓冲:
#include <stdio.h> void setbuf(FILE *restrict fp, char *restrict buf); void setvbuf(FILE *restrict fp, char *restrict buf, int mode, size_t size); //成功返回0,失败返回非0值。
3.打开流
1).下面三个函数打开一个标准I/O流
#include <stdio.h> FILE *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); //成功返回文件指针,错误返回NULL。
区别:
a.fopen函数打开一个指定的文件。
b.freopen函数在一个指定的流上打开一个指定的文件,并关闭之前打开的流。如果之前的流有方向,freopen会清除它.
c.fdopen函数接受一个已有的文件描述符,它可以通过open、dup、dup2、fcntl、pipe、socket、socketpair或 accept函数得到
2).一个打开的流可以调用fclose来关闭
#include <stdio.h> int fclose(FILE *fp); //成功返回0,错误返回EOF。
任何缓冲的输出数据都在文件关闭前被冲洗。任何可能被缓冲的输入数据都会被舍弃。如果标准I/O库之前自动开辟了一个缓冲,这个缓冲会被释放。
4.读和写流
打开一个流,可以从以下三种非格式化的I/O中选择:
- 一次一字符(Character-at-a-time)I/O。我们可以一次读写一个字符,而让标准I/O函数处理所有的缓冲事宜,如果流有缓冲的话。
- 一次一行(Line-at-a-time)I/O。如果我们读一次读写一行,我们使用fgets和fputs。每行都以一个换行符终止,而且当我们调用fgets时必须指定我们能处理的最大行长度。
- 直接(Direct)I/O。这种类型的I/O通过fread和fwrite函数支持。对于每个I/O操作,我们读写一些对象,而每个对象都有一个指定的尺寸。这两个函数经常用来处理二进制文件,那里我们每次操作读写一个结构。
1).输入函数:
下面三个函数允许我们一次读一个字符
#include <stdio.h> int getc(FILE *fp); int fgetc(FILE *fp); int getchar(void); //三者成功都返回下一个字符,失败都返回EOF。
函数getchar被定义为与getc(stdin)等价。前两个函数的区别是getc可以被作为一个宏而实现,而fgetc不能作为宏实现。
2).注意上面三个函数当碰到一个错误或到达文件尾时返回同一个值。为了区别这两者,我们必须调用ferror或feof
#include <stdio.h> int ferror(FILE *fp); int feof(FILE *fp); //两者如果条件成立的话返回非0(真),否则返回0(假)。 void clearerr(FILE *fp);
在多数实现上,在FILE对象里为每个流维护了两个标志:
a.一个错误标志
b.一个文件结束标志
两个标志都可以调用clearerr来清除。
3).在从一个流读入后,我们可以调用ungetc来把字符放回去。
#include <stdio.h> int ungetc(int c, FILE *fp); //成功返回c,否则返回EOF。
放回的字符会被后续在流上的读操作以放回的相反顺序返回
4).输出函数
对应于每个之前讨论过的输入函数的输出输出有:
#include <stdio.h> int putc(int c, FILE *fp); int fputc(int c, FILE *fp); int putchar(int c); //三个函数成功都返回c,否则返回EOF。
和输入函数相似,putchar(c)和putc(c, stdout)等价,而putc可以作为一个宏实现(将被替换为IO_putc),但fputc不能作为一个宏实现。
5.每次一行I/O
1).以下两个函数提供一次一行输入功能
#include <stdio.h> char *fgets(char *restrict buf, int n, FILE* restrict fp); char *gets(char *buf); //两者成功返回buf,文件结束或错误返回NULL。
两都都指明一个把行读入的缓冲区的地址。gets函数从标准输入中读,而fgets从指定的流中读取。
2).一次一行输出由fputs和puts提供
#include <stdio.h> int fputs(const char *restrict str, FILE *restrict fp); int puts(const char *str); //成功返回非负值,否则返回EOF。
函数fputs把一个null终止的字符串写入到指定的流中,末尾的null字节没有被写,puts函数在标准输出写一个空字符终止的字符串,但不会把这个空字节写出。不同的是puts接着会把一个换行符写入到标准输出。
6.二进制I/O
以下两个函数被用来执行二进制I/O
#include <stdio.h> 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); //两者都返回读或写的对象数。
这些函数有两种普遍的使用方法:
1)读写一个二进制数组。
2)读写一个结构体。
7.定位流
有三种方式来定位一个标准I/O流
1)
#include <stdio.h> long ftell(FILE *fp); //如果成功返回当前文件位置指示器,错误返回-1L。 int fseek(FILE *fp, long offset, int whence); //成功返回0,否则返回非0值。 void rewind(FILE *fp);
对于一个二进制文件,一个文件的位置指示器是从文件开始的字节来衡量的.
a.ftell用于二进制文件,返回值是这个字节位置。
b.为了用fseek定位一个二进制文件,我们必须指定一个字节偏移以及这个偏移如何被解释。whence的值和前面的lseek函数里的一样:SEEK_SET表示从文件开头开始;SEEK_CUR表示从当前文件位置开始;SEEK_END表示从文件末尾开始。
c.使用rewind函数也可将一个流设置到文件起始位置
2).ftello函数和ftell函数相同,而fseeko函数和fseek函数相同,除了偏移量的类型是off_t而不是long
#include <stdio.h> off_t ftello(FILE* fp); //成功返回当前文件位置,否则返回(off_t)-1。 int fseeko(File *fp, off_t offset, int whence); //成功返回0,失败返回非0.
3).想移植到非UNIX系统上运行应用程序应该使用fgetpos和fsetpos
#inlcude <stdio.h> int fgetpos(FILE *restrict fp, fpos_t *restrict pos); int fsetpos(FILE *fp, const fpos_t *pos); //两者成功都返回0,失败返回非0值。
fgetpos返回把文件位置指示器的值存储在由pos指向的对象里。这个值可以被之后的fsetpos调用使用,来重定位流的位置。
8.格式化I/O
1).格式化输出
格式化输出由4个printf函数处理:
#include <stdio.h> int printf(const char *restrict format, ...); int fprintf(FILE *restrict fp, const char *restrict format, ...); //两者成功都返回字符数,输出错误则返回负值。 int sprintf(char *restrict buf, const char *restrict format, ...); int snprintf(char *restrict buf, size_t n, const char *restrict format, ...); //两者成功都返回在字节里存储的字符数量,编码错误返回负值。
printf函数向标准输出写,fprintf向一个指定的流写,而sprintf把格式化的字符串放入到数组buf里。sprintf函数在数组末自动将上一个空字节,但这个空字节没有包含在返回值里。注意sprintf可能为溢出buf指向的缓冲区,这时调用者的执行来保证这个缓冲区足够大。
下面4个printf家族的变体与前4个函数相似,只是参数列表(...)被arg代替。
#include <stdarg.h> #include <stdio.h> int vprintf(const char *restrict format, va_list arg); int vfprintf(FILE *restrict fp, const char *restrict format, va_list arg); //两者成功都返回输出的字符数,失败返回负值 int vsprintf(char *restrict buf, const char *restrict format, va_list arg); int vsnprintf(char *restrict buf, size_t n, const char *restrict format, va_list arg); //两都成功都返回数组存储的字符数,编码错误返回负数。
2).格式化输入
格式化输入由三个scanf函数处理
#include <stdio.h> int scanf(const char *restrict format, ...); int fscanf(FILE *restrict fp, const char *restrict format, ...); int sscanf(const char *restrict buf, const char *restrict format, ...); //三者都返回被赋值的输入项的数目,输入出错或碰到文件结尾时返回EOF。
scanf家族用来解析一个输入的字符串并转换成指定类型的变量。在格式后的参数包含需要用转换结果初绐化的变量的地址。
和printf家族一样,scanf家族也支持使用可变参数列表的函数,它们由<stdarg.h>指定。
#include <stdarg.h> #include <stdio.h> int vscanf(const char *restrict format, va_list arg); int vfscanf(FILE *restrict fp, const char *restrict format, va_list arg); int vsscanf(const char *restrict buf, const char *restrict format, va_list arg); //三者都返回赋好值的输入项的数量,错误或转换前遇到文件结尾时返回EOF。
9.实现细节
可以调用fileno来得到流的文件描述符
#include <stdio.h> int fileno(FILE *fp); //返回相关联的文件描述符。
10.临时文件
ISO C标准定义了两个由标准I/O库提供的函数,用来协助创建临时文件
#include <stdio.h> char *tmpnam(char *ptr); //返回指向唯一的路径名的指针。 FILE *tmpfile(void); //成功返回文件指针,失败返回NULL。
tmpname函数返回一个不与任何已有文件相同的合法路径名的字符串