5.1 流和FILE
所有I/O函数都是围绕文件描述符的。 当打开一个文件时返回一个文件描述符,该文件描述符就用于后续的I/O操作。
标准I/O库的操作是围绕流(stream) 进行的。
标准I/O文件流可用于单字节或多字节(“宽” )字符集。 流的定向决定了所读、写的字符是单字节还是多字节的。fwide函数可用于对未定向的流设置定向。
当打开一个流时标准I/O函数fopen返回一个指向FILE对象的指针。
5.2 缓冲
标准I/O库提供缓冲的目的是尽可能减少使用read和write调用的次数。
标准I/O提供了以下3种类型的缓冲:
(1)全缓冲。在填满缓冲区之后才进行IO操作。对磁盘上的文件时通常使用全缓冲。在标准I/O库方面,调用fflush可以刷新缓冲区,把缓冲区内的内容写入磁盘。
(2)行缓冲。当在输入和输出中遇到换行符时,标准I/O库执行I/O操作。由于行缓冲的缓冲区大小是固定的,所以如果超过缓冲区也会进行刷新。
(3)不缓冲。立刻输出。
通常来说,标准错误不带缓冲;若是指向终端设备的流,则是行缓冲的; 否则是全缓冲。
setvbuf
可以设置缓冲类型。
5.3 打开流
打开文件流返回的都是FILE指针。打开类型使用w, r, a, +
等描述符,b
表示二进制打开,在unix
系统中b
没有作用。
若流引用终端设备,则该流是行缓冲。否则按系统默认,流被打开时是全缓冲的。
#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);
//从fd打开文件流
FILE *fdopen(int fd, const char *type);
关闭文件流使用fclose。在文件被关闭之前, 冲洗缓冲中的输出数据, 缓冲区中的任何输入数据被丢弃。 如果标准I/O库已经为该流自动分配了一个缓冲区, 则释放此缓冲区。
当一个进程正常终止时,所有带未写缓冲数据的标准I/O流都被冲洗,所有打开的标准I/O流都被关闭。
5.4 读和写流
三种读和写流的方式
1. 每次一个字符的IO,getc, putc
2. 每次一行的IO,gets, puts
3. 以二进制直接IO, fwrite, fread
读取字符
#include <stdio.h>
int getc(FILE *fp);
int fgetc(FILE *fp);
int getchar(void);
函数getchar等同于getc(stdin)。 前两个函数的区别是,getc可被实现为宏,而fgetc不能实现为宏。 返回整型的理由是要返回所有可能的字符值再加上一个已出错或已到达文件尾端的指示值。
出错标志
常量EOF表示出错,其值经常是−1。无论是出错还是到达末尾都返回同一个值,使用函数判断是否是错误。
#include <stdio.h>
int ferror(FILE *fp);
int feof(FILE *fp);
从流中读取数据以后,可以调用ungetc将字符再压送回流中。一次成功的ungetc调用会清除该流的文件结束标志。
写字符
#include <stdio.h>
int putc(int c, FILE *fp);
int fputc(int c, FILE *fp);
int putchar(int c);
5.5 按行IO
读
char *fgets(char *restrict buf, int n, FILE *restrict fp);
char *gets(char *buf);
两个区别
- fgets必须指定缓冲的长度n,一直读取到下一个换行符。缓冲区始终以null结尾,gets读取时没有指定长度,可能会造成缓冲区溢出。
- gets并不将换行符存入缓冲区中。
写
#include <stdio.h>
int fputs(const char *restrict str, FILE *restrict fp);
int puts(const char *str);
两个区别
- 函数fputs将一个以null字节终止的字符串写到指定的流。
- puts最后会将一个换行符写到标准输出。
5.5 IO效率
对比getc, fgetc, fgets, read:
系统CPU时间几乎相同,原因是因为所有这些程序对内核提出的读、写请求数基本相同。标准IO会自己决定最佳IO长度。
用户CPU时间相差很多,和函数调用次数有关。fgets会比fgetc快。调用同样次数的read和fgetc,前者的时间更久。因为前者每次都是系统调用,而后者系统调用次数更少。
系统调用与普通的函数调用相比需要花费更多的时间。
5.6 二进制IO
因为按照按字符或行读取有很多限制,不适合复杂的结构体的读写。
#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);
使用二进制I/O的基本问题是,只能用于读在同一系统上已写的数据。对不同系统之间的IO,比如网络IO需要借助互认的某种协议。
5.7 定位流
#include <stdio.h>
long ftell(FILE *fp); //返回值: 若成功, 返回当前文件位置指示; 若出错, 返回-1L
int fseek(FILE *fp, long offset, int whence); //返回值: 若成功, 返回0; 若出错, 返回−1
void rewind(FILE *fp); //重新定位到开头
5.8 格式化IO
格式化输出
#include <stdio.h>
int printf(const char *restrict format, ...);
printf将格式化数据写到标准输出,fprintf写至指定的流,dprintf写至指定的文件描述符,sprintf 将格式化的字符送入数组buf中。sprintf在该数组的尾端自动加一个 null字节,但该字符不包括在返回值中。
格式化输入
#include <stdio.h>
int scanf(const char *restrict format, ...);
5.9 临时文件
下面的函数创建可用的临时路径名,然后创建临时文件。临时文件在关闭或程序退出时自动删除。
#include<stdio.h>
char *tmpnam(char *ptr);
FILE *tmpfile(void);
下面的方式创建,临时文件不会自动删除。
mkdtemp和mkstemp
5.10 内存流
可以设置使标准IO库使用给定的缓冲区,IO流并没有底层文件,所有的I/O都是通过在缓冲区与主存之间来回传送字节来完成。
创建者提供缓冲区用于内存流。
#include <stdio.h>
FILE *fmemopen(void *restrict buf, size_t size, const char *restrict type)
一些特性:
- 以追加方式打开时,总是定位到第一个null字节位置或者最后一个字节。
- 以非追加方式打开时,定位到第一个字节。
- 如果buf是null,任何操作都没有意义。
- 任何时候需要增加流缓冲区中数据量以及调用fclose、fflush、 fseek、 fseeko以及fsetpos时都会在当前位置写入一个null字节。
内存流只在linux上支持,在mac上运行结果不同。