针对标准C的部分I/O函数做个记录,以下函数头文件均为:stdio.h
int fgetc( FILE *stream );
int getc( FILE *stream );
int getchar( void );
fgetc和getc的参数是需要操作的流,前提是这个流之前已经被打开,而getchar始终从标准输入流(stdin)读取。每个函数从流中读取下一字符,并把它作为函数的返回值返回。如果流中并不存在更多的字符,函数就返回常量EOF;
值得注意的是,这些函数都返回的是整型int,而不是字符型char。尽管标准字符集共128个(0~127),但是返回int型的真正原因是为了允许函数报告文件的结尾(EOF)。如果返回值是char型,那么在char型数据所能表达的256的字符中,必须有一个被指定为表示EOF。如果这个字符出现在文件内部,那么这个字符之后的内容将不会被读取,因为已经读取到了EOF标志。
让函数返回一个int型值就能解决这个问题,EOF被定义为一个整型,它的值在任何可能出现的字符范围之外。这种解决方法允许我们使用这些函数来读取二进制文件。因为在二进制文件中,所有的字符都有可能出现,文本文件也是如此。
int fputc( int ch, FILE *stream );
int fputc( int ch, FILE *stream
int putchar( int ch );
这些函数的功能是把单个字符写入流中,fputc和fputc写入的是其第二个参数所指定的流,puchar是向标准输出写入字符。
第一个参数是要打印的字符。在打印之前,函数吧这个整形参数裁剪成一个无符号字符型值,所以
putchar(‘abc’);
只打印一个字符,至于是哪一个,不同的编译器可能不同。
第二个参数是一个已打开的流。如果由于任何原因(如写入到一个已被关闭的流)导致函数失败,返回EOF。
fgetc和fputc都是真正的函数,但是getc、putc、getchar、putchar都是通过#define指令定义的宏。宏在执行时间上效率稍高,二函数在程序的长度方面更胜一筹,但是实际使用的话,不管采用何种类型,其结果都相差甚微。
行I/O可以用两种方式执行:未格式化的行I/O和格式化的行I/O。这两种形式都用于操纵字符串。区别在于未格式化的行I/O只是简单读取或写入字符,而格式化的行I/O则执行数字和其他变量的内部和外部表示形式之间的转换。
char *fgets( char *str, int numChars, FILE *stream );
char *gets( char *str );
int fputs( const char *str, FILE *stream );
int puts( char *str );
fgets从指定的stream中读取字符串并把它们复制到以str所指位置开始的一块内存。当它读取到一个换行符并存储到缓冲区之后就不再读取。如果缓冲区内存储的字符数达到numChars-1个时它也停止读取。在这种情况下,并不会出现数据丢失的情况,因为下一次调用fgets将从流中刚刚停止读取的字符的下一个字符开始读取。无论哪种情况下,str都会在最后为’\0’空出一个位置,使它成为一个字符串。
如果在函数读取任何字符之前就到了文件结尾,缓冲区就未进行修改,fgets函数返回一个NULL指针。否则,fgets返回它的第一个参数。这个返回值通常用于检查是否到达了文件尾。
传递给fputs的缓冲区必须包含一个字符串,它的字符被写入到流中。如果传参正确,这个字符串应该以’\0’结尾,所以这个函数没有一个缓冲区长度参数,因为它一直将字符写入到流中,直至写完’\0’。这个字符串是逐字写入的:如果它不包含一个换行符,就不会写入换行符。如果包含了好几个换行符(人为修改str中的字符就可以实现),所有的换行符都会被写入。因此,当fgets每次读取一整行时,fputs却既可以一次写入一行的一部分,也可以一次写入一整行,甚至可以一次写入好几行。如果写入时出现了错误,fputs返回常量值EOF,否则它将返回一个非负值,
gets和puts函数几乎和fgets与fputs相同。之所以存在它们是为了允许向后兼容。它们之间的一个主要的功能性区别在于当gets读取一行输入时,他并不在缓冲区存储结尾的换行符(不过存储’\0’,这是字符串所必须的)。当puts写入一个字符串是,他在字符串写入之后向输出再添加一个换行符。
另一个区别从gets函数的原型中可以看出,他没有指定缓冲区的长度。因此,如果一个长的输入行输入到一个短的缓冲区,多出来的字符将被写到缓冲区之外的地方,这是非常危险的(比如著名的1988年网络蠕虫病毒)。而且,可以用gets的地方也可以用fgets,但是fgets更安全,所以get渐渐淘汰了,但为了兼容,微软在vs2005提供了gets的安全版本gets_s。
char *gets_s(char *buffer, size_t sizeInCharacters);
和gets相比,多了一个输入缓冲区的长度。总之少用gets就对了。
称他们为行I/O从某种意义上来说不是很准确,因为这些函数并不仅限于单行。他们也可以在行的一部分或是多行上执行I/O操作。
int fscanf( FILE *stream, const char *format, ... );
int scanf( const char *format, ... );
int sscanf( const char *buffer, const char *format, ... );
这些函数都是从输入源读取字符串并根据format字符串给出的格式代码对他们进行转换。fscanf的输入源就是作为参数给出的流(stream),scanf从标准输入读取内容,而sscanf则从第一个参数所给出的字符串中读取字符。
当格式化字符串到达末尾或者读取的输入不再匹配格式字符串format所指定的类型时,输入就停止,在任何一种情况下被转换的输入值的数目作为函数的返回值返回。如果在任何输入值之前文件就已经到达尾部,函数就返回常量EOF。
int fprintf( FILE *stream, const char *format, ... );
int printf( const char *format, ... );
int sprintf( char *buffer, const char *format, ... );
这些函数根据格式代码和format参数的其他字符对参数列表中的值进行格式化。
使用printf,结果输出到标准输出。
使用fprintf,输出到参数指定的流(stream),前提是这个流已被打开且未关闭。
使用sprintf,把转换后的结果作为一个以’\0’结尾的字符串存储到指定buffer缓冲区,前提是buffer足够大去容纳该字符串,否则结果是未知的。
这3个函数的返回值都是实际打印或存储的字符个数。
把数据写到文件效率最高的方法是用二进制形式写入。二进制输出避免了数值转换为字符串过程中所涉及的开销和精度的丢失。但二进制数据并非人眼所能阅读。fwrite函数用于写入二进制数据,fread用于读取二进制数据。原型如下:
int fwrite( const void *buffer, size_t size, size_t count, FILE *stream );
int fread( void *buffer, size_t size, size_t num, FILE *stream );
buffer是指向一个用于保存数据的内存位置的指针,size是缓冲区每个元素的字节数,count是读取或写入的元素数,stream是数据读取或写入的流。
buffer参数被解释为一个或多个值得数组。count参数指定数组中有多少个值,所以读取或写入一个标量时,count应为1,。函数的返回值是实际读取或写入的元素(而不是字节)个数。如果输入过程中遇到了文件结尾或是输出过程中遇到了错误,这个数字可能比预期的袁术数目要小。
由于是用二进制进行输入输出,这就给了他们处理复杂结构的可能。比如有这样一个结构:
struct STU
{
char address[32];
char name[16];
double score;
int number;
char sex;
}stu[10];
我们就可以直接从文件读取或向文件输出对应数据,而不用将得到的数据进行转换成字符数组、double型、int型和char型。
int count = fread(stu, sizeof(struct STU), 10, input_stream);
/* 处理数据。。。 */
fread(stu, sizeof(struct STU), count, output_stream);
之所以将这两个函数单独列出来,是因为它们实在和上面长得太像了,导致我最初学习的时候经常弄混,但是他们并不是标准C库的函数,它们的头文件为: conio.h
int getch(void);
int putch(int ch);
返回值:读取到的字符
getch()是个不回显的函数,即当用户按下某个按键时,函数自动读取,并不需要按回车。
ch:要输出的字符
返回值:如果输出成功,函数返回该字符,否则返回EOF。
putch()和putchar()类似,向当前输出输出字符ch,然后光标自动右移一个字符位置。