标准I/O - 介绍
不仅在UNIX系统,在很多操作系统上都实现了标准I/O库
标准I/O库由ANSI C标准说明
标准I/O库处理很多细节,如缓存分配、以优化长度执行I/O等,这样使用户不必关心如何选择合适的块长度
标准I/O在系统调用函数基础上构造的,它便于用户使用
标准I/O库及其头文件stdio.h为底层I/O系统调用提供了一个通用的接口。
文件指针
FILE指针:每个被使用的文件都在内存中开辟一个区域,用来存放文件的有关信息,这些信息是保存在一个结构体类型的变量中,该结构体类型是由系统定义的,取名为FILE。
标准I/O库的所有操作都是围绕流(stream)来进行的,在标准I/O中,流用FILE *来描述。
标准I/O库是由Dennis Ritchie在1975年左右编写的
流(stream)
定义:所有的I/O操作仅是简单的从程序移进或者移出,这种字节流,就称为流。
分类:文本流/二进制流。
文本流
定义:在流中处理的数据是以字符出现。在文本流中,’\n’被转换成回车符CR和换行符LF的ASCII码0DH和0AH。而当输出时,0DH和0AH被转换成’\n’
数字2001在文本流中的表示方法为 ‘2’ ‘0’ ‘0’ ‘1’
ASCII: 50 48 48 49
二进制流
定义:流中处理的是二进制序列。若流中有字符,则用一个字节的二进制码表示;若是数字,则用对应的二进制数表示。对’\n’不进行变换
数字2001在二进制流中的表示方法为 00000111 11010001。
缓冲文件系统(高级磁盘IO)
目的:尽量减少使用read/write的调用
定义:系统自动的在内存中为每一个正在使用的文件开辟一个缓冲区,从内存向磁盘输出数据必须先送到内存缓冲区,装满缓冲区在一起送到磁盘中去。从磁盘中读数据,则一次从磁盘文件将一批数据读入到内存缓冲区中,然后再从缓冲区逐个的将数据送到程序的数据区。
分类:全缓存,行缓存,不缓存。
非缓冲文件系统 (低级磁盘IO)
定义:依靠于操作系统,通过操作系统的功能对文件进行读写,是系统级的输入输出。
标准I/O提供了三种类型的缓存
全缓存
当填满I/O缓存后才进行实际I/O操作,或者满足一定条件后,系统通过调用malloc来获得所需要的缓冲区域,默认值。
刷新(fflush):标准I/O的写操作。
当缓冲区满了,或者满足一定的条件后,就会执行刷新操作。
行缓存
当在输入和输出中遇到新行符(‘\n’)时,进行I/O操作。
当流遇到一个终端时,典型的行缓存。
不带缓存
标准I/O库不对字符进行缓冲,例如stderr。
很多的人机交互界面要求不可全缓存。
标准出错决不会是全缓存的。
使用setbuf()和setvbuf()可以更改缓存的类型
在任何时刻,可以使用fflush强制刷新一个数据流
标准I/O – fflush()
定义:
#include <stdio.h>
int fflush(FILE *fp);
说明:
可强制刷新一个流。此函数使该流所有未写的数据都被传递至内核。
标准I/O - stdin,stdout,stderr
标准I/O预定义3个流,他们可以自动地为进程所使用
在启动程序时,有三个文件流是自动打开的。它们是stdin、stdout和stderr。它们都是在stdio.h头文件里定义的,分别代表着标准输入、标准输出和标准错误输出,与底层文件描述符0、1和2相对应。
下列三个函数可用于打开一个标准I/O流:
FILE *fopen (const char *path, const char *mode);
FILE *freopen(const char *restrict pathname, const char *restrict type, FILE* restrict fp)
FILE *fdopen(int filedes, const char *type);
标准I/O - fopen() - mode参数
打开标准I/O流的mode参数:
* 当给定“b”参数时,表示以二进制方式打开文件,“+”表示文件可读写,“r”表示文件必须存在,w、a表示创建文件。
标准I/O库-fclose()
fclose()用于关闭一个已经打开的流:
int fclose(FILE *stream);
fclose()调用成功返回0,失败返回EOF,并设置errno
在该文件被关闭之前,刷新缓存中的数据。如果标准I / O库已经为该流自动分配了一个缓存,则释放此缓存。
当一个进程正常终止时(直接调用exit函数,或从main函数返回),则所有带未写缓存数据的标准I / O流都被刷新,所有打开的标准I / O流都被关闭。
在调用fclose()关闭流后对流所进行的任何操作,包括再次调用fclose(),其结果都将是未知的。
标准I/O库-读写流
调用fopen()成功打开流之后,可在三种不同类型的非格式化I/O中进行选择,对其进行读、写操作:
每次一个字符的I/O。使用fgetc()/fputc()一次读或写一个字符,如果流是带缓存的,则标准I/O函数处理所有缓存。
每次一行的I/O。使用fgets()和fputs()一次读或写一行。每行都以一个新行符终止。当调用fgets()时,应说明能处理的最大行长。
直接I/O。fread()和fwrite()函数支持这种类型的I/O。每次I/O操作读或写某种数量的对象,而每个对象具有指定的长度。这两个函数常用于从二进制文件中读或写一个结构。
标准I/O库-读写流
#include<stdio.h>
int getc( FILE *file_p );
int fgetc( FILE *file_p );
int getchar( void );
getc和fgetc的区别是:getc可以被实现为宏,fgetc不能被实现为宏。这意味着:
1 getc的参数不应当是具有副作用的表达式;
2 fgetc是一个函数,可以将fgetc的地址作为一个参数传递给另一个函数;
3 调用fgetc的时间通常长于调用getc。
为了区分出错还是到达文件尾端,必须调用ferror或feof。
#include<stdio.h>
int ferror( FILE *file_p );
int feof( FILE *file_p );
void clearerr( FILE *file_p );
#include<stdio.h>
int ungetc( int c, FILE *file_p );
ungetc将字符压送回流中,一次只送回一个字符。回送的字符不必一定是上次读到的字符。不能回送EOF。但是当到达文件尾时,仍可以回送一个字符,因为一次成功的ungetc调用会清除该流的文件结束符标志。
#include<stdio.h>
int putc( int c, FILE *file_p );
int fputc( int c, FILE *file_p );
int putchar( int c );
与输入函数一样,putc可以实现为宏,而fputc却不能。
标准I/O库-读写流-二进制I/O
#include<stdio.h>
size_t fread( void *restrict ptr, size_t size, size_t nobj, FILE *restrict file_p );
size_t fwrite( const void *restrict ptr, size_t size, size_t nobj, FILE *restrict file_p );
两个函数的返回:读或写的对象数
对于二进制数据我们更愿意一次读或写整个结构。
为了使用getc()或putc()做到这一点,必须循环读取整个结构,一次读或写一个字节。(效率低)
fputs()在遇到null字节时就停止,而在结构中可能含有null字节,所以不能使用每次一行函数实现这种要求。如果输入数据中包含有null字节或换行符,则fgets()也不能正确工作。(实现限制)
标准I/O库-定位流
定位标准I/O流的两种方式
ftell()和fseek(): 这两个函数自V7以来就存在了,但是它们都假定文件的位置可以存放在一个长整型中。
fgetpos()和fsetpos()。这两个函数是新由ANSI C引入的。它们引进了一个新的抽象数据类型fpos_t,它记录文件的位置。
需要移植到非UNIX系统上运行的应用程序应当使用fgetpos()和fsetpos()
标准I/O库-定位流
#include<stdio.h>
long ftell( FILE *file_p );
int fseek( FILE *file_p, long off_set, int whence );
这两个函数假定文件的位置可以存放在一个长整型中。
void rewind( FILE *file_p );
off_t ftello( FILE *file_p );
int fseeko( FILE *file_p, off_t off_set, int whence );
这两个函数用off_t数据类型替代了长整型。
int fgetpos( FILE *restrict file_p, fpos_t *restrict pos );
int fsetpos( FILE * file_p, const fpos_t *pos );
这两个函数是ISO C引进的。
ftell()用于取得当前的文件位置,调用成功则为当前文件位置指示,若出错则为-1L
fseek()用户设定stream流的文件位置指示,调用成功返回0,失败返回-1,并设置errno
fseek()的whence参数:SEEK_SET/ SEEK_CUR/ SEEK_END。
rewind()用于设定流的文件位置指示为文件开始,该函数调用成功无返回值。
rewind()等价于(void)fseek(stream, 0L, SEEK_SET)
fgetpos()将文件位置指示器的当前值存入由pos指向的对象中。在以后调用fsetpos()时,可以使用此值将流重新定位至该位置。
需要移植的程序,应该优先考虑fgetpos()/fsetpos()。
I/O模型比较