apue 第5章 标准I/O库

引言

标准库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种形式的缓冲
  1. 全缓冲。在系统的标准I/O缓冲区后进行I/O操作。使用malloc来获得需要使用的缓冲区
    术语冲洗(flush)说明标准I/O缓冲区的写操作。在标准I/O库方面,flush(冲洗)意味着将缓冲区中的内容写到磁盘上。在终端驱动程序方面,flush(刷清)表示丢弃已经存储在缓冲区中的数据。
  2. 行缓冲。遇到换行符时执行真正的I/O操作。Terminal一般使用行缓冲。对于行缓冲的两个限制:(1)行缓冲有固定的size,当这个固定size的行缓冲区被填满时,不管有没有遇到换行符都开始进行一次I/O操作。(2)得到输入数据时,会自动刷新输出流。这里说的得到数据,指的时数据在输入流中, 并不要求输入的数据一定被内核读取了。
  3. 不带缓冲。标准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

区别:

  1. fopen函数打开路径名为pathname的一个指定的文件。
  2. freopen函数在一个指定的流上打开一个指定的文件,如若该流已经打开,则先关闭该流。若该流已经定向,则使用freopen清除该定向。此函数一般用于将一个指定的文件打开为一个预定于的流:标准输入、标准输出或标准错误。
  3. 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

当以读和写类型打开一个文件时。具有下列限制

  1. 如果中间没有fflush、fseek、fsetpos或rewind,则在输出的后面不能直接跟随输入。
  2. 如果中间没有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不能实现为宏。这意味着以下几点。

  1. getc的参数不应当是具有副作用的表达式,因为它可能会被计算多次。
  2. 因为fgetc一定是个函数,所以可以得到其地址。这就允许将fgetc的地址作为一个参数传送给另一个函数
  3. 调用fgetc所需时间很可能比调用getc要长,因为调用函数所需的时间通常长于调用宏。

区分EOF和错误发生:

#include <stdio.h>
int ferror(FILE *fp);
int feof(FILE *fp);
//两个函数返回值:若条件为真,返回非0(真);否则,返回0(假)
void clearerr(FILE *fp);

在大多数实现中,为每个流在FILE对象中维护了两个标志:

  1. 出错标志
  2. 文件结束标志

调用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流

  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);
  • 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参数访问缓冲区地址和大小;
  • 关闭流后需要自行释放缓冲区;
  • 对流添加字节会增加缓冲区大小。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值