1. 概述
这里所说的是标准I/O库。以标准二字限定,是因为接下来介绍的I/O函数由ISO C标准说明并在诸多操作系统上都进行了实现,包括UNIX like OS, Linux, Mac, Windows等。标准I/O库处理了很多细节,例如缓冲区分配,以优化长度执行I/O等。这些处理使得用户不必担心如何使用正确的块长度,从而大大提高了开发效率。
标准I/O库实际上就是在我在上一片博客(Linux系统C语言读写文件总结 (一))中提到的基本I/O操作函数(read,write等)的一个封装。标准I/O函数操作对应的是于文件相关的流(用文件指针FILE *进行表示),而不是文件描述符(fd)。一个FILE结构中包括了实际操作文件的文件描述符,指向用于该流缓冲区的指针,缓冲区的长度,当前在缓冲区中的字符数以及出错标志等等。
此外流的定向分为面向单字节的和多字节字符集。freopen函数可以用来清除流的定向,fwide函数可以用来设置流的定向。
2. 标准输入,标准输出和标准出错
有几个特殊的流,对应于标准输入,标准输出和标准出错。他们的文件指针分别是stdin, stdout和stderr,并被定义在头文件中。到这儿了就可以回想下这三个特殊文件对应的文件描述符。
3. 缓冲
标准I/O提供了三种类型的缓冲:
(1)全缓冲。
(2)行缓冲。
(3)不带缓冲。
4. 打开和关闭流
打开流,成功返回文件指针,否则返回NULL
#include
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 filedes, const char *type);type指定对该I/O流的读写方式,ISO C规定该参数有15种不同的值,如下
type
说明
r或rb
w或wb
a或ab
r+或r+b或rb+
w+或w+b或wb+
a+或a+b或ab+
为读而打开
把文件截短至0长,或为写而创建
添加;为在文件尾写而打开,或为写而创建
为读和写而打开
把文件截短至0长,或为读和写而打开
为在文件尾读和写而打开或创建
注:这里r+, w+, a+都可以对文件进行写。区别在于以r+方式打开,文件位置指向文件开头;以w+方式打开,会将原文件长度截短为0;以a+方式打开会将文件位置指向文件结尾处,并且将结尾处作为文件位置的开始,fseek函数也不能将文件位置指向开头处。所以这也就解释了我的疑问:为什么会有r+的存在。
关闭流,成功返回0,否则返回EOF
#include
int fclose(FILE *fp);
5. 读和写流(非格式化)
三种不同类型的非格式化I/O:
(1)每次一个字符的I/O。
输入函数
#include
int getc(FILE *fp);
int fgetc(FILE *fp);
int getchar(void);成功则返回下一个字符,若已达到文件结尾或出错则返回EOF。
getc可以实现为宏(更快),而fgetc则只能是函数;getchar等价于getc(stdin)。
其对应的输出函数为
#include
int putc(int c, FILE *fp);
int fputc(int c, FILE *fp);
int putchar(int c);成功则返回c,否则返回EOF。
putc可以实现为宏(更快),而fputc则只能是函数;putchar等价于putc(c, stdout)。
(2)每次一行的I/O。
输入函数
#include
char *fgets(char *restrict buf, int n, FILE *restrict fp);
char *gets(char *buf);
成功则返回buf,若已达到文件结尾或出错则返回NULL。
fgets从指定流读,而gets从标准输入读。fgets必须指定缓冲区的长度n,此函数一直读到下一个换行符为止,但是不超过n-1(缓冲区以null结尾)个字符,读入的字符被送入缓冲区。
gets这是一个不推荐使用的函数。因为用户不能指定缓冲区的长度,可能造成缓冲区溢出。
其对应的输出函数为
#include
char *fputs(const char *restrict str, FILE *restrict fp);
char *puts(const char *str);
成功则返回非负值,否则返回EOF。
尽量避免使用gets和puts。使用fgets和fputs时需要记住在每行终止处必须处理换行符。
(3)直接I/O(二进制I/O)。
#include
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);返回值:读或写的对象数。
例子:
a. 写一个数组
float data[10];
if(write(&data[2], sizeof(float), 4, fp) != 4)
err_sys("fwrite error");
b. 写一个结构体
struct{
short cout;
long total;
char name[NAMESIZE];
}item;
if(fwrite(&item, sizeof(item), 1, fp) != fp)
err_sys("fwrite error");
6. 格式化I/O
格式化输出
#include
int printf(const char *restrict format, ...);
int fprintf(FILE *restrict fp, const char *restrict format, ...);
//若成功则返回输出的字符数,若输出出错则返回负值
int sprintf(FILE *restrict buf, const char *restrict format, ...);
int snprintf(FILE *restrict buf, size_t n, const char *restrict format, ...);
//若成功则返回存入数组的字符数,如编码出错则返回负值
其中的format的描述形式如:%[flags][fldwidth][precision][lenmodifier]convtype
flags选项如下表
标志
说明
-
在字段内左对齐输出
+
总是显示带符号转换的符号
(空格)
如果第一个字符不是符号,则在其前面加上一个空格
#
指定另外一种转换形式(例如,对于十六进制格式,加0x前缀)
0
添加前导0(而非空格)进行填充
fldwidth说明转换的最小字段宽度,如果转换得到的字符较少,则用空格填充它。字段宽度是一个非负十进制数,或是一个星号(*)。
precision说明整型转换后最少输出数字位数,浮点数转换后小数点后的最少位数,字符串转换后的最大字符数。精度时一个句号(.),后接一个可选的非负十进制整数或一个星号(*)。
转换类型选项如下表
转换类型
说明
d, i
有符号十进制
o
无符号八进制
u
无符号十进制
x, X
无符号十六进制
f, F
double精度浮点数
e, E
指数格式的double精度浮点数
g, G
解释为f, F或e, E,取决于被转换的值
a, A
十六进制指数格式的double精度浮点数
c
字符(若带长度修饰符l,则为宽字符)
s
字符串(若带长度修饰符l,则为宽字符)
p
指向void的指针
n
将到目为止,所写的字符数写入到指针所指向的无符号整型中
%
%字符
C
宽字符(XSI扩展,等效于lc)
S
宽字符串(XSI扩展,等效于ls)
格式化输入
#include
int scanf(const char *restrict format, ...);
int fscanf(FILE *restrict fp, const char *restrict format, ...);
int sscanf(FILE *restrict buf, const char *restrict format, ...);
//若成功则返回指定的输入项数,如输入出错或在任意变换前已经到达文件结尾则返回EOF
format格式转换说明如下:%[*][fldwidth][lenmodifier]convtype
除转换说明和空白字符外,格式字符串中的其他字符必须与输入匹配。若有一个字符不匹配,则停止后续处理,不再读输入的其他部分。
*用于抑制转换。
fldwidth说明最大宽度(即最大字符数)。
lenmodifier说明要用转换结果初始化的参数大小。
转换类型选项如下表。与print族有些类似,但是也有所差别。一个差别是,存储在无符号类型中的结果可在输入时带上符号。例如,-1可被转换成4,294,967,295赋予无符号整型变量。
转换类型
说明
d
有符号十进制,基数为10
i
有符号十进制,基数由输入格式决定
o
无符号八进制(输入可选地有符号)
u
无符号十进制,基数为10(输入可选地有符号)
x
无符号十六进制(输入可选地有符号)
a, A, e, E, f, F, g, G
浮点数
c
字符(若带长度修饰符l,则为宽字符)
s
字符串(若带长度修饰符l,则为宽字符)
[
匹配列出的字符序列,以 ] 终止
[^
匹配列出字符序列以外的所有字符,以 ] 终止
p
指向void的指针
n
将到目为止读取的字符数写入到指针所指向的无符号整型中
%
%字符
C
宽字符(XSI扩展,等效于lc)
S
宽字符串(XSI扩展,等效于ls)
7. 定位流
#include
long ftell(FILE *fp);
//成功则返回当前文件位置指示,出错返回-1L
int fseek(FILE *fp, long offset, in whence);
//成功返回0,出错返回非0值
void rewind(FILE *fp);
//将一个流设置到文件的起始位置
#include
off_t ftello(FILE *fp);
//成功返回当前文件位置指示,出错则返回-1
int fseeko(FILE *fp, off_t offset, int whence);
//成功则返回0,出错则返回非0值除了offset的类型时off_t而非long外,ftello函数和ftell相同,fseeko和fseek相同
#include
int fgetpos(FILE *restrict fp, fpos_t *restrict pos);
int fsetpos(FILE *fp, const fpos_t *pos);
//成功则返回0,出错则返回非0值这两个函数时ISO C标准引入的。如果程序要移植到非UNIX系统,则可以使用该函数。其中fgetpos将文件位置指示器的的当前值存入到由pos指向的对象中。在以后调用fsetpos时,可以使用此值将流重新定位至该位置。