当打开一个文件时,即返回一个文件描述符,然后该文件描述符就用于后续的I/O操作。而对于标准I/O库,它们的操作时围绕流(stream)进行的。
标准I/O文件流可用于单字节或多字节(“宽”)字符集。流的定向(stream`s orientation)决定了所读、写的字符是单字节还是多字节。只有两个函数可以改变流的定向。freopen函数清除一个流的定向;fwide函数可用于设置流的定向。
#include <stdio.h>
#include <wchar.h>
int fwide(FILE *fp, int mode);
//返回值:若流是宽定向的,返回正值;若流是字节定向的,返回负值;若流是为定向的,返回0
根据mode参数的不同值,fwide函数执行不同的工作。
1. 如若mode参数值为负,fwide将试图使指定的流是字节定向的。
2. 如若mode参数值为正,fwide将试图使指定的流是宽定向的。
3. 如若mode参数值为0,fwide将是不试图设置流的定向,但返回标志该流定向的值。
注意,fwide并不改变已定向流的定向。
对一个进程预定义了3个流,并且这3个流可以自动地被进程使用,他们是:标准输入、标准输出和标准错误。
这3个标准I/O流通过预定义文件指针stdin、stdout和stderr加以引用。这3个文件指针定义在头文件
标准I/O库提供缓冲的目的是尽可能减少使用read和write调用的次数。
标准I/O提供了以下3中类型的缓冲:
1. 全缓冲。在填满标准I/O缓冲区后进行实际I/O操作。
术语冲洗(flush)说明标准I/O缓冲区的写操作。在标准I/O库方面,flush(冲洗)意味着将缓冲区中的内容写到磁盘上。在终端驱动程序方面,flush(刷清)表示丢弃已经存储在缓冲区中的数据。
2. 行缓冲。当在输入和输出中遇到换行符时,标准I/O库执行I/O操作。
3. 不带缓冲。标准I/O库不对字符进行缓冲存储。
当且仅当标准输入和标准输出并不指向交互式设备时,它们才是全缓冲。
标准错误是不带缓冲的。
若是指向终端设备的流,则是行缓冲的;否则是全缓冲的。
对任何一个给定的流,如果我们并不喜欢这些系统默认,则可调用下列两个函数中的一个更改缓冲类型。
#include <stdio.h>
void setbuf(FILE *restrict fp, char *restrict buf);
int setvbuf(FILE *restrict fp, char *restrict buf, int mode,
size_t size);
//返回值:若成功,返回0;若出错,返回非0
可以使用setbuf函数打开或关闭缓冲机制。为了带缓冲进行I/O,参数buf必须指向一个长度为BUFSIZE的缓冲区。为了关闭缓冲,将buf设置为NULL。
使用setvbuf,我们可以精确地说明所需的缓冲类型。这是用mode参数实现的:
_IOFBF 全缓冲
_IOLBF 行缓冲
_IONBF 不带缓冲
如果指定一个不带缓冲的流,则忽略buf和size参数。如果指定全缓冲或行缓冲,则buf和size可选择地指定一个缓冲区及其长度。如果该流是带缓冲的,而buf是NULL,则标准I/O库将自动地为流分配适当长度的缓冲区。适当长度指的是由常量BUFSIZ所指的值。
任何时候我们都可以强制冲洗一个流
#include <stdio.h>
int fflush(FILE *fp);
//返回值:若成功,返回0;若出错,返回EOF
此函数使该流所有未写的数据都被传送至内核。作为一种特殊情形,如若fp是NULL,则此函数将导致所有输出流被冲洗。
下列3个函数打开一个标准I/O流
#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);
FILE *fdopen(int fd, const char *type);
//3个函数的返回值:若成功,返回文件指针;若出错,返回NULL
这3个函数的区别如下。
1. fopen函数打开路径名为pathname的一个指定的文件。
2. freopen函数在一个指定的流上打开一个指定的文件,如若该流已经打开,则先关闭该流。若该流已经定向,则使用freopen清除该定向。此函数一般用于将一个指定的文件打开为一个预定于的流:标准输入、标准输出或标准错误。
3. fdopen函数取一个已有的文件描述符,并使一个标准的I/O流与该描述符相结合。
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 |
当以读和写类型打开一个文件时。具有下列限制
1. 如果中间没有fflush、fseek、fsetpos或rewind,则在输出的后面不能直接跟随输入。
2. 如果中间没有fseek、fsetpos或rewind,或者一个输入操作没有达到文件尾端,则在输入操作之后不能直接跟随输出。
调用fclose关闭一个打开的流
#include <stdio.h>
int fclose(FILE *fp);
//返回值:若成功,返回0;若出错,返回EOF
在该文件被关闭之前,冲洗缓冲中发热输出数据。缓冲区中的任何输入数据被丢弃。
以下3个函数可用于一次读一个字符
#include <stdio.h>
int getc(FILE *fp);
int fgetc(FILE *fp);
int getchar(void);
//3个函数的返回值:若成功,返回下一个字符;若已达到文件尾端或出错,返回EOF
函数getchar等同于getc(stdin)。前两个函数的区别是,getc可被实现为宏,而fgetc不能实现为宏。这意味着以下几点。
1. getc的参数不应当是具有副作用的表达式,因为它可能会被计算多次。
2. 因为fgetc一定是个函数,所以可以得到其地址。这就允许将fgetc的地址作为一个参数传送给另一个函数
3. 调用fgetc所需时间很可能比调用getc要长,因为调用函数所需的时间通常长于调用宏。
为了区分出错还是达到文件尾端,必须调用ferror或feof。
#include <stdio.h>
int ferror(FILE *fp);
int feof(FILE *fp);
//两个函数返回值:若条件为真,返回非0(真);否则,返回0(假)
void clearerr(FILE *fp);
在大多数实现中,为每个流在FILE对象中维护了两个标志:
1. 出错标志
2. 文件结束标志
调用clearerr可以清除这两标志。
从流中读取数据以后,可以调用ungetc将字符再压送回流中。
#include <stdio.h>
int ungetc(itn c, FILE *fp);
//返回值:若成功,返回c;若出错,返回EOF
压送回到流中的字符以后又可以从流中读出,但读出字符的顺序与压送回的顺序相反。
回送的字符,不一定必须是上一次读到的字符。不能回送EOF。一次成功的ungetc调用会清除该流的文件结束标志。
输出函数
对应于上面所述的每个输入函数都有一个输出函数
#include <stdio.h>
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 <stdio.h>
char *fgets(char *restrict buf, int n, FILE *restrict fp);
char *gets(char *buf);
//两个函数返回值:若成功,返回buf;若已到达文件尾端或出错,返回NULL
这两个函数都指定了缓冲区的地址,读入的行将送入其中。gets从标准输入读,而fgets则从指定的流读。
gets是一个不推荐使用的函数。
fputs和puts提供每次输出一行的功能
#include <stdio.h>
int fputs(const char *restrict str, FILE *restrict fp);
int puts(const char *str);
//两个函数返回值:若成功,返回非负值;若出错,返回EOF
函数fputs将一个以null字节终止的字符串写到指定的流。
如果进行二进制I/O操作,那么我们更愿意一次读或写一个完整的结构。因此,提供了两个函数已执行二进制I/O操作
#include <stdio.h>
size_t fread(void *restrict ptr, size_t size, size_t nobj, FILE
*restrict fp);
size_t fwrite(void *restrict ptr, size_t size, size_t nobj, FILE
*restrict fp);
//两个函数的返回值:读或写的对象数
这些函数有以下两种常见的用法。
1. 读写一个二进制数组
2. 读或写一个结构
其中,指定size为每个数组元素的长度,nobj为元素个数
有3中方法定位标准I/O流
1. ftell和fseek函数。这两个函数自V7以来就存在了,但是它们都假定文件的位置可以存放在一个长整型中。
2. ftello和fseeko函数。Single UNIX Specification引入了这两个函数,使文件偏移量可以不必一定使用长整形。它们使用off_t数据类型代替了长整形。
3. fgetpos和fsetpso函数。这两个函数是有IOS C引入的。它们使用一个抽象数据类型fpos_t记录文件的位置。这种数据类型可以根据需要定义为一个足够大的数,用以记录文件位置。
#include <stdio.h>
long ftell(FILE *fp);
//返回值:若成功,返回当前文件位置指示;若出错,返回-1L
int fseek(FILE *fp, long offset, int whence);
//返回值:若成功,返回0;若出错,返回-1
void rewind(FILE *fp);
对于一个二进制文件,其文件位置指示器是从文件起始位置开始度量,并以字节为度量单位的。
参数offset是为了定位一个二进制文件以及解释这种偏移量的方式。
whence的值与lseek函数相同。
对于文本文件,它们的文件当前位置可能不以简单的字节偏移量来度量。这主要也是在非UNIX系统中,它们可能以不同的格式存放文本文件。为了定位一个文本文件,whence一定要是SEEK_SET,而且offset只能有两种值:0 (后退到文件的起始位置),或是对该文件的ftell所返回的值。使用rewind函数也可将一个流设置到文件的起始位置。
除了偏移量的类型是off_t而非long以外,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 *restrict fp, fpos_t *restrict pos);
int fsetpos(FILE *fp, const fpos_t *pos);
//两个函数返回值:若成功,返回0;若出错,返回非0
文件位置指示器的当前值存入由pos指向的对象中。
格式化输出由5个printf函数来处理的
#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,...);
//返回值:若缓冲区足够大,返回将要存入数组的字符数;若编码出错,返回负值
printf将格式化数据写到标准输出,fprintf写至指定的流,dprintf写至指定的文件描述符,sprint将格式化的字符送入数组buf中。sprint在改数组的尾端自动加一个null字节,但该字符不包括在返回值中。
注意sprintf函数可能会造成由buf指向的缓冲区的溢出。为了解决这个问题,引入了snprintf
一个转换说明有4个可选部分:
% [flags] [fldwidth] [precision] [lenmodifier] convtype
标志 | 说明 |
---|---|
‘ | 将整数按千分位组字符 |
- | 在字段内左对齐输出 |
+ | 总是显示带符号转换的正负号 |
(空格) | 如果第一个字符不是正负号,则在其前面加上一个空格 |
# | 指定另一种转换形式(例如,对于十六进制格式,加0x前缀) |
0 | 添加前导0进行填充 |
fldwidth说明最小字段宽度。转换后参数字符数若小于宽度,则多余字符位置用空格填充。字段宽度是一个非负十进制数,或是一个星号(*)。
precision说明整型转换后最少输出数字位数、浮点数转换后小数点后的最少位数、字符串转换后最大字节数。精度是一个点 . , 其后跟随一个可选的非负十进制数或一个星号 *
宽度和精度字段两者皆可为*。此时,一个整型参数指定宽度或精度的值。该整型参数正好位于被转换的参数之前。
lenmodifier说明参数长度
长度修饰符 | 说明 |
---|---|
hh | 将相应的参数按signed或unsigned char类型输出 |
h | 将相应的参数按signed或unsigned short类型输出 |
l | 将相应的参数按signed或unsigned long或宽字符类型输出 |
ll | 将相应的参数按signed或unsigned long long类型输出 |
j | intmax_t或uintmax_t |
z | size_t |
t | ptrdiff_t |
L | long double |
convtype不是可选的
转换类型 | 说明 |
---|---|
d、i | 有符号十进制 |
o | 无符号八进制 |
u | 无符号十进制 |
x,X | 无符号十六进制 |
f, F | 双精度浮点数 |
e, E | 指数格式双精度浮点数 |
g, G | 根据转换后的值解释为f、F、e或E |
a, A | 十六进制指数格式双进度浮点数 |
c | 字符 |
s | 字符串 |
p | 指向void的指针 |
n | 到目前为止,此printf调用输出的字符的数目将被写入到指针所指向的带符号整型中 |
% | 一个%字符 |
C | 宽字符 |
S | 宽字符串 |
#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,...);
//3个函数返回值:赋值的输入项数;若输入出错或在任一转换前已到达文件尾端,返回EOF
一个转换说明有三个可选择部分:
% [*] [fldwidth] [m] [lenmodifier] convtype
可选择的星号(*)用于抑制转换。按照转换说明的其余部分对输入进行转换,单转换结果并不存放在参数中。
fldwidth说明最大宽度。
lenmodifier说明要用转换结果赋值的参数大小。由printf函数族支持的长度修饰符同样得到scanf族函数的支持。
转换类型 | 说明 |
---|---|
d | 有符号十进制,基数为10 |
i | 有符号十进制,基数由输入格式决定 |
o | 无符号八进制 |
u | 有符号十进制,基数为10 |
x、X | 无符号十六进制 |
a、A、e、E、f、F、g、G | 浮点数 |
c | 字符 |
s | 字符串 |
[ | 匹配列出的字符序列,以]终止 |
[^ | 匹配除列出字符以外的所有字符,以 ] 终止 |
p | 指向void指针 |
n | 将到目前为止该函数读取的字符数写入到指针所指向的无符号整型中 |
% | 一个%符号 |
C | 宽字符 |
S | 宽字符串 |
可以对一个流调用fileno函数以获得其描述符
#include <stdio.h>
int fileno(FILE *fp);
//返回值:与该流相关联的文件描述符
ISO C标准I/O库提供了两个函数以帮助创建临时文件。
#include <stdio.h>
char *tmpnam(char *ptr);
//返回值:指向唯一路径名的指针
FILE *tmpfile(void);
//返回值:若成功,返回文件指针;若出错,返回NULL
tmpnam函数产生一个与现有文件名不同的一个有效路径名字字符串。每次调用它时,都产生一个不同的路径名,最多调用的次数是TMP_MAX。
若ptr是NULL,则所产生的路径名存放在一个静态区中,指向该静态区的指针作为函数值返回。后续调用tmpnam时,会重写该静态区。若ptr不是NULL,则认为它应该是指向长度至少是L_tmpnam个字符的数组。所产生的路径名存放在该数组中,ptr也作为函数值返回。
tmpfile创建一个临时二进制文件,在关闭该文件或程序结束时将自动删除这种文件。
#include <stdlib.h>
char *mkdtemp(char *template);
//返回值:若成功,返回指向目录名的指针;若出错,返回NULL
int mkstemp(char *template);
//返回值:若成功,返回文件描述符;若出错,返回-1
mkdtemp函数创建了一个目录,该目录有一个唯一的名字;mkstemp函数创建了一个文件,该文件有一个唯一的名字。名字是通过template字符串进行选择的
mkstemp创建的临时文件并不会自动删除。如果希望从文件系统命名空间中删除该文件,必须自己对它解除链接。
有3个函数可用于内存流的创建
#include <stdio.h>
FILE *fmenopen(void *restrict buf, size_t size, const char
*restrict type);
//返回值:若成功,返回流指针;若错误,返回NULL
buf参数指向缓冲区的开始位置,size参数指定了缓冲区大小的字节数。如果buf参数为空,fmenopen函数分配size字节数的缓冲区。在这种情况下,当流关闭时,缓冲区会被释放。type参数控制如何使用流
type | 说明 |
---|---|
r过rb | 为读而打开 |
w或wb | 为写而打开 |
a或ab | 追加;为再第一个null字节处写而打开 |
r+或r+b或rb+ | 为读和写打开 |
w+或w+b或wb+ | 把文件截断至0长,为读和写打开 |
a+或a+b或ab+ | 追加;为再第一个null字节处读和写而打开 |
#include <stdio.h>
FILE *open_memstream(char **bufp, size_t *sizep);
#include <wchar.h>
FILE *open_wmenstream(wchar_t **bufp, size_t *sizep);
//两个函数的返回值:若成功,返回流指针;若出错,返回NULL
open_menstream函数创建的流是面向字节的,open_wmemstream函数创建的流是面向宽字节的。这两个函数与fmenopen函数的不同在于:
1. 创建的流只能写打开
2. 不能指定自己的缓冲区,但可以分别通过bufp和sizep参数访问缓冲区地址和大小;
3. 关闭流后需要自行释放缓冲区;
4. 对流添加字节会增加缓冲区大小。
但是在缓冲区地址和大小的使用上必须遵循一些原则。第一,缓冲区地址和长度只有在调用fclose或fflush后才生效;第二,这些值只有在下一次流写入或调用fclose前才有效。