在系统学习输入输出函数之前,首先了解一个 stdio.h 头文件。
头文件stdio.h包含了标准库函数的I/O部分的声明
流的概念:就C语言而言,所有的I/O操作只是简单的从程序中移进移出字节的事情,因此这种字节流杯称为流,程序只需关心创建正确的输出字节数据,以及正确的解释从输入读取的字节数据,特定的I/O设备的细节是隐藏的。
绝大多数流失完全缓冲的,也就是说“读取”和“写入”实际上是在一块被称为缓冲区的内存区域来回的复制数据,用于输出流的缓冲区只有当它写满的时候才会被刷新到设备和文件中,因为这样效率更高,类似,输入缓冲区当它为空时通过从设备或文件读取下一个块输入重新填满缓冲区。
系统经常使用的缓冲区策略是把标准输入和标准输出联系在一起,就是当请求输入时同时刷新输出缓冲区。这样,在用户必须进行输入之前,把提示用户进行输入的信息和以前写入到输出缓冲区的内容呈现在屏幕上。
文件
stdio.h中声明了一个FILE结构,这个FILE和存储于磁盘上的数据文件并不是同一个概念。FILE是一个数据结构,用于访问一个流,如果同时激活了几个流,每个流都会有一个FILE与它相关联。
对于每一个C程序,运行时系统必须提供至少三个流:标准输入(stdin)、标准输出(stdout)、标准错误(stderr)他们都指向一个FILE结构的指针,一般情况下stdin为键盘、stdout为终端或屏幕、stderr一般和stdout是相同的。
在C程序中执行与文件的相关的I/O操作时,标准库函数通常为我们处理了下面几工作,使得我们的任务变得非常方便。
1、程序必须为同时处于活动状态的每个文件声明一个指针变量,其类型为FILE *。指针指向FILE结构,当它处于活动状态时由流使用。
2、流通过调用fopen函数打开。调用fopen函数时需要提供两个参数,一个是文件名(包括文件路径),另一个是访问的方式。
3、根据需要对打开的文件进行读写操作。
4、最后,调用fclose函数关闭流。关闭一个流可以防止与它相关联的文件被再次访问,保证任何存储于缓冲区的数据被正确的写到文件中,并且释放FILE结构使它可以用于另外的文件。
字符I/O
最简单的处理形式就是字符I/O,字符输入是由getchar函数来完成的。原型如下:
int fgetc( FILE *stream );
int getc( FILE *stream );
int getchar( void );
把字符写入到流中使用的另一套函数来完成。原型如下:
int fputc( FILE *stream );
int putc( FILE *stream );
int putchar( FILE *stream );
在这些函数中只有 fputc和fgetc是真正的函数,其他的getc、putc、getchar、putchar都是通过#define定义的宏,关于宏宇函数的优劣点在前面已经讨论过。
撤销字符I/O
在实际的程序中我们有可能会遇到这种情况:必须从一个流中逐个读取一串数字,由于在实际读入之前,我们并不知道下一个字符是什么,因此必须连续的读取,直到读入一个非数字字符,但是有时这个读入的非数字字符是另外一个程序需要的或者程序的其他地方需要这个字符,那么我们就不能把这个字符简单的丢弃,撤销字符I/O就是用来解决这个问题的。原型如下:
int ungetc( int ch, FILE *stream );
ungetc函数吧一个先前读入的字符返回到流中,这样它可以在以后被重新读入。
未格式化的行I/O
行I/O可以有两种方式执行——未格式化和格式化的,这两种形式都用于操作字符串,区别在于未格式化的I/O只是简单的读取或写入字符串,而格式化的I/O则执行数字和其他变量的内部和外部表示形式之间的转换。
gets和puts函数家族用于执行未格式化的I/O,原型如下:
char *fgets( char *buffer, int buffer_size, FILE *stream );
char *gets( char *buffer );
int fgets( char const *buffer, FILE *stream );
int gets( char const *buffer );
fgets函数从指定的stream读取字符并把它们复制到buffer中,当它读取到一个换行符兵存储到缓冲区之后就不在读取。如果缓冲区内存储发字符数达到buffer_size - 1 个时它也停止读取。在这种情况下并不会出现数据丢失的情况,因为下一次fgets将从流的下一个字符开始读取,不管是哪种情况下,NUL字节都回被添加到缓冲区的末尾,使它成为一个字符串。并返回一个指向这个缓冲区的指针。如果在读取任何一个字符之前就已经到达文件的末尾,那么函数将返回一个NULL指针。
gets函数和puts函数功能几乎和fgets函数、fputs函数一样,之所以有它们是为了允许向后兼容,主要区别在于gets函数在读取一行时不会在缓冲区的结尾存储换行符,puts函数在写入一个字符串时会在字串写入之后再添加一个换行符。
另外gets函数还有一个缺陷,从它的参数列表页可以看出,gets函数没有指定缓冲区的大小,当一个很长的输入串读取到一个叫较小的缓冲区时多出来的字符就会写入到缓冲区的后面的内存单元,这将带来不可预料的结果。
格式化的行I/O
格式化的行I/O主要有scanf家族和printf家族函数实现,原型如下:
int fscanf( FILE *stream, char const *format, ... );
int scanf( char const *format, ... );
int sscanf( char const *string, char const *format );
int fprintf( FILE *stream, char const *format, ... );
int printf( char const *format, ... );
int sprintf( char const *buffer, char const *format );
fscanf的输入源是最为参数给出的,scanf从标准输入读取,而sscanf则从第一个参数所给出的字符串中读取字符。
当格式化字符串到达文件末尾或者读取的输入不再匹配格式字符串所指定的类型时,输入就停止。在任何一种情况下,被转换的输入之的数目作为函数的返回值返回。如果在任何输入值被转换之前文件就已到达尾部,函数就返回常量EOF。
格式码:scanf函数家族的格式码都是以一个百分号开头,后面可以是(1)、一个可选的星号。(使转换的值被丢弃而不是进行存储)(2)、一个可选的宽度。(这个技巧可以用于跳过不需要的输入字符。宽度以一个非负的整数给出)(3)、一个可选的限定符(4)、格式代码
二进制I/O
把数据写到文件效率最高的方法是使用二进制形式写入,二进制输出避免了在数值转换为字符串过程中所涉及的开销和精度损失,但二进制数据并非人眼所能阅读,所以这个技巧只有当数据被另一个程序按顺序读取时才能使用。
size_t fread( void *buffer, size_t size, size_t count, FILE *stream );
size_t fwrite( void *buffer, size_t size, size_t count, FILE *stream );
参数说明:buffer是一个指向用于保存数据的内存位置指针,size是缓冲区中每一个元素的字节数,count是读取或写入的元素数。stream是数据读取或写入的流。函数的返回至是实际读取或写入的元素数目(并非字节数)。
刷新和定位函数
fflush函数,它迫使一个输出流的缓冲区内的数据进行物理写入,不管它是不是已经写满(前面提到过,当输出缓冲区满时就进行物理写入)。它的原型如下:
int fflush( FILE *stream );
在正常情况下,数据以线性的方式写入,这意味着后面写入的数据在文件中的位置是在以前所有写入数据的后面,但是C也同时支持随机访问I/O,也就是以任意顺序访问文件的不同位置。随机访问时通过在读取或写入先前定位到文件中需要的位置来实现的。有两个函数用于执行这项操作,原型如下:
long ftell( FILE *stream );
int fseek( FILE *stream, long offset, int from );
ftell函数返回流的当前位置,也就是说,下一个读取或写入将要开始的位置距离文件起始位置的偏移量。
fseek函数允许你在一个流中定位这个操作将改变下一个读取或写入操作的位置,它的第一个参数是需要改变的流,第2、3个参数标识文件中需要定位的位置。
在使用fseek改变一个流的位置会带来三个副作用。首先,行末指示符被清除,其次,如果在fseek之前使用了ungetc函数把一个字符返回到流中,那么这个被退回的字符将会被丢弃,最后定位模式允许你从写入模式切换到读取模式。
除了上面的两个函数,还有下面的函数分别执行不同的功能。
void rewind( FILE *stream )
int fgetpos( FILE *stream, fpos_t *position );
int fsetpos( FILE *stream, fpos_t *position );
rewind函数将读/写指针设备回流的起始位置。同时清除流的错误提示标志。
fgetpos和fsetpos函数时是ftell和fseek的替换方案。
流错误函数
int feof( FILE *stream ); //若当前流处于文件尾则函数返回真
int ferror( FILE *stream ); //如果出现任何读写错误函数返回真
int clearerr( FILE *stream ); //对指定流的错误标识进行重置
临时文件
FILE *tmpfile( void )
这个函数创建了一个临时文件,当文件被关闭或程序终止时这个文件便自动删除。