apue 第5章 标准I/O库
2016-09-08 21:07
246 查看
引言
标准库I/O库处理很多细节,如缓冲区分配,以优化的块长度执行I/O等流和FILE对象
当打开一个文件时,即返回一个文件描述符,然后该文件描述符就用于后续的I/O操作。而对于标准I/O库,它们的操作时围绕流(stream)进行的。流可用于单字节或多字节(“宽”)字符集
流的定向(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将是不试图设置流的定向,但返回标志该流定向的值。
标准输入、标准输出和标准错误
对一个进程预定义了3个流,并且这3个流可以自动地被进程使用,他们是:标准输入、标准输出和标准错误。这3个标准I/O流通过预定义文件指针stdin、stdout和stderr加以引用。这3个文件指针定义在头文件
缓冲
标准I/O库提供缓冲的目的是尽可能减少使用read和write调用的次数。3种形式的缓冲
全缓冲。在系统的标准I/O缓冲区后进行I/O操作。使用malloc来获得需要使用的缓冲区术语冲洗(flush)说明标准I/O缓冲区的写操作。在标准I/O库方面,flush(冲洗)意味着将缓冲区中的内容写到磁盘上。在终端驱动程序方面,flush(刷清)表示丢弃已经存储在缓冲区中的数据。
行缓冲。遇到换行符时执行真正的I/O操作。Terminal一般使用行缓冲。对于行缓冲的两个限制:(1)行缓冲有固定的size,当这个固定size的行缓冲区被填满时,不管有没有遇到换行符都开始进行一次I/O操作。(2)得到输入数据时,会自动刷新输出流。这里说的得到数据,指的时数据在输入流中, 并不要求输入的数据一定被内核读取了。
不带缓冲。标准I/O库不对字符进行缓冲存储。标准错误流默认使用不带缓冲。
ISO C要求下列缓冲特征:
标准输入、输出流:当且仅当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函数用于打开关闭缓冲机制,将一个长度为BUFSIZ的缓冲区传入参数,就会打开缓冲区,而传入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,则此函数将导致所有输出流被冲洗。
打开流
打开流的函数
#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
区别:
fopen函数打开路径名为pathname的一个指定的文件。
freopen函数在一个指定的流上打开一个指定的文件,如若该流已经打开,则先关闭该流。若该流已经定向,则使用freopen清除该定向。此函数一般用于将一个指定的文件打开为一个预定于的流:标准输入、标准输出或标准错误。
fdopen函数取一个已有的文件描述符,并使一个标准的I/O流与该描述符相结合。
type参数指定对该I/O流的读、写方式。
type | 说明 | open(2)标志 |
---|---|---|
r/rb | 读打开 | O_RDONLY |
w/wb | 写打开 | O_WRONLY or O_CREAT or O_TRUNC |
a/ab | 追加 | O_WRONLY or O_CREAT or O_APPEND |
r+/r+b/rb+ | 读写打开 | O_RDWR |
w+/w+b/wb+ | 读写打开 | O_RDWR or O_CREAT or O_TRUNC |
a+/a+b/ab+ | 文件尾读写打开 | O_RDWR or O_CREAT or O_APPEND |
如果中间没有fflush、fseek、fsetpos或rewind,则在输出的后面不能直接跟随输入。
如果中间没有fseek、fsetpos或rewind,或者一个输入操作没有达到文件尾端,则在输入操作之后不能直接跟随输出。
关闭流函数
#include <stdio.h> int fclose(FILE *fp); //返回值:若成功,返回0;若出错,返回EOF
在该文件被关闭之前,冲洗缓冲中发热输出数据。缓冲区中的任何输入数据被丢弃。
读和写流
三种不同类型的非格式化I/O中进行选择每次一个字符的I/O。一次读或写一个字符,如果流是带缓冲的,则标准I/O函数处理所有缓冲。
每次一行的I/O。如果想要一次读或写一行,则使用fgets和fputs。每行都以一个换行符终止。
直接I/O。fread和fwrite函数支持这种类型的I/O。每次I/O操作读或写某种数量的对象,而每个对象具有指定的长度。这两个函数常用于从二进制文件中每次读或写一个结构。
输入函数
#include <stdio.h> int getc(FILE *fp); int fgetc(FILE *fp); int getchar(void); //3个函数的返回值:若成功,返回下一个字符;若已达到文件尾端或出错,返回EOF
函数getchar等同于getc(stdin)。前两个函数的区别是,getc可被实现为宏,而fgetc不能实现为宏。这意味着以下几点。
getc的参数不应当是具有副作用的表达式,因为它可能会被计算多次。
因为fgetc一定是个函数,所以可以得到其地址。这就允许将fgetc的地址作为一个参数传送给另一个函数
调用fgetc所需时间很可能比调用getc要长,因为调用函数所需的时间通常长于调用宏。
区分EOF和错误发生:
#include <stdio.h> int ferror(FILE *fp); int feof(FILE *fp); //两个函数返回值:若条件为真,返回非0(真);否则,返回0(假) void clearerr(FILE *fp);
在大多数实现中,为每个流在FILE对象中维护了两个标志:
出错标志
文件结束标志
调用clearerr可以清除这两标志。 从流中读取数据以后,可以调用ungetc将字符再压送回流中。回送的字符,不一定必须是上一次读到的字符。不能回送EOF。一次成功的ungetc调用会清除该流的文件结束标志。
#include <stdio.h> int ungetc(itn c, FILE *fp); //返回值:若成功,返回c;若出错,返回EOF
输出函数
下面两个函数提供每次输入一行的功能#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不能实现为宏。
每次一行I/O
char *fgets(char * restrict str, int size, FILE * restrict stream); char *gets(char *str);
这两个函数都指定了缓冲区的地址,读入的行将送入其中。gets从标准输入读,而fgets则从指定的流读。对于fget,必须指定缓冲的长度n,此函数一直读到下一个字符为换行符为止,但是不超过n。
gets不推荐使用。
fputs和puts提供每次输出一行的功能
#include <stdio.h> int fputs(const char *restrict str, FILE *restrict fp); int puts(const char *str); //两个函数返回值:若成功,返回非负值;若出错,返回EOF
函数fputs将一个以null字节终止的字符串写到指定的流,尾端的终止符null不写出。
puts将一个以NULL字符终止的字符串写到标准输出,终止符不写出,但是,puts随后又将一个换行符写出到标注输出。(puts不推荐使用)
二进制I/O
如果进行二进制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); //两个函数的返回值:读或写的对象数
指定size为每个数组元素的长度,nobj为元素个数.
定位流
有3中方法定位标准I/O流ftell和fseek函数。这两个函数自V7以来就存在了,但是它们都假定文件的位置可以存放在一个长整型中。
ftello和fseeko函数。Single UNIX Specification引入了这两个函数,使文件偏移量可以不必一定使用长整形。它们使用off_t数据类型代替了长整形。
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);
whence的值与lseek函数相同
除了偏移量的类型是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
格式化I/O
格式化输出函数
#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字节,但该字符不包括在返回值中。
一个转换说明有4个可选部分:
% [flags] [fldwidth] [precision] [lenmodifier] convtype
标志 | 说明 |
---|---|
‘ | 将整数按千分位组字符 |
- | 在字段内左对齐输出 |
+ | 总是显示带符号转换的正负号 |
(空格) | 如果第一个字符不是正负号,则在其前面加上一个空格 |
|指定另一种转换形式(例如,对于十六进制格式,加0x前缀)
0 |添加前导0进行填充fldwidth说明最小字段宽度。转换后参数字符数若小于宽度,则多余字符位置用空格填充。字段宽度是一个非负十进制数,或是一个星号(*)。
precision说明整型转换后最少输出数字位数、浮点数转换后小数点后的最少位数、字符串转换后最大字节数。精度是一个点 . , 其后跟随一个可选的非负十进制数或一个星号 *
宽度和精度字段两者皆可为*
lenmodifier说明参数长度.
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族函数的支持。
实现细节
标准I/O库最终都要调用不带缓冲的I/O,每一个标准I/O流都有一个与其关联的文件描述符,可以对一个流调用fileno函数以获得文件描述符。#include <stdio.h> int fileno(FILE *fp); //返回值:与该流相关联的文件描述符
临时文件
创建临时文件的两个函数#include <stdio.h> char *tmpnam(char *ptr); //返回值:指向唯一路径名的指针 FILE *tmpfile(void); //返回值:若成功,返回文件指针;若出错,返回NULL
tmpnam函数产生一个与现有文件名不同的一个有效路径名字字符串
若ptr是NULL,则所产生的路径名存放在一个静态区中,指向该静态区的指针作为函数值返回。后续调用tmpnam时,会重写该静态区。
若ptr不是NULL,则认为它应该是指向长度至少是L_tmpnam个字符的数组。所产生的路径名存放在该数组中,ptr也作为函数值返回。
tmpfile创建一个临时二进制文件,在关闭该文件或程序结束时将自动删除这种文件。
XSI的扩展:
#include <stdlib.h> char *mkdtemp(char *template); //返回值:若成功,返回指向目录名的指针;若出错,返回NULL int mkstemp(char *template); //返回值:若成功,返回文件描述符;若出错,返回-1
mkdtemp函数创建了一个目录,该目录有一个唯一的名字
mkstemp函数创建了一个文件,该文件有一个唯一的名字
名字是通过template字符串进行选择的
内存流
我们可以直接调用setbuf和setvbuf函数让I/O库使用我们自己的缓冲区.创建内存流的三个函数:
#include <stdio.h> FILE *fmenopen(void *restrict buf, size_t size, const char *restrict type); //返回值:若成功,返回流指针;若错误,返回NULL
fmenopen运行调用者提供缓存区用于内存流
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函数的不同在于:
创建的流只能写打开
不能指定自己的缓冲区,但可以分别通过bufp和sizep参数访问缓冲区地址和大小;
关闭流后需要自行释放缓冲区;
对流添加字节会增加缓冲区大小。
相关文章推荐
- APUE第5章标准I/O库 笔记
- 第5章 标准I/O库
- APUE学习: 第五章, 标准I/O库
- APUE 标准I/O库
- APUE-第五章学习-标准I/O库-习题
- 【APUE】标准I/O库
- UNIX环境高级编程第5章标准I/O库
- UNIX环境高级编程(第5章 标准I/O库)
- APUE——Chapter 5:标准I/O库
- UNIX环境高级编程 第5章 标准I/O库
- 《APUE》读书笔记—第五章标准I/O库
- 《APUE》笔记-第五章-标准I/O库
- APUE学习:标准I/O库
- APUE(5)---标准I/O库 (1)
- apue:标准I/O库
- 第5章 标准I/O库
- APUE 学习笔记(四) 标准I/O库
- apue学习第九天——标准I/O库(第五章)
- APUE------标准I/O库
- APUE(5)---标准I/O库 (2)