本章用于解析C语言标准I/O库,之所以在UNIX类系统的编程中会介绍C语言标准库,主要是因为UNIX和C之间具有密不可分的关系。
标准I/O库相比于操作系统的I/O库,具有更高的效率和可移植性,前者是因为标准I/O库提供了缓冲和块长度优化功能,后者是因为使用标准I/O库的代码不仅能在各UNIX系统上移植,也能在支持标准C的非UNIX系统上移植。
一、基本概念
流和FILE对象
UNIX系统I/O是建立在文件描述符的抽象概念上的,而标准I/O库则是建立在流的概念上的。当使用标准I/O库打开一个文件进行读写时,会创建一个流,该流与将要打开的文件进行关联,通过对抽象流的读写来间接读写文件。
标准输入、标准输出和标准错误
UNIX系统的shell中会默认为进程打开3个文件描述符:标准输入0、标准输出1和标准错误2。但它们是文件描述符的可阅读宏,ISO C标准I/O是无法使用的,为此ISO C标准I/O定义了三个另外的名字来引用它们,分别是:stdin、stdout、strerr。它们在头文件<stdio.h>中被定义。实际上STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO分别对应stdin、stdout、strerr。
缓冲
对于UNIX系统来说,标准I/O库最终是建立在系统调用的read()和write()上的。而UNIX系统的read()和write()是不带缓冲的。为了提供效率,标准I/O库提供了缓冲管理。
对于需要立即执行完成的标准读写操作,我们可以强制使用fflush()函数来刷新流的缓冲区。其函数声明如下:
1 #include <stdio.h> 2 3 int fflush(FILE *__stream); 4 5 /* 例子 */ 6 fflush(stream);
二、标准I/O函数
1 #include <stdio.h> 2 3 /* 1. 打开流 */ 4 FILE *fopen(const char * __filename, const char * __modes); 5 FILE *freopen(const char * __filename, const char * __modes, FILE * __stream); 6 FILE *fdopen(int __fd, const char *__modes); 7 /* fopen()用于打开指定路径的文件 */ 8 /* freopen()在指定流上关联指定的文件。如流已经打开,则重新打开;如流已定向,则清除定向。该函数通常用来重定向标准输入、标准输出和标准错误 */ 9 /* fdopen()用于将一个已有的文件描述符与一个标准I/O流关联。该函数常在创建管道或者网络socket得到的描述符上 */ 10 11 /* 2. 读/写流 */ 12 /* 2.1 字节I/O */ 13 int getc(FILE *__F); 14 int fgetc(FILE *__F); 15 int getchar(void); 16 /* 这三个函数一次只读取一个unsigned char,然后转换为int,在读取一个字符后,流自动移动到下一个字符,然后再次调用函数时会返回相对于上一次字符的下一个位置上的字符 */ 17 18 int putc(int __c, FILE *__F); 19 int fputc(int __c, FILE *__F); 20 int putchar(int __c); 21 /* 这三个函数成功返回__c,失败返回NULL。这三个函数一次只写入一个unsigned char,如果传递的值超过256的int类型实参给函数,那么超出范围的会被截断 */ 22 23 /* 2.2 行I/O,遇到换行符位置 */ 24 char *gets(char *__s); 25 char *fgets(char* __s, int __n, FILE* __F); 26 /* gets()函数建议不要使用,因为此函数会导致缓存区溢出 */ 27 28 int fputs(const char* __s, FILE* __F); 29 int puts(const char* __s); 30 31 /* 2.3 二进制I/O */ 32 size_t fread(void* __ptr, size_t __size, size_t __n, FILE* __F); 33 size_t fwrite(const void* __ptr, size_t __size, size_t __n, FILE* __F); 34 /* 这两个函数的返回值都是读或写的对象数量。参数中的__size是对象的大小,即sizeof计算得到的大小;__n是对象的数量 */ 35 36 /* 3. 定位流 */ 37 int fseek(FILE *__F, long int __off, int __whence); 38 39 /* 4.关闭流 */ 40 int fclose(FILE *__F);
代码中的fopen()函数的第二个参数有以下几种模式:
r 以只读方式打开文件,该文件必须存在
r+ 以读/写方式打开文件,该文件必须存在
rb+ 以读/写方式打开一个二进制文件,只允许读/写数据
rt+ 以读/写方式打开一个文本文件,允许读和写
w 打开只写文件,若文件存在则文件长度清为零,即该文件内容会消失;若文件不存在则创建该文件
w+ 打开可读/写文件,若文件存在则文件长度清为零,即该文件内容会消失;若文件不存在则创建该文件
a 以附加的方式打开只写文件。若文件不存在,则会创建该文件;如果文件存在,则写入的数据会被加到文件尾后,即文件原先的内容会被保留(EOF 符保留)
a+ 以附加方式打开可读/写的文件。若文件不存在,则会创建该文件,如果文件存在,则写入的数据会被加到文件尾后,即文件原先的内容会被保留(EOF符不保留)
wb 以只写方式打开或新建一个二进制文件,只允许写数据
wb+ 以读/写方式打开或新建一个二进制文件,允许读和写
wt+ 以读/写方式打开或新建一个文本文件,允许读和写
at+ 以读/写方式打开一个文本文件,允许读或在文本末追加数据
ab+ 以读/写方式打开一个二进制文件,允许读或在文件末追加数据
常用的有r+,rt+,at+等
代码中的gets()会导致缓存区溢出,尽量不要使用。同样会导致缓存区溢出的函数有strcpy()、strcat()等
示例代码:
1 FILE *stream = NULL; 2 char buf[100] = "Hello World\n"; 3 int ret = 0; 4 5 stream = fopen("a.txt", "w+"); 6 7 fwrite(buf, sizeof(char), strlen(buf), stream); 8 fseek(stream, 0, SEEK_SET); /* whence可选为SEEK_SET、SEEK_CUR、SEEK_END */ 9 memset(buf, 0, sizeof(buf)); 10 ret = fread(buf, sizeof(char), sizeof(buf), stream); 11 if (ret) { 12 printf("Read %d Bytes From a.txt, Content: %s\n", ret, buf); 13 } 14 15 fclose(stream);
下一章 第七章:进程环境