标准I/O库处理很多细节,如缓冲区分配、以优化的块长度执行I/O等。这些处理使用户不必担心如何选择正确的块长度。
缓冲
标准I/O库提供缓冲的目的是尽可能减少使用read和write的调用次数。标准I/O提供了3种类型的缓冲。
- 全缓冲。在这种情况下,在填满标准I/O缓冲区后才进行实际的I/O操作。对于驻留在磁盘上的文件通常是由标准I/O库实施全缓冲的。flush说明标准I/O缓冲区的写操作。缓冲区可由标准I/O例程自动flush,或者可以调用函数fflush冲洗一个流。
- 行缓冲。在这种情况下,当输入和输出中遇到换行符时,标准I/O库执行I/O操作。当流涉及一个终端时,通常使用行缓冲。
- 不带缓冲。标准I/O库不对字符进行缓冲存储。标准错误流stderr通常是不带缓冲的,这就使得出错信息可以尽快显示出来。
对于任何一个给定的流,如果我们不喜欢这些系统默认,则可调用下列两个函数中的一个更改缓冲类型。
#include <stdio.h>
void setbuf(FILE *fp, char *buf);
int setvbuf(FILE *fp, char *buf, int mode, size_t size);
返回值:若成功,返回0;若出错,返回非0
可以使用setbuf函数打开或关闭缓冲机制。为了带缓冲I/O,参数buf必须指向一个长度为BUFSIZ的缓冲区(该常量定义在<stdio.h>中)。通常在此之后该流就是全缓冲的。为了关闭缓冲,将buf设置为NULL。
使用setvbuf函数,可以精确的说明所需的缓冲类型。这是用mode参数实现的:
- _IOFBF 全缓冲
- _IOLBF 行缓冲
- _IONBF 不带缓冲
如果指定一个不带缓冲的流,则忽略buf和size参数。如果指定全缓冲或行缓冲,则buf和size可选择地指定一个缓冲区及其长度。如果该流是带缓冲的,而buf是NULL,则标准I/O库将自动地为该流分配适当长度的缓冲区。适当长度指的是由常量BUFSIZ所指定的值。
一般而言,应由系统选择缓冲区的长度,并自动分配缓冲区。
任何时候,我们都可以用fflush函数冲洗一个流。
#include <stdio.h>
int fflush(FILE *fp);
返回值:若成功,返回0;若出错,返回EOF
打开流
#include <stdio.h>
FILE *fopen(const char *pathname, const char *type);
FILE *freopen(const char *pathname, const char *type, FILE *fp);
FILE *fdopen(int fd, const char *type);
3个函数返回值:若成功,返回文件指针;若出错,返回NULL
这三个函数的区别如下。
- fopen函数打开路径名为pathname的一个指定的文件。
- freopen函数再一个指定的流上打开一个指定的文件,如该流已经打开,则先关闭该流。如该流已经定向,则使用freopen清除该定向。此函数一般用于将一个指定的文件打开为一个预定义的流:标准输入、标准输出或标准错误。
- fdopen函数取一个已有的文件描述符,并使一个标准的I/O流与该描述符相结合。此函数常用于由创建管道和网络通信通道函数返回的描述符。因为这些特殊类型文件不能用标准I/O函数fopen
参数type指定对该I/O流的读写方式。
type | 说明 | open(2)标志 |
---|---|---|
r或rb | 为读而打开 | O_RDONLY |
w或wb | 把文件截断至0,或为写而创建 | O_WRONLY | O_CREAT | O_TRUNC |
a或ab | 追加;为在文件尾写而打开,或为写而创建 | O_WRONLY | O_CREAT | O_APPEND |
r+或r+b或rb+ | 为读和写而打开 | O_RDWR |
w+或w+b或wb+ | 把文件截断至0长,或为读和写而打开 | O_RDWR | O_CREAT | O_TRUNC |
a+或a+b或ab+ | 为在文件尾读和写而打开或创建 | O_RDWR | O_CREAT | O_APPEND |
除非流引用了终端设备,否则按系统默认,流被打开时是全缓冲的。若流引用终端设备,则该流是行缓冲的。
调用fclose关闭一个打开的流。
#include <stdio.h>
int fclose(FILE *fp);
返回值:若成功,返回0;若出错,返回EOF
在该文件被关闭之前,冲洗缓冲中的输出数据。当一个进程正常终止时,则所有带未写缓冲数据的标准I/O流都被冲洗,所有打开的标准I/O流都被关闭。
读写流
一旦打开了流,则可在三种不同类型的非格式化I/O中进行选择,对其进行读写操作。
- 每次一个字符的I/O。
- 每次一行的I/O。如果想要每次读或写一行,则使用fgets或fputs。每次都以换行符终止。当调用fgets时,应说明能处理的最大行长度。
- 直接I/O。fread和fwrite函数支持这种类型的I/O。这两个函数常用于从二进制文件中每次读或写一个结构。
每次一个字符I/O
#include <stdio.h>
int getc(FILE *fp);
int fgetc(FILE *fp);
int getchar(void);
三个函数的返回值:若成功,返回下一个字符;若已到达文件尾或出错,返回EOF
前两个函数的区别是,getc可被实现为宏,而fgetc不能实现为宏。这意味着以下几点。
- getc的参数不能是具有副作用的表达式。
- 因为fgetc一定是一个函数,所以可以得到其地址。这就允许将fgetc的地址作为一个参数传送给另一个函数。
- 调用fgetc所需的时间很可能比调用getc要长,因为调用函数的时间通常长于调用宏。
注意,不管是到达文件尾还是出错,这三个函数都返回相同的值。为了区分不同的情况,必须调用ferror或feof。
#include <stdio.h>
int ferror(FILE *fp);
int feof(FILE *fp);
两个函数返回值:若条件为真,返回非0;否则,返回0
void clearerr(FILE *fp);
大多数实现中,为每个流在FILE对象中维护了两个标志:出错标志;文件结束标志。调用clearerr可以清除这两个标志。
对应于getc,fgetc和getchar三个输入函数,输出函数如下。
#include <stdio.h>
int putc(int c, FILE *fp);
int fputc(int c, FILE *fp);
int putchar(int c);
三个函数返回值:若成功,返回c;若出错,返回EOF
每次一行I/O
以下两个函数提供每次输入一行的功能。
#include <stdio.h>
char *fgets(char *buf, int n, FILE *fp);
char *gets(char *buf);
两个函数返回值:若成功,返回buf;若已到达文件尾或出错,返回NULL
gets从标准输入读,而fgets从指定的流读。对于fgets,必须指定缓冲的长度n,此函数一直读到写一个换行符为止,但是不超过n-1个字符,读入的字符被送入缓冲区。该缓冲区以null字符结尾。如果改行包括最后一个换行符的字符数超过n-1,则fgets只返回一个不完整的行,但是,缓冲区总是以null字符结尾。对fgets的下一次调用会继续读该行。gets是一个不推荐使用的函数。其问题是调用者在使用gets时不能指定缓冲区的长度。这有可能导致内存溢出,产生不可预料的结果。
fputs和puts提供每次输出一行的功能。
#include <stdio.h>
int fputs(const char *str, FILE *fp);
int puts(const char *str);
两个函数返回值:若成功,返回非负值;若出错,返回EOF
函数fputs将一个以null字符终止的字符串写到指定的流,尾端的null字符不写出。puts将一个以null字符终止的字符串写到标准输出,null字符不写出。但是,puts随后又将一个换行符写到标准输出。
二进制I/O
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nobj, FILE *fp);
size_t fwrite(const void *ptr, size_t size, size_t nobj, FILE *fp);
两个函数返回值:读或写的对象数
fread和fwrite返回读或写的对象数。对于读,如果出错或到达文件尾端,则此数字可以少于nobj。在这种情况应该用ferror或feof以判断究竟是哪一种情况。对于写,如果返回值少于nobj,则出错。
定位流
有三种方式可以定位标准I/O流。
- ftell和fseek函数。它们都假定文件的位置可以存放在一个长整形中。
- ftello和fseeko函数。它们用off_t代替了long。
- fgetpos和fsetpos函数。它们使用一个抽象数据类型fpos_t记录文件位置。这种数据类型可以根据需要定义为一个足够大的数,用以记录文件位置。
#include <stdio.h>
long ftell(FILE *fp);
返回值:若成功,返回当前文件位置指示;若出错,返回-1L
int fseek(FILE *fp, long offset, int whence);
返回值:若成功,返回0;若出错,返回-1
void rewind(FILE *fp);
使用rewind函数也可以将一个流设置到文件的起始位置。
除了偏移量的类型是off_t以外,ftello函数与ftell相同,fseeko与fseek相同。
#include <stdio.h>
off_t ftello(FILE *fp);
返回值:若成功,返回当前文件位置;若出错,返回(off_t)-1
int fseeko(FILE *fp, off_t offset, int whence);
返回值:若成功,返回0;若出错,返回-1
#include <stdio.h>
int fgetpos(FILE *fp, fpos_t *pos);
int fsetpos(FILE *fp, const fpos_t *pos);
两个函数返回值:若成功,返回0;若出错,返回-1
fgetpos将文件位置指示器的当前值存入pos指向的对象中。在以后调用fsetpos时,可以使用此值将流重新定位至该位置。
格式化I/O
5个格式化输出函数。
#include <stdio.h>
int printf(const char *format, ...);
int fprintf(FILE *fp, const char *format, ...);
int dprintf(int fd, const char *format, ...);
三个函数返回值:若成功,返回输出字符数;若输出错误,返回负值
int sprintf(char *buf, const char *format, ...);
返回值:若成功,返回存入数组的字符数;若出错,返回负值
int snprintf(char *buf, size_t n, const char *format, ...);
返回值:若缓冲区足够大,返回将要存入数组的字符数;若出错,返回负值
printf将格式化数据写到标准输出,fprintf写到指定的流,dprintf写到指定的文件描述符,sprintf将格式化数据写入buf中,sprintf自动在buf尾端添加null字符,但null字符不包含在返回值中。snprintf指定了缓冲区的长度,如果输入的字符数超过缓冲区长度,则将被丢弃。使用snprintf更加安全。
5个格式化输出函数的变体,将可变参数换成了arg。
#include <stdarg.h>
#include <stdio.h>
int vprintf(const char *format, va_list arg);
int vfprintf(FILE *fp, const char *format, va_list arg);
int vdprintf(int fd, const char *format, va_list arg);
3个函数的返回值:若成功,返回输出的字符数;若出错,返回负值
int vsprintf(char *buf, const char *format, va_list arg);
返回值:若成功,返回存入数组的字符数;若出错,返回负值
int vsnprintf(char *buf, size_t n, const char *format, va_list arg);
返回值:若缓冲区足够大,返回存入数组的字符数;若出错,返回负值
格式化输出转换说明
一个转换说明有4个可选的部分。
%[flags][fieldwidth][precision][lenmodifier]convtype
- flags
标志 说明 ' 将整数按千位分组字符 - 在字段内左对齐输出 + 总是显示带符号转换的正负号 space 如果第一个字符不是正负号,则在其前面加上空格 # 指定另一种转换形式(例如,对于十六进制格式,加0x前缀) 0 添加前导0(而非空格)进行填充 - fieldwidth。说明最小字段宽度。转换后字符数若小于宽度,则多余字符位置用空格填充。字符宽度是一个非负十进制整数,或者是一个星号(*)。
- precision。
- lenmodifier。
- convtype。
对于转换说明请参考APUE的第128页到129页的详细说明。
格式化输入函数
#include <stdio.h>
int scanf(const char *format, ...);
int fscanf(FILE *fp, const char *format, ...);
int sscanf(const char *buf, const char *format, ...);
三个函数的返回值:赋值的输入项数;若输入出错或在任一转换前已经到达文件尾端,返回EOF
#include <stdarg.h>
#include <stdio.h>
int vscanf(const char *format, va_list arg);
int vfscanf(FILE *fp, const char *format, va_list arg);
int vsscanf(const char *buf, const char *format, va_list arg);
三个函数返回值:指定的输入项目数;若出错或在任一转换前文件结束,返回EOF
可以使用fileno函数获取流的文件描述符。
#include <stdio.h>
int fileno(FILE *fp);
返回值:与该流相关的文件描述符
临时文件
ISO C标准库提供了两个函数以帮助创建临时文件。
#include <stdio.h>
char *tmpnam(char *ptr);
返回值:指向唯一路径名的指针
FILE *tmpfile(void);
返回值:若成功,返回文件指针;若出错,返回NULL
tmpnam产生一个与现有文件名不同的有效路径名字符串。tmpfile创建一个临时二进制文件(wb+),在关闭该文件或程序结束时将自动删除该文件。
Single UNIX Specification为处理临时文件定义了另外两个函数。
#include <stdlib.h>
char *mkdtemp(char *template);
返回值:若成功,返回指向目录名的指针;若出错,返回NULL
int mkstemp(char *template);
返回值:若成功,返回文件描述符;若出错,返回-1
内存流
有3个函数可以用于内存流的创建。
#include <stdio.h>
FILE *fmemopen(void *buf, size_t size, const char *type);
返回值:若成功,返回流指针;若错误,返回NULL