标准I/O库 (第五章)
1. 流定向
标准I/O可用于单字节或多字节。流最初创建时没有定向,第一次使用多字节I/O,则定向为宽字节;使用单字节则为字节定向。可改变定向的函数:freopen()和fwide(),如下:
#include <stdio.h>
#include <wchar.h>
int fwide(FILE *fp, int mode);
// 宽字节返回正;单字节返回负;未定向返回0
//mode<0:设为字节定向;
//mode>0:设为宽字节定向;
//mode=0:不设置,返回当前定向;
2. 标准输入、输出和出错
#include <stdio.h>
stdin
stdout
stderr
3. 缓冲
标准I/O的三种缓冲:
- 全缓冲 : 缓冲区填满或者调用fflush()时执行I/O;对于fflush(),若为标准I/O,则将数据写入磁盘;若为终端设备,则丢弃缓冲区中的数据;
- 行缓冲 : 遇到换行符时,执行I/O。如果缓冲区满了,即使没有换行符也要执行I/O;
- 不带缓冲,如stderr
ISO C缓冲特征
- 当且仅当标准输入、输出不指向交互式设备时,它们才是全缓冲;
- stderr决不是全缓冲
默认情况下:stderr不带缓冲;指向终端设备的流为行缓冲,其它为全缓冲。
更改缓冲的函数,如下:
#include <stdio.h>
void setbuf(FILE *restrict fp, char *restrict buf);
//buf : 设为指向长度为 BUFSIZ 的缓冲区,则为带缓冲I/O;设为NULL则关闭缓冲
int setvbuf(FILE *restrict fp, char *restrict buf, int mode, size_t size);
// 成功返回0,失败非0
//mode : _IOFBF(全缓冲);_IOLBF(行缓冲):_IONBF(无缓冲)
//若带缓冲,而buf为NULL,则自动分配缓冲(BUFSIZ)
int fflush(FILE *fp);
//若fp为NULL,则冲洗所有输出流。
4. 打开流
#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);
//将指定文件打开为指定流,如stdin/stdout/stderr
FILE *fdopen(int fd, const char *type);
// 出错返回NULL
//常用于管道或Socket返回的fd
int fclose(FILE *fp);
//冲洗缓冲中的输出数据,丢弃输入数据。
type参数如下:
b表示二进制,Linux中无用(Linux不区分文本文件和二进制文件);
fdopen以写打开不会截断文件;
追加写方式不能用于创建文件,所以pathname必须存在。
5. 读写流
- 每次读写一个字符,如下:
#include <stdio.h>
int getc(FILE *fp);
// 实现为宏
int fgetc(FILE *fp);
// 实现为函数
int getchar(void);
// 同 getc(stdin)
// 到达文件末尾或出错返回 EOF
int putc(int c, FILE *fp);
int fputc(int c, FILE *fp);
int putchar(int c);
// 成功返回c,出错返回EOF
EOF为一负值,常为-1。要区分实际问题,用下面函数:
#include <stdio.h>
int ferror(FILE *fp);
int feof(FILE *fp);
// 条件为真返回非0;否则返回0
每个流在FILE中有两个标志:出错标志和文件结束标志,可用clearerr清除这两个标志。
将字符再压回流中,如下:
#include <stdio.h>
int ungetc(int c, FILE *fp);
// 成功返回c,出错返回EOF
送回的字符可再读出,但顺序相反,送回的可为其它字符,但不能为EOF,到文件尾仍可再送回字符。该方法只是将字符写到了绥冲区中。
- 每次读写一行,如下:
#include <stdio.h>
char *fgets(char *restrict buf, int n, FILE *restrict fp);
// 最多读取 n-1 个字符;保留换行
char *gets(char *buf);
// 无换行
// 文件尾或出错返回NULL
int fputs(const char *restrict str, FILE *restrict fp);
// 需要自己添加换行符
int puts(const char *str);
// 自动添加换行符
// 成功返回非负值,出错返回EOF
- 每次读写一个结构
#include <stdio.h>
size_t fread(void *restrict ptr, size_t size, size_t nobj, File *restrict fp);
// 返回值少于nobj,则调用ferror()和feof()判断是出错还是文件尾
size_t fwrite(void *restrict ptr, size_t size, size_t nobj, File *restrict fp);
// 返回值少于nobj则出错
// 返回读写对象数
6. 定位流
#include <stdio.h>
long ftell(FILE *fp);
// 成功返回当前位置,出错返回 -1L
int fseek(FILE *fp, long offset, int whence);
// 成功返回0,出错返回 -1,文件函数用法同 lseek()
// 对于文本文件,whence一定为SEEK_SET,且offset只能为0或ftell()的返回值
void rewind(FILE *fp);
// 将流设到起始位置
off_t ftello(FILE *fp);
// 成功返回当前位置,出错返回 (off_t)-1
int fseeko(FILE *fp, off_t offset, int whence);
int fgetpos(FILE *restrict fp, fpos_t *restrict pos);
int fsetpos(FILE *fp,const fpos_t *pos);
// 调用fgetpos()将位置存入pos,以后可调用fsetpos()重新定位到该位置。
// 成功返回0;出错返回非0
7. 格式化I/O
#include <stdio.h>
int printf(const char *restrict format, ...);
int fprintf(FILE *restrict fp, const char *restrict format, ...);
int dprintf(int fd, 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, ...);
// 成功返回存入buf的字符数,出错返回负值(在buf中自动添加'\0')
#include <stdio.h>
#include <stdarg.h>
int vprintf(const char *restrict format, va_list arg);
int vfprintf(FILE *restrict fp, const char *restrict format, va_list arg);
int vdprintf(int fd, 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);
// 成功返回存入buf的字符数,出错返回负值(在buf中自动添加'\0')
#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, ...);
#include <stdio.h>
#include <stdarg.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);
8. fileno() 从FILE中得到fd
#include <stdio.h>
int fileno(FILE *fp);
9. 临时文件
创建临时文件,如下:
#include <stdio.h>
char *tmpnam(char *ptr);
// 返回唯一路径名的指针,最多调用次数(TMP_MAX)
// ptr为NULL,则产生的路径名在静态区中,后续再调用会覆盖;
// ptr不为NULL,则应指向长度至少为L_tmpnam(strio.h)的buf。
FILE *tmpfile(void);
// 创建的二进制文件(wb+),在关闭文件或程序结束时自动删除。
#include <stdlib.h>
char *mkdtemp(char *template);
// 成功返回目录名的指针,失败返回NULL
// 目录权限位:0700
int mkstemp(char *template);
// 成功返回文件fd,失败返回 -1
// 以读写方式打开,不会自动删除
template是后6位为XXXXXX的路径名,如下:
“/tmp/dirXXXXXX” -> /tmp/dirUmBT7h
tmpnam()和tempnam()缺点:有时间差,另的进程可以用相同的名字创建文件,所以最好用 tmpfile()和mkstemp()
10. 内存流
#include <stdio.h>
FILE* fmemopen(void *restrict buf, size_t size, const char *restrict type);
FILE* open_memstream(char**ptr, size_t* sizeloc);
#include <wchar.h>
FILE* open_wmemstream(wchar_t** ptr, size_t* sizeloc);
如果buf为NULL,则fmemopen分配size字节数的缓冲区,此时当流关闭时缓冲区会被释放。
fmemopen()函数打开一个内存流,使你可以读取或写入由buf指定的缓冲区。其返回FILE*fp就是打开的内存流,虽然仍使用FILE指针进行访问,但其实并没有底层文件(并没有磁盘上的实际文件,因为打开的内存流fp是在内存中的),所有的I/O都是通过在缓冲区与主存(就是内存)之间来回传送字节来完成的。
重点内容
open_memstream()函数打开一个面向字节的流来写入一个缓冲区,而open_wmemstream()函数创建一个面向宽字符的流。当用fclose()关闭流或用fflush()刷新流时,bufp和sizep被更新,以包含缓冲区的指针及其大小。只要没有进一步的输出流发生,这些值仍然有效。如果执行额外的输出,必须再次刷新流来存储新的值,才能再次使用它们。一个空字符被写入缓冲区的末尾,但它存储在sizep中的size值中不包括它。
通过调用fmemopen()、open_memstream()或open_wmemstream()创建的一个与内存缓冲区相关联的流的输入和输出操作,发生在内存缓冲区的范围内,受限于实现。对于用open_memstream()或open_wmemstream()打开的流的情况,内存区域动态增长,以适应必要的写操作。对于输出,在刷新或关闭操作期间,数据从函数setvbuf()提供的缓冲区移动到内存流。如果没有足够的内存来增长内存区域,或者操作需要访问相关内存区域以外的地方,相关的操作失败。
因为避免了缓冲区溢出,内存流非常适用于创建字符串。因为内存流只访问主存,不访问磁盘上的文件,所以对于把标准I/O流作为参数用于临时文件的函数来说,会有很大的性能提升。