本章介绍的标准IO库,不仅是UNIX,多其他操作系统都实现了标准io库,比如windows,由ISO C标准说明
缓冲的重要性
流和FILE对象
-
标准IO库的操作是围绕流(stream)进行的。
-
标准IO文件流可用于单字节和多字节(宽)字符集,当一个流最初被创建时,并没有定向
- 若在未定向的流上使用多字节的IO函数,则该流的定向设置为宽定向的
- 若在未定向的流上使用单字节的IO函数,则该流的定向设置为字节定向的。
-
只有两个函数可以改变流的定向,freopen清除一个流定向,
fwide设置流的定向
#include <stdio.h>
#include <wchar.h>
int fwide(FILE *fp,int mode);//若流时宽定向的,返回正值,若流是字节定向的返回负值,未定向的返回0
-
mode为负,fwide试图使指定的流是字节定向的
-
mode为正,fwide将试图使指定的流是宽定向的
-
mode参数值为0,fwide将不试图设置流的定向,但返回标识该流定向的值
-
不改变已定向的流,fwide不存在出错返回,
唯一检查办法是在调用fwide前先清除errno,从fwide返回时检查errno值,APUE只涉及字节定向流
-
fp为打开的流返回的指向FILE结构的指针,FILE结构对象包含了IO库为管理该流需要的所有信息,包括IO文件描述符、指向用于该流缓冲区的指针、缓冲区长度、当前在缓冲区的字符数以及出错标志等
-
应用程序没必要检验FILE对象。引用一个流,需要将FILE指针作为参数传递给每个IO函数,我们称指向FILE对象的指针为文件指针
标准输入、标准输出和标准错误
-
预定义的三个流分别是:标准输入、标准输出和标准错误分别对应,stdin、stdout和stderr,这三个预定义的文件指针引用的文件和前文的文件描述符的标准输入、标准输出和标准错误引用的文件一样
-
预定义的文件描述符,STDIN_FILENO、STDOUT_FILENO与STDERR_FILENO
缓冲
缓冲是尽可能减少使用read和write的调用次数,即减少面向内核的系统调用。
标准IO即本章的标准IO库,实际IO又称文件IO不存在缓冲,每次都存在系统调用类似的由read、write
-
全缓冲。在填满标准IO缓冲区后才进行实际IO操作。获得标准IO缓冲区常用malloc函数获得,
笔者是这样理解的缓冲区是运存即RAM,磁盘是内存即ROM,cpu对RAM操作要比ROM快的多,因此先将数据写入缓冲区即RAM,缓冲区满了之后再写入磁盘,这样就减少了实际IO的调用减少了系统调用次数,速度就变快了
冲洗(flush)
:标准IO缓冲区的写操作。缓冲区可由标准IO例程自动的冲洗,缓冲区满时、或者可以调用函数fflush冲洗一个流。对于标准IO库,flush意味着把缓冲区内容写入磁盘。再终端驱动意味着丢弃缓冲区中的数据
-
行缓冲。在输入输出(不一定是准输入流,标准输入出流)中遇到换行符时,标准IO库执行IO操作。这允许我们一次输出一个字符,但只有写一行后才进行实际IO操作,当流涉及一个终端时,通常使用行缓冲。
-
不带缓冲。标准IO不对字符进行缓冲存储。
- 标准错误流stderr通常是不带缓冲的
ISO C要求的缓冲特征
- 当且仅当标准输入和标准输出并不指向交互式设备时,他们才是全缓冲
- 标准错误不会是全缓冲的
- 一般标准错误是不带缓冲
- 一般,若是指向中断设备的流,则是行缓冲;否则是全缓冲的
使用以下函数更改缓冲类型,应用于已经打开的流
#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
- 参数buf必须指向一个长度为BUFSIZ的缓冲区,BUFSIZ常量定义在stdio.h中,其中在Ubuntu20.04中为8192,设置完后一般为全缓冲,但该流与一个终端设备有关的话可能设置为行缓冲的。
- 关闭缓冲只需buf=null即可
-
对于setvbuf,可以更精确说明所需缓冲类型,mode参数如下
- _IOFBF 全缓冲
- _IOLBF 行缓冲
- _IONBF 不带缓冲
- buf和size可以指定缓冲区长度
- buf=null,则为该流适当分配缓冲区长度
强制冲洗一个流
#include <stdio.h>
int fflush(FLIE *fp); //成功0,错误EOF(-1)
打开流
下列3个函数打开一个便准IO流
#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);
-
fopen函数打开路径名pathname的一个指定的文件
-
freopen函数在一个**
指定的流上打开一个指定的文件,如若该流已经打开,应当先关闭该流。若该流已经定向,使用freopen清除定向。此函数一般用于将一个指定的文件打开一个预定的流:标准输出,标准输入,标准错误
** -
fdopen函数取一个已有的文件描述,并使一个标准的IO流与该描述符相结合。
此函数常用于创建管道和网络通信通道函数返回的描述符,因为这类特殊文件不能用标准IO函数fopen打开,所以用fdopen使一个标准的IO流与该描述符相结合
-
type参数如下表
上表带字符b与不带字符b的区别
- 字符b是标准IO系统用来区别文本文件与二进制文件,因为UNIX内核不对这种文件进行区分,所以UNIX有没有字符b是一样的
fdopen
- 参数type对于fdopen来说,并不截断该文件
- 对于fdopen,标准io的追加写不能用于创建该文件,以为该文件一定存在
多个进程用标准io追加写打开同一个文件后,每次写数据都会准确写道文件尾端,因为APPEND是个原子操作
除非引用流是终端设备,否则按系统默认流被打开时是全缓冲的,若流引用终端设备,则该流是行缓冲的。
#include <stdio.h>
int fclose(FILe *fp);//成功返回0,错误-1
-
关闭一个打开流
-
在文件关闭前,冲洗缓冲中的数据,缓冲区中任何输入数据被丢弃,如果标准io已经为该流自动分配一个缓冲区,则释放此缓冲区
-
当一个进程正常终止时,则所有带未写缓冲的数据的标准IO都会被冲洗,所有打开的标准IO流都被关闭
读和写流
每次一个字符
#include <stdio.h>
int getc(FILE *fp);
int fgetc(FILE *fp);
int getchar(void); //成功返回下一个字符,出错或尾端EOF
- getchar等于getc(stdin)
- getc可被实现为宏,fgetc不行
- getc的参数不应当是具有副作用的表达式,因为可能被计算多次
- fgetc一定是一个函数,所以fgetc的地址作为一个参数传递给另一个函数
- 调用fgetc所需的时间可能要比getc长,因为调用函数比调用宏时间长
为了区分出错还是到达文件尾端,可以用以下函数测试
#include <stdio.h>
int ferror(FILE *fp);
int feof(FILE *fp);//满足返回非0,否则为0
void clearerr(FILE *fp);//清除下文所说的两个标志
FILE对象维护了两个标志
出错标志,文件结束标志
-
put
每次一行IO
每次输入一行
#include <stdio.h>
char *fgets(char *restrict buf,int n,FILE *restrict fp);
char *gets(char *buf);//成功返回buf,文件尾端或出错返回NULL
-
gets从标准输入读,fgets从指定流读
-
对于fget是,必须指定缓冲长度为n,此函数一直读到下一个换行符为止,但是不能超过n-1个字符,读入字符被送入缓冲区。该缓冲区于null字节结尾。
如果改行包括最后一个换行符的字节数超过n-1,则fgets只返回一个不完整的行,但是缓冲区总是以字节null结尾,对fgets的下一次调用回继续改行
puts于fputs
标准IO的效率
二进制IO
执行二进制IO操作
#include <stdio.h>
size_t fread(void *restrict ptr,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);//读或写对象数
- ptr要读或写内容指针,比如数组
- size每个元素类型的大小
- nobj都少个元素
- 要读或写的流对象
定位流
定位标准IO
#include <stdio.h>
long ftell(FILE *fp);//读取位置//成功返回当前文件位置指示,出错返回-1L
int fseek(FILE *fp,long offset,int whence);//设置位置//成功返回0,出错返回-1
void rewind(FILE *fp);//设置位置到文件起始位置
#include <stdio.h>
long ftello(FILE *fp);//读取位置//成功返回当前文件位置指示,出错返回off_t-1
int fseeko(FILE *fp,off_t offset,int whence);//设置位置//成功返回0,出错返回-1
#include <stdio.h>
long fgetpos(FILE *restrict fp,fpos_t *restrict pos);//读取位置
int fsetpos(FILE *fp,const fpos_t *pos);//设置位置//成功返回0,出错返回非0
格式化IO
格式化输出
#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写入指定文件描述符
- sprintf将格式化数据写入数组buf
- sprintf在该数组的尾端自动加有一个null字节,该字符不包含在返回值内
- sprintf函数可能会造成由buf指向的缓冲区溢出,要保证缓冲区buf足够大
- snprintf的缓冲区长度是一个显示参数n,超出部分会被丢弃,若足够大,于sprintf相同。
format具体格式如下
-
%[flags][fldwidth][prescision][lenmodifier]convtype
-
flags如下,
注意最后一项
-
fldwidth说明最小字段宽度。转换后参数字符小于宽度,则多余字符位置用空格填充,字段宽度是一个非负十进制数或者是一个*
-
precision说明整型转换后最少输出数字位数、浮点数转换后的最少位数、字符串转换后的最大字节数。精度是一个(. ), 其后可选的非负十进制数或一个星号*
-
宽字符和精度字段都可以为*
-
lenmodifier说明参数长度
-
convtype不是可选的。
-
格式化输入,scanf、fsanf、sscanf
#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,...);//成功返回输入项数,错误或者尾端EOF
- scanf用于标准输入
format具体格式如下
-
%[*][fldwidth][m][lenmodifier]convtype
- m是复制分佩符
实现细节
获取流关联的文件描述符
#include <stdio.h>
int flieno(FILE *fp);
临时文件
内存文件
内存流
标准IO替代软件
参考
《APUE》