1、引言
标准IO库有ISO C标准说明
- 标准IO库处理很多细节:包括缓冲区分配、优化的块长度执行IO
2、流和FILE对象
- 当用标准IO库打开或创建一个文件时,已使一个流与一个文件相关联
- 对于ASCII字符集,一个字符用一个字节表示,对于国际字符集,一个字符可用多个字节表示。标准IO文件流可用单字节或多字节字符集。
流的定向
决定了所读写的字符是单字节还是多字节(创建时未定向),若在未定向的流上使用多字节IO,则将流设置为宽定向的,用单字节IO则设置为字节定向的
#include <wchar.h>
int fwide(FILE *stream, int mode); //设置流定向
- mode:
- 为负则设置为字节定向
- 为正则指定为宽定向
- 0不设置但返回流的定向值
- 注意:fwide不改变已定向的流,且无出错返回
3、标准输入/输出/错误
- 对一个进程预定义了stdin、stdout、stderr与前面提到的STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO所引用的相同
4、缓冲
- 目的是尽可能减少使用read()、write()调用次数
- 全缓冲:填满缓冲区后才进行IO操作,在一个流上执行第一次IO操作时,常调用malloc获得需使用的缓冲区
- 也可调用fflush()冲洗一个流。标准IO库意味着冲洗到磁盘,终端驱动程序意味着丢弃缓冲区中的数据
- 行缓冲:遇到换行符时,执行IO操作,涉及到终端时常使用行缓冲
- 满了也会执行
- 任何时候从一个不带缓冲的流或一个行缓冲的流得到输入数据也执行IO
- 不带缓冲:标准IO不对字符进行缓冲存取(如fputs)
- stderr通常不带缓冲
- 全缓冲:填满缓冲区后才进行IO操作,在一个流上执行第一次IO操作时,常调用malloc获得需使用的缓冲区
- ISO C要求:
- 仅当标准输入输出不指向交互式设备时,它们才是全缓冲
- 标准错误绝不会是全缓冲
- 很多系统默认:
- 标准错误不带缓冲
- 若是指向终端设备的流,则是行缓冲的,否则是全缓冲的
- 可更改缓冲类型:
#include <stdio.h> void setbuf(FILE *stream, char *buf); void setbuffer(FILE *stream, char *buf, size_t size); void setlinebuf(FILE *stream); int setvbuf(FILE *stream, char *buf, int mode, size_t size);
- 函数需要在打开流之后,指向IO操作前调用
- 使用setbuf时,buf必须指向一个BUFSIZE长度的缓冲区,关闭可设为NULL
- setvbuf可精确说明:
- _IOFBF:全缓冲
- _IOLBF:行缓冲
- _IONBF:不带缓冲
- 任何时候都可以强制冲洗一个流
int fflush(FILE *stream);
5、打开流
#include <stdio.h>
FILE *fopen(const char *path, const char *mode);
FILE *freopen(const char *path, const char *mode, FILE *stream);
FILE *fdopen(int fd, const char *mode);
- freopen() 函数在一个指定的流上打开一个指定的文件。已打开则先关闭,已定向则清除。一般用于将指定文件打开为预定义的流(stdin、stdout…)
- fdopen() 取一个已有的文件描述符,并使一个IO流与之结合。
- fdopen()写方式打开时并不截断文件(因为已经打开),
- mode中有加号时:
- 如果中间没有fflush()、fseek()、fsetpos()、rewind(),则输出后面不能跟输入
- 如果中间没有fflush()、fsetpos()、rewind(),或输入操作没有的达到文件尾,则输入操作后不能跟输出
int fclose(FILE *stream);
- 文件关闭前,冲洗缓冲区中的输出数据,并释放缓冲区
- 进程正常终止时,所有带未写缓冲数据的标准IO流被冲洗,所有打开的标准IO流被关闭
6、读和写流
- 有三种不同类型的非格式化IO
- 每次一个字符的IO:带缓冲则标准IO处理所有缓冲
- 每次一行的IO:以换行符终止,fgets()应说明能处理的最大行长
- 直接IO:每次IO操作读写某种数量的对象
6.1、输入函数
int getc(FILE *stream);
int fgetc(FILE *stream);
int getchar(void);
- getc()可以实现为宏,fgetc不能
- getc()参数不应该时具有副作用的表达式
- fgetc()是函数,所以可以得到其地址
- fgetc()的时间要长
- 需要注意的是,这里的返回值是int,这样是为了返回所有可能的字符加一个已出错或文件尾指示值。
- 为了区分出错和文件尾:
int ferror(FILE *stream); int feof(FILE *stream); void clearerr(FILE *stream);
- 大多数实现为FILE维护两个标志:出错标志和文件结束标志
- 从流中读取数据后,也可以再将字符再压送回流中:
int ungetc(int c, FILE *stream);
- 再读出的顺序与压送的顺序相反
- 压送并没有写到底层文件或设备,只是写到缓冲中
6.2、输出函数
int putc(int c, FILE *stream);
int fputc(int c, FILE *stream);
int putchar(int c);
- putc()可以实现为宏而fputc()不能
7、每次一行IO
char *fgets(char *s, int size, FILE *stream);
char *gets(char *s);
- 函数都指定了缓冲区地址,读入将送入其中
- fgets()读入不超过n-1个字符直到换行符,缓冲区总是以null结尾
- gets是一个不推荐的函数,且不读入换行符
int fputs(const char *s, FILE *stream);
int puts(const char *s);
- 提供每次输出一行的功能
- fputs将null结尾的字符串写到指定流
- puts将null结尾的字符串写到标准输出流,再输出一个换行符
8、标准IO的效率
9、二进制IO
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb,
FILE *stream);
- 不同环境下两个函数可能不能正常工作
- 在一个结构中,偏移量可能不同
- 用来存储多字节整数和浮点值得二进制格式在不同的系统结构间可能不同
10、定位流
int fseek(FILE *stream, long offset, int whence);
long ftell(FILE *stream);
void rewind(FILE *stream);
- whence的值与lseek相同,但是ISO C并没要求一个实现对二进制文件支持SEEK_END规格说明。
- 对于文本文件,whence一定要是SEEK_SET(当前位置可能不以字节偏移量度量),offset只能是0或ftell的返回值。
int fseeko(FILE *stream, off_t offset, int whence);
off_t ftello(FILE *stream);
- 不同之处是这里的函数类型是
off_t
,它的长度大于32位
int fgetpos(FILE *stream, fpos_t *pos);
int fsetpos(FILE *stream, fpos_t *pos);
- 上述函数是ISO C引入的,为了程序的可移植性应该使用它们
11、格式化IO
11.1 格式化输出
int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
int dprintf(int fd, const char *format, ...);
int sprintf(char *str, const char *format, ...);
int snprintf(char *str, size_t size, const char *format, ...);
- sprintf可能造成缓冲区溢出
- 一个转换说明有4个可选择的部分:
- %[flags][fldwidth][precision][lenmodifier]convtype
- flags
- `:将整数按千分位分组字符
- - :在字段内左对齐输出
- +:显示带符号转换的正负号
- (空格):字符第一个不是正负号则加空格
- #:指定转换形式,如十六进制加0x前缀
- 0:添加前导0进行填充
- fldwidth说明最小字段宽度,非负十进制数或*号
- precision说明整数转换后最少输出数字位数、浮点数转换后的最少位数、字符串转换后的最大字节数精度是一个点,其后跟随一个非负十进制数或*
- lenmodifier说明参数长度,可以是hh,h,l,ll,j,z,t,L
- convtype可以是d,i,o,u,x,X,f,F,e,E,g,G,a,A,c,s,p,n,C,S,%
- flags
11.2 格式化输入
int scanf(const char *restrict format, ...);
int fscanf(FILE *restrict stream, const char *restrict format, ...);
int sscanf(const char *restrict s, const char *restrict format, ...);
int vfscanf(FILE *restrict stream, const char *restrict format,va_list arg);
int vscanf(const char *restrict format, va_list arg);
int vsscanf(const char *restrict s, const char *restrict format,va_list arg);
12、实现细节
每个标准IO库都要调用前面讨论的IO例程,每个标准IO流都有一个相关联的文件描述符
int fileno(FILE *stream);
13、临时文件
char *tmpnam(char *ptr);
FILE *tmpfile(void);
char *mkdtemp(char *template);
int mkstemp(char *template);
- tmpnam产生一个与现有文件名不同的一个有效路径名字串,每次调用产生一个不同的路径名,最多调用TMP_MAX次。
- tmpfile创建一个临时的二进制文件(wb+),关闭该文件或程序结束时自动删除。
- mkdtemp创建一个目录,该目录有唯一的名字
- mkstemp创建一个文件,该文件有唯一的名字,名字通过template进行选择,template的后6位设置成设置为xxxxxx,函数将用不同字符来替换这些占位符
- mkstemp创建的临时文件不会自动删除,需要手动解除链接
14、内存流
通过调用setbuf()或setvbuf()让IO库使用我们自己的缓冲区。而标准IO流虽然仍使用FILE指针进行访问,但其实没有底层文件,所有IO流都是通过在缓冲区与主存之间来回传递字节来完成。
FILE *fmemopen(void *restrict buf, size_t size, const char *restrict mode);
- 函数允许调用者提供缓冲区用于内存流,buff为空时,自动分配size字节并在流关闭时自动释放。
- 内存流不适合二进制数据
- 如果buf是null指针,读写操作没有意义
- 增加缓冲流中的数据量以及调用fclose()、fflush()、fseek()、fseeko()、fsetpos()都会在当前位置写入一个null字节。
#include <stdio.h>
FILE *open_memstream(char **bufp, size_t *sizep);
#include <wchar.h>
FILE *open_wmemstream(wchar_t **bufp, size_t *sizep);
- open_memstream()函数创建的流是面向字节的,后者是面向宽字节的
- 与fmemopen区别在于
- 创建的流只用写打开
- 不能指定自己的缓冲区,但可以通过bufp和sizep访问缓冲区地址和大小
- 关闭流后不需要自行释放缓冲区
- 对流添加字节会增加缓冲区大小
- 缓冲区可以增长,下次调用fclose()时可能已经改变
- 因为避免了内存溢出,内存流非常适合创建字符串
15、标准IO的替代软件
- 标准IO库效率不高与需要复制的数据量有关,当使用每次一行函数fgets()、fputs()时,需要复制两次数据:内核与标准IO缓冲(read、write)、IO缓冲与用户程序之间