标准I/O库
2.1 标准输入、标准输出和标准错误
进程中预定义了这3个流,可以自动地被进程调用。这些流引用的文件与文件描述符中的STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO所引用的相同。这3个标准I/O流通过预定义文件指针stdin、stdout和stderr加以引用,定义在头文件<stdio.h>中。2.2 打开标准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 fd, const char *type); 返回值:成功返回文件指针,出错返回NULL |
(1) fopen打开路径名为pathname的一个指定文件。
(2) freopen在一个指定的流(fp)上打开一个指定的文件,如若该流已经打开,则先关闭该流。若该流已经定向,则使用freopen清除该定向。一般用于将一个指定的文件打开为一个预定义的流:stdin,stdout,stderr。
(3) fdopen取一个已有的文件描述符,并使一个标准的I/O流与该描述符相结合。常用于创建管道和网络通信通道返回的描述符,因为这次特殊的文件不能用标准I/O函数打开。
type参数指定对该I/O流的读、写方式:
Type | 说明 | open(2)标志 |
r | 只读打开 | O_RDONLY |
w | 把文件长度截断0,写打开或创建 | O_WRONLY|O_CREAT|O_TRUNC |
a | 在文件为写,或为写而创建 | O_WRONLY|O_CREAT|O_APPEND |
r+ | 为读写打开 | O_RDWR |
w+ | 文件长度截断0,或为读写而打开或创建 | O_RDWR|O_CREAT|O_TRUNC |
a+ | 在文件尾读和写而打开或创建 | O_RDWR|O_CREAT|O_APPEND |
在指定w或a类型创建一个新文件时,我们无法说明该文件的访问权限位,而是由系统的umask值来限制这些权限。我们可以通过调整umask值来限制我们创建一个新文件的权限。
2.3 读和写流
2.3.1 每次一个字符的I/O
一次读或写一个字符,如果流是带缓冲的,则标准I/O函数处理所有缓冲。 #include <stdio.h> int getc(FILE *fp); int fgetc(FILE *fp); int getchar(void); 返回值:成功返回一个读取到的字符,若到文件尾或出错,返回EOF |
不管是出错还是达到文件尾端,函数否返回EOF,为了区分这两种不同的情况,需要嗲用ferror或feof函数。
2.3.2 每次一行的I/O
#include <stdio.h> char *fgets(char *restrict buf, int n, FILE *retrict fp); char *gets(char *buf); 返回值:成功返回buf,达到文件尾端或出错返回NULL |
fgets函数一直读到下一个换行符为止,但是不超过n-1个字符。读到的字符被送入缓冲区buf中。该缓冲区以null字节结尾。如果改行包括最后一个换行符的字符数超过n-1,则fgets只返回一个不完整的行,但缓冲区总是以null字节结尾。
gets是一个不推荐使用的函数,有可能造成缓冲区溢出。
2.3.3 二进制I/O
因为fputs在遇到null字节时就停止,而在结构中可能包含有null字节,所以不能实现读结构的要求。同样输入数据中包含null字节,fgets也不能正确工作。因此,提供了下列两个函数以执行二进制I/O操作。 #include <stdio.h> size_t fread(void *restrict prt, size_t size, size_t nobj,FILE *restrict fp); size_t fwrite(const void *restrict prt, size_t size, size_t nobj,FILE *restrict fp); 返回值:读或写的对象数 |
2.4 流的定位
#include <stdio.h> long ftell(FILE *fp); 返回值:成功返回当前文件的位置指示,出错返回-1L int fseek(FILE *fp,long offset, int whence); 返回值:成功返回0,出错返回-1 void rewind(FILE *fp); |
2.5 关闭流
#include <stdio.h> int fclose(FILE *fp); 返回值:成功返回0,出错返回EOF |
当一个进程中止时(直接调用exit函数或main函数返回),则所有带未写缓冲数据的标准I/O流都被冲洗,所有打开的标准I/O流都被关闭。
2.6 缓冲
2.6.1 全缓冲
全缓冲,在填满标准I/O缓冲区后才进行实际I/O操作。对于驻留在磁盘上的文件通常是有标准I/O库实施全缓冲的。术语冲洗(flush)说明标准I/O缓冲区的写操作。缓冲器可由标准I/O例程自动冲洗,或者调用fflush冲洗一个流。
2.6.2 行缓冲
当在输入和输出中遇到换行符时,标准I/O库执行I/O操作。虽然标准库允许我们一次输出一个字符,但只有在写了一行之后才进行实际I/O操作。当设计一个终端时,通常使用行缓冲。对于行缓冲有两个限制,第一,因为标准I/O库用来收集每一行的缓冲区的长度是固定的,所以只要填满了缓冲区,那么即使还没有写一个换行符,也进行I/O操作。第二,任何时候只要通过标准版I/O库从一个不带缓冲的流或者一个行缓冲的流得到输入数据,那么就会冲洗所有行缓冲输出流。2.6.3 不缓冲
标准I/O库不对字符进行缓冲存储,字符会被立即输出。标准出错流strerr通常是不带缓冲的,这使得出错信息可以尽快显示出来,而不管他们是否含有一个换行符。对任何一个给定的流,如果我们并不喜欢这些系统默认,可以调用下列两个函数中的一个更改缓冲类型:
#include <stdio.h> void setbuf(FILE * fp, char *buf); int setvbuf(FILE *fp, char *buf, int mode, size_t size); 返回值:成功返回0,出错返回非0 |
使用setvbuf函数,我们可以精确地说明所需的缓冲类型。这是用mode参数实现的:
mode | 说明 |
_IOFBF | 全缓冲 |
_IOLBF | 行缓冲 |
_IONBF | 不到缓冲 |
任何时候,我们都可以强制冲洗一个流。
#include <stdio.h> int fflush(FILE *fp); 返回值:成功返回0,出错返回EOF |
2.7 内存流
标准I/O库吧数据缓存在内存中,因此每次一字符和每次一行的I/O更有效。我们也可以通过调用setbuf或setvbuf函数让I/O库使用我们自己的缓冲区。标注的I/O流,虽然仍使用FILE指针进行访问,但其实并没有底层文件,所有的I/O都是通过在缓冲区与主存之间来回传送字节完成的。 #include <stdio.h>
FILE *fmemopen(void *buf, size_t size, const char * type); 返回值:成功返回流指针,出错返回NULL |
type参数控制如何使用流:
type | 说明 |
r | 为读而打开 |
w | 为写而打开 |
a | 追加,为在第一个null字节处写而打开 |
r+ | 为读写而打开 |
w+ | 把文件截断0长度,为读写而打开 |
a+ | 追加:为在第一个null字节处读和写而打开 |
(1) 无论何时以追加写方式打开内存时,当前位置设为缓冲区中的第一个null字节。如果缓冲区中不存在null字节,则当前位置就设为缓冲区结尾的后一个字节。当流不是以追加方式打开时,当前位置设为缓冲去的开始位置。因为追加写模式通过第一个null字节确定数据的尾端,内存留并不适合存储二进制数据(二进制数据在数据尾端之前就可能包含多个null字节)。
(2) 如果buf参数是一个null指针,打开流进行读写没有任何意义。
(3) 任何时候需要增加缓缓冲区中的数量以及调用fclose、fflush、fseek、fseeko和fsetpos时都会在当前位置写入一个null字节。