第五章 标准I/O库
1. Unix系统标准I/O库是在系统调用函数基础上构造的。
2. 流和FILE对象:
♥ 流的概念:ANSI C对程序移进或移出字符的操作进行了抽象,将字节流成为“流”,当程序打开或
写入一个文件时,此时就已经使得流与文件进行了结合。
(1) 流的类型主要有:文本流,二进制流.
(2) 一个进程已经预定了三个流,即标准输入,标准输出和标准出错。在文件中,分别对应的是STDIO_FILENO,STDOUT_FILENO和STDERR_FILENO.这三种流可以通过预定义FILE指针stdio, stdout和stderr加以引用。这些均定义在<stdio.h>头文件中!
♥ FILE对象的概念:FILE是一种数据结构,用于访问一个流,不能与存储在磁盘上的文件进行混淆。
3. 错误报告函数perror: 在I/O函数中,随时存在错误的可能,标准库函数在一个外部整型变量errno
中保存错误代码之后再将这个信息传递给用户程序,并提示错误的准确原因。
void perror(char const *message)
返回值:若message非空,则perror会打印出message中的内容,后面跟一个分号和一个空格,然后打印出一条解释error当前错误代码的信息。
4. 缓存:
标准I/O库提供了三种不同的缓存类型:全缓存,行缓存和无缓存。
♥ 全缓存:对于驻留在磁盘上的文件通常是由标准I/O实施全缓存的。
♥ 行缓存:当流涉及到一个终端(标准输入或者标准输出)时,典型地使用行缓存。
行缓存存在以下几个限制:
(1) 因为标准I/O库用来收集每一行的缓存的长度是固定的,所以只要填满了缓存,那么即使还没有写一个新行符,也进行I/O操作.
(2) 任何时候只要通过标准输入输出库要求从一个不带缓存的流,或一个行缓存的流(它预先要求从内核得到数据)得到输入数据,那么就会造成刷新所有行缓存输出流. 这也正是如printf函数向标准输出输出信息时会立即在终端上输出,而不管是否行缓存是否填满。
【注意】fflush函数在全缓存和行缓存方面存在差异:对于全缓冲,fflush移位着将缓存中的内容写入到磁盘上,而在行缓存方面,fflush表示丢弃已经存在缓存中的数据。
(3) 不带缓存
(4) ANSI C要求下列缓存特征:
当且仅当标准输入和标准输出并不涉及交互作用设备时,它们才是全缓存的。
标准出错决不会是全缓存的。
♥ 改变缓存方式:
void setbuf(FILE *fp, char *buf)
int setvbuf(FILE *fp, char * buf, int mode, size_t size)
(1) setbuf可以打开或者关闭缓存机制,当参数buf设置为空时,则会关闭缓存机制。否则,参数buf必须指向一个长度为BUFSIZ(1024)的缓存(参数在<stduo.h>定义) ,为一个流自行制定缓存区可以防止I/O函数库默认为它动态分配一个未知的缓冲区。
(2) 流与缓存的关系:当用setbuf函数为某个流指定了缓存关系后,此时可以看做流是指向缓存空间的,或者可以理解为它们共享一个内存区域。
(3) setbuf函数运用经典错误与解释:
#include <stdio.h> |
【解释】源程序在利用setbuf函数将标准输出流定向于buf数组之后,将会导致一个错误。因为buf是一个内部数组,而buf得到fflush的最后时刻是main函数结束之后,但是此时buf早已释放掉了,从而导致未能输出。解决方法就是将buf数组声明为静态数组或者直接放在函数外面。
(1) 对比setbuf函数,setvbuf函数的功能则更加强大,她不仅自行设置缓存区的大小(size),而且还可以直接指定缓存的类型(mode)。
_IOFBF 全缓存
_IOLBF 行缓存
_IONBF 不带缓存
5. 打开与关闭流操作函数:#include <stdio.h>
FILE *fopen(const char *pathname, const char *type)
FILE *freopen(const char *pathname, char *type, FILE *fp)-----------重定向
FILE *fdopen(int filedes, const char *type)
♥ fopen是一个普通的打开流的函数,若成功,它的返回值为指向FILE结构的指针;否则返回一个NULL指针。
【注意】在利用fopen函数试图打开文件时,一定要检查函数的返回值,因为若出现错误,它会返回一个NULL指针,这会对后面I/O操作造成错误。常见type有:
♥ freopen函数则主要是用来实现重定向,类似于shell中的重定向功能。用来实现在一个特定的
流(fp所指)上打开一个指定的文件,该函数一般用于将一个指定的文件打开为一个预定义的流:
stdin,stdout和stderr.
#include <stdio.h> int main() { int a,b; freopen("in.txt","r",stdin); //输入重定向,输入数据将从in.txt文件中读取 freopen("out.txt","w",stdout); //输出重定向,输出数据将保存在out.txt文件中 scanf("%d %d",&a,&b); printf("%d\n",a+b); fclose(stdin);//关闭文件 fclose(stdout);//关闭文件 return 0; }----------------------------------从文件in.txt文件中读取输入a,b值,然后输出到out.txt |
#include <stdio.h> int main(void) { FILE *fp; fp=freopen("in.txt","r",stderr); freopen("error.out","w",stderr); if(NULL==fp) fprintf(stderr,"error!"); else fprintf(stderr,"True!"); fclose(fp); fclose(stderr); return 0; } |
♥ fdopen函数则是通过取得一个现存的文件描述符,来将其与流进行结合。在使用这个函数之前,一定要调用设备专用函数获得描述符。
♥ 关闭流: int fclose(FILE *fp)
对于输出流,fclose函数会在文件关闭前刷新缓存区,若成功则返回0,否则返回EOF。
6. 流I/O函数库:
♥ 当打开一个流后,可以对其采取格式化函数(读,写)或者格式化I/O函数(如printf和scanf).
♥ 按照一次操作的字符数量,可以分为:一次一个字符、一次一行字符或者指定长度的字符(fread)。
♥ 输入函数:
int getc(FILE *fp) ;
int fgetc(FILE *fp) ;
int getchar(void);
(1) getchar==getc(stdin)
(2) getc与fgetc的区别主要有:getc可被实现为宏,而fgetc只能实现为函数,具体表现在:
getc的参数应该是不具有副作用的表达式(这是宏的性质决定的); fgetc因为是一个函数,可以将其地址传向其它函数(这是函数的性质);在运行时间方面,getc要少于fgetc(这是函数调用过程决定的)
(3) 返回值问题:若成功,则返回下一个字符;若已经到达文件尾部或者出错返回EOF。
【注意】函数的返回值均为整型数据,中间经历了将无符号类型字符转换为整型数据的过程,返回整型的原因是:这样就可以返回所有可能的字符值再加上一个已发生错误或已到达文件尾端的指示值EOF,在库函数中EOF一般被定义为-1, 因此二者还可以直接进行比较判断。
不管是出错还是到达文件的尾部,返回值均为EOF,为了区分这两种情况,可以调用ferror和feof。
int ferror(FILE *fp)
int feof(FILE *fp)
返回值:若条件为真,则返回非0值,否则为0(假)
♥ 撤销字符(回流)操作函数
int ungetc(int ch,FILE *fp)
返回值:若成功,返回字符的ASCII值,否则返回EOF。
|
♥ 输入与输出函数(以行为输入单位)
char*fgets(char *buf, int n,FILE*fp) ;
char *gets(char *buf) ;
返回值:若成功则为buf,若已处文件尾端或出错则为NULL
int *fputs(const char *str,FILE *fp) ;
int puts(const char *str) ;
返回值:若成功则为非负值,若出错则为 EOF
(1) 两个函数均指明了缓存地址,二者区别是gets是从标准输入读,而fgets则是从指定的流中读。
fgets函数中指明了行缓存的长度,若所读的流中的字符数大于buffer长度-1,则将会停止读取,然后下一次调用fgets时会从下一个字符开始读取,不管怎样,在缓冲区的末尾都会有一个NULL添加到存储数据,使之成为一个完整的字符串,gets函数因为没有指明缓存的长度,所以经常出现溢出的现象而导致不安全因素。
(2) 两输出函数将一个以NULL结尾的字符串写到指定的流,Null不会被写出。
|
(3) 常见应用举例:整行整行读取文本文件:
|
♥ 二进制I/O(指定长度)函数
(1) 二进制是将数据写到文件中的高效方式,它避免了在数值转换为字符串过程中所涉及到的开销和精度损失。
(2) 二进制读写函数:
size_tfread(void *ptr, size_t size, size_t count, FILE *fp)
size_tfwrite(const void *ptr, size_t size, size_t count, FILE *fp)
其中,ptr指向一个保存数据的内存位置的指针,size是缓存区中每个元素的字节数,count是读或者写入的元素数,fp则是数据读取或者写入的流。函数的返回值为读或者写的对象数。常见用法:
☻ 读或者写一个二进制数组:
list charlist[10]={'a','s','d','e','e','e','e','e','e','\0'};
fp=fopen("/tmp/yang1.txt","w");
if(4 != fwrite(list, sizeof(char), 4, fp))
☻ 读或者写一个结构(结构体):
struct {
short count;
long total;
char name [NAMSIZE];
} item;
if(fwrite(&item, sizeof(item), 1, fp) != 1)
err_sys("fwriteerror");
☻ 不足:两个函数不能在异构系统(不在同一个系统)上进行读写操作,原因有:首先,在一个结构中,同一成员的位移量可能随编译程序和系统的不同而异;用来存储多字节整数和浮点值的二进制格式在不同的系统结构间也可能不同。
♥ 定位流函数:
longftell(FILE *fp)
返回值:若成功则为当前文件指示,若出错为–1L
intfseek(FILE *fp,long offset,int from)
返回值:若成功则为0,否则为非0
void rewind(FILE*fp)
(1) ftell函数用来返回流的当前位置,即下一个读取或者写入将要开始的位置距离文件起始位置的偏移量。对比ftell,fseek函数的功能更加强大,它可以通过指定要定位的起始位置(从文件哪个地方开始),以及相应的偏移量。fseek参数说明如下:
(2) rewind函数可以直接将一个流设置到文件的起始位置。
(3) 常见用法:ftell与fseek函数联合使用可以求出一个文件的长度。
fp=fopen("/tmp/yang.txt","r");
fseek(fp,0L, SEEK_END); -----------------------将fp定位在文件的尾部
len=ftell(fp) --------------------------------可以求出文件的长度
(4) C标准新引进的函数:
intfgetpos(FILE *fp, fpos_t *pos) ;
intfsetpos(FILE *fp, const fpos_t *pos)
fgetpos将文件位置指示器的当前值存入由pos指向的对象中。在以后调用fsetpos时,可以使用
此值将流重新定位至该位置。若成功,则返回0,否则为非0.
♥ 格式化I/O函数:
intprintf(const char *format, ...)
intfprintf(FILE *fp, const char *format, ...)
返回值:若成功则输出字符数,若出错则为负值
intsprintf(char *buf, const char *format, ...)
返回值:返回存入数组的字符数
(1) printf函数将格式化数据写到标准输出,fprintf函数则是将数据写到指定的流,sprintf将数据写入数组buf中,它会在数组的尾部自动添加NULL字节。
(2) 若把可变参数表换成args,则可以转变成:
int vprintf(const char *format, va_list arg);
int vfprintf(FILE *fp, const char *format,va_list arg) ;
返回值:若成功则为输出字符数,若输出出错则为负值
int vsprintf(char *buf, const char *format,va_list arg) ;
返回值:返回存入数组的字符数
|
|
♥ 文件描述符函数:返回与该流相关的文件描述符
int fileno(FILE *fp)
♥ 临时文件函数
char *tmpnam(char*ptr) -----------------------返回指向一个唯一路径名的指针
FILE*tmpfile(void) ------------------若成功返回文件指针,否则为NULL