本章讲述标准I/O库,这个库由ISO C标准说明。
fwide
函数可用于设置流的定向:
#include
#include
int fwide(FILE *fp, int mode);
// 返回值:若流是宽定向的,返回正值;若流是字节定向的,返回负值;若流是未定向的,返回0
fwide
将试图使指定的流是字节定向的;fwide
将试图使指定的流是宽定向的;fwide
将不试图设置流的定向,但返回标识该流定向的值。注意:
fwide
不改变已定向流的定向;fwide
无出错返回,在调用fwide
前先清除errno,返回时检查errno的值。当打开一个流时,标准I/O函数fopen
返回一个指向FILE对象的指针。该对象包含了标准I/O库为管理该流需要的所有信息,包括用于实际I/O的文件描述符、指向用于该流缓冲区的指针加粗样式、缓冲区的长度、当前在缓冲区中的字符数以及出错标志等。
对一个进程预定义了3个流,并且这3个流可以自动地被进程使用,它们分别是:标准输入、标准输出和标准错误,分别通过预定义文件指针stdin、stdout和stderr加以引用。
标准I/O提供了以下3种类型的缓冲:
可以调用setbuf
或setvbuf
来更改缓冲类型:
#include
void setbuf(FILE *restrict fp, char *restrict buf);
int setvbuf(FILE *restrict fp, char *restrict buf, int mode, size_t size);
// 返回值:若成功,返回0;若出错,返回非0
setbuf
函数打开或关闭缓冲机制,参数buf必须指向一个长度位BUFSIZ的缓冲区;为了关闭缓冲,将buf设置位NULL;setvbuf
可以根据mode精确说明所需的缓冲类型:mode | 缓冲 |
---|---|
_IOFBF | 全缓冲 |
_IOLBF | 行缓冲 |
_IONBF | 不带缓冲 |
#include
int fflush(FILE *fp);
// 返回值:若成功,返回0;若出错,返回EOF
此函数使该流所有未写的数据都被传送至内核,特别的,如若fp是NULL,则此函数将导致所有输出流被冲洗。
下列3个函数打开一个标准I/O流:
#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 fd, const char *type);
// 3个函数的返回值:若成功,返回文件指针;若出错,返回NULL
这3个函数的区别如下:
fopen
函数打开路径名为pathname的一个指定的文件;freopen
函数在一个指定的流上打开一个指定的文件,如若该流已经打开,则先关闭该流;若该流已经定向,则使用freopen
清除该定向;此函数一般用于将一个指定的文件打开为一个预定义的流:标准输入、标准输出或标准错误;fdopen
函数取一个已有的文件描述符,并使一个标准的I/O流与该描述符相结合;此函数常用于由创建管道和网络通信通道函数返回的描述符。type参数指定对该I/O流的读、写方式,共有15种不同的值:
调用fclose
关闭一个打开的流:
#include
int fclose(FILE *fp);
// 返回值:若成功,返回0;若出错,返回EOF
一旦打开了流,则可在3种不同类型的非格式化I/O中进行选择,对其进行读、写操作:
fgets
和fputs
,每行都以一个换行符终止,当调用fgets
时,应说明能处理的最大行长;fread
和fwrite
函数支持这种类型的I/O,每次I/O操作读或写某种数量的对象,而每个对象具有指定的长度,这两个函数常用于从二进制文件中每次读或写一个结构。以下3个函数可用于一次读一个字符:
#include
int getc(FILE *fp);
int fgetc(FILE *fp);
int getchar(void);
// 3个函数的返回值:若成功,返回下一个字符;若已到达文件尾端或出错,返回EOF
getchar
等同于getc(stdin)
;getc
可被实现为宏,而fgetc
不能实现为宏;EOF
被要求是一个负值,其值通常是-1。不管是出错还是到达文件尾端,这3个函数都返回同样的值,为了区分这两种不同的情况,必须调用ferror
和feof
:
#include
int ferror(FILE *fp);
int feof(FILE *fp);
// 两个函数的返回值:若条件为真,返回非0(真);否则,返回0(假)
在大多数实现中,为每个流在FILE对象中维护了两个标志:
调用clearerr
可以清除这两个标志:
#include
void clearerr(FILE *fp);
从流中读取数据以后,可以调用ungetc
将字符再压送回流中:
#include
int ungetc(int c, FILE *fp);
// 返回值:若成功,返回c;若出错,返回EOF
#include
int putc(int c, FILE *fp);
int fputc(int c, FILE *fp);
int putchar(int c);
// 3个函数返回值:若成功,返回c;若出错,返回EOF
putchar(c)
等同于putc(c, stdout)
;putc
可被实现为宏,而fputc
不能实现为宏。下面两个函数提供每次输入一行的功能:
#include
char *fgets(char *restrict buf, int n, FILE *restrict fp);
char *gets(char *buf);
// 两个函数返回值:若成功,返回buf;若已到达文件尾端或出错,返回NULL
gets
从标准输入读,fgets
从指定的流读;fgets
必须指定缓冲区的长度n,此函数一直读到下一个换行符为止,但不超过n-1个字符,读入的字符被送入缓冲区;fputs
和puts
提供每次输出一行的功能:
#include
int fputs(const char *restrict str, FILE *restrict fp);
int puts(const char *str);
// 两个函数返回值:若成功,返回非负值;若出错,返回EOF
fputs
将一个以null字节终止的字符串写到指定的流,尾端的终止符null不写出;puts
将一个以null字节终止的字符串写到标准输出,终止符不写出,但puts
随后又将一个换行符写到标准输出。exit
函数将会冲洗任何未写的数据,然后关闭所有打开的流;read
和write
函数相比并不慢很多。二进制I/O操作,一次读或写一个完整的结构,下列两个函数用于执行I/O操作:
#include
size_t fread(void *restrict buf, 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);
// 两个函数的返回值:读或写的对象数
ferror
或feof
以判断究竟是哪一种情况;有3种方法定位标准I/O流:
ftell
和fseek
函数;ftello
和fseeko
函数;fgetpos
和fsetpos
函数。#include
long ftell(FILE *fp);
// 返回值:若成功,返回当前文件位置指示;若出错,返回-1L
int fseek(FILE *fp, long offset, int whence);
// 若成功,返回0;若出错,返回-1
void rewind(FILE *fp);
fseek
的whence参数的可选值:SEEK_SET表示从文件的起始位置开始,SEEK_CUR表示从当前文件位置开始,SEEK_END表示从文件的尾端开始;ftell
所返回的值;rewind
函数将一个流设置到文件的起始位置。#include
off_t ftello(FILE *fp);
// 返回值:若成功,返回当前文件位置;若出错,返回(off_t)-1
int fseeko(FILE *fp, off_t offset, int whence);
// 返回值:若成功,返回0;若出错,返回-1
#include
int fgetpos(FILE *restrict fp, fpos_t *restrict pos);
int fsetpos(FILE *fp, const fpos_t *pos);
// 两个函数返回值:若成功,返回0;若出错,返回非0
fgetpos
将文件位置指示器的当前值存入由pos指向的对象中,在以后调用fsetpos
时,可以使用此值将流重新定位至该位置。格式化输出是由5个printf
函数来处理的:
#include
int printf(const char *restrict format, ...);
int fprintf(FILE *restrict fp, const char *restrict format, ...);
int dprintf(int fd, const char *restrict format, ...);
// 3个函数返回值:若成功,返回输出字符数;若输出出错,返回负值
int sprintf(char *restrict buf, const char *restrict format, ...);
// 返回值:若成功,返回存入数组的字符数;若编码出错,返回负值
int snprintf(char *restrict buf, size_t n, const char *restrict format, ...);
// 返回值:若缓冲区足够大,返回将要存入数组的字符数;若编码出错,返回负值
printf
将格式化数据写到标准输出;fprintf
写至指定的流;dprintf
写至指定的文件描述符;sprintf
将格式化的字符送入数组buf中,在该数组的尾端自动添加一个null字节,但该字符不包括在返回值中;snprintf
比sprintf
多了一个参数n,它指明了缓冲区长度,超过缓冲区尾端写的所有字符都被丢弃。每个参数按照转换说明编写,转换说明以百分号%
开始,除转换说明外,格式字符串中的其他字符将按原样,不经任何修改被复制输出。一个转换说明有4个可选择的部分,下面将它们都展示于方括号中:
%[flags][fldwidth][precision][lenmodifier]convtype
flags
标志如下图所示:fldwidth
说明最小字段宽度,转换后参数字符数若小于宽度,则多余字符位置用空格填充,字段宽度是一个非负十进制数,或是一个星号(*);precision
说明整型转换后最少输出数字位数、浮点数转换后小数点后的最少位数、字符串转后最大字节数,精度是一个点(.),其后跟随一个可选的非负十进制数或一个星号(*);lenmodifier
说明参数长度,其可能值如下图:convtype
不是可选的,它控制如何解释参数,下图列出了各种转换类型字符:printf
族的变体类似于上面的5种,但是可变参数(...
)替换成了arg:#include
#include
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);
// 所有3个函数返回值:若成功,返回输出字符数;若输出出错,返回负值
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);
// 函数返回值:若缓冲区足够大,返回存入数组的字符数;若编码出错,返回负值
执行格式化输入处理的是3个scanf
函数:
#include
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, ...);
// 3个函数返回值:赋值的输入项数;若输入出错或在任一转换前已到达文件尾端,返回EOF
scanf
族用于分析输入字符串,并将字符序列转换成指定类型的变量;格式说明控制如何转换参数,以便对它们赋值,转换说明以百分号%
字符开始,除转换说明和空白字符外,格式字符串中的其他字符必须于输入匹配。一个转换说明有3个可选择的部分,下面将它们都示于方括号中:
%[*][fldwidth][m][lenmodifier]convtype
fldwidth
说明最大宽度(即最大字符数);lenmodifier
说明要用转换结果赋值的参数大小,由printf
函数族支持的长度修饰符同样得到scanf
族函数的支持;convtype
说明转换类型,scanf
族函数支持的转换类型如下图:printf
族相同,scanf
族也使用由#include
#include
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);
// 3个函数返回值:指定的输入项目数;若输入出错或在任一转换前文件结束,返回EOF
每个标准I/O流都有一个与其相关联的文件描述符,可以对一个流调用fileno
函数以获得其描述符:
#include
int fileno(FILE *fp);
// 返回值:与该流相关联的文件描述符
ISO C标准I/O库提供了两个函数来创建临时文件:
#include
char *tmpnam(char *ptr);
// 返回值:指向唯一路径名的指针
FILE *tmpfile(void);
// 返回值:若成功,返回文件指针;若出错,返回NULL
tmpnam
函数产生一个与现有文件名不同的一个有效路径名字符串,每次调用它时,都产生一个不同的路径名;tmpnam
时,会重写该静态区;tmpfile
创建一个临时二进制文件(类型wb+),在关闭该文件或程序结束时将自动删除这种文件。Single UNIX Specification为处理临时文件定义了另外两个函数,即mkdtemp
和mkstemp
:
#include
char *mkdtemp(char *template);
// 返回值:若成功,返回指向目录名的指针;若出错,返回NULL
int mkstemp(char *template);
// 返回值:若成功,返回文件描述符;若出错,返回-1
mkdtemp
函数创建了一个目录,该目录有一个唯一的名字;mkstemp
函数创建了一个文件,该文件有一个唯一的名字;mkdtemp
函数创建的目录使用下列访问权限位集:S_IRUSR | S_IWUSR | S_IXUSR;mkstemp
函数创建的文件使用访问权限位:S_IRUSR | S_IWUSR;mkstemp
创建的临时文件并不会自动删除。有3个函数可用于内存流的创建,第一个是fmemopen
函数:
#include
FILE *fmemopen(void *restrict buf, size_t size, const char *restrict type);
// 返回值:若成功,返回流指针;若错误,返回NULL
fmemopen
函数分配size字节的缓冲区,在这种情况下,当流关闭时缓冲区会被释放;用于创建内存流的其他两个函数分别是open_memstream
和open_wmemstream
:
#include
FILE *open_memstream(char **bufp, size_t *sizep);
#include
FILE *open_wmemstream(wchar_t **bufp, size_t *sizep);
// 两个函数的返回值:若成功,返回流指针;若出错,返回NULL
open_memstream
函数创建的流是面向字节的,open_wmemstream
函数创建的流是面向宽字节的,这两个函数与fmemopen
函数的不同在于:
chapter5