15.8 字符I/O
当一个流被打开之后,它可以用于输入和输出。它最简单的形式是字符I/O。字符输入是由getchar函数家族执行的,它们的原型如下所示。
int fgetc( FILE *stream );
int getc( FILE *stream );
int getchar( void );
需要操作的流作为参数传递给getc和fgetc,但getchar始终从标准输入读取。每个函数从流中读取下一个字符,并把它作为函数的返回值返回。如果流中不存在更多的字符,函数就返回常量值EOF。
这些函数都用于读取字符,但它们都返回一个int型值而不是char型值。尽管表示字符的代码本身是小整数,但返回int型值的真正原因是为了允许函数报告文件的末尾(EOF)。如果返回值是char型,那么在256个字符中必须有一个被指定用于表示EOF。如果这个字符出现在文件内部,那么这个字符以后的内容将不会被读取,因为它被解释为EOF标志。
让函数返回一个int型值就能解决这个问题。EOF被定义为一个整型,它的值在任何可能出现的字符范围之外。这种解决方法允许我们使用这些函数来读取二进制文件。在二进制文件中,所有的字符都有可能出现,文本文件也是如此。
为了把单个字符写入到流中,可以使用putchar函数家族。它们的原型如下:
int fputc( int character, FILE *stream );
int putc( int character, FILE *stream );
int putchar( int character );
第1个参数是要被打印的字符。在打印之前,函数把这个整型参数裁剪为一个无符号字符型值,所以
putchar( 'abc' );
只打印一个字符(至于是哪一个,则于编译器相关)。
如果由于任何原因(如写入到一个已被关闭的流)导致函数失败,它们就返回EOF。
/*
** 字符I/O。
*/
#include <stdio.h>
#include <stdlib.h>
int main( void ){
fputc( 'abcd', stdout );
putc( 'abcd', stdout );
putchar( 'abcd' );
return EXIT_SUCCESS;
}
/* 输出:
*/
15.8.1 字符I/O宏
fgetc和fputc都是真正的函数,但getc、putc、getchar和putchar都是通过#define指令定义的宏。宏在执行时间上效率稍高,而函数在程序的程序的长度方面更胜一筹。之所以提供两种类型的方法,是为了允许用户根据程序的长度和执行速度选择正确的方法。这个区别实际上不必太看重,通过对实际程序的观察,不论采用何种类型,其结果通常相差甚微。
15.8.2 撤销字符I/O
在实际读取之前,并不知道流的下一个字符是什么。因此,偶尔所读取的字符是自己想要读取的字符的后面一个字符。例如,假定必须从一个流中逐个读入一串数字。由于在实际读入之前,无法知道下一个字符,因此必须连续读取,直到读入一个非数字字符。
但是如果不希望丢弃这个字符,那么该如何处置它呢?
ungetc函数可以解决这种类型的问题。下面是它的原型:
int ungetc( int character, FILE *stream );
ungetc把一个先前读入的字符返回到流中,这样它可以在以后被重新读入。程序15.2说明了ungetc的用法。它从标准输入读取字符并把它们转换为一个整数。如果没有ungetc,这个函数将不得不把这个多余的字符返回给调用程序,后者负责把它发送到读取下一个字符的程序部分。处理这个额外字符所涉及的特殊情况和额外逻辑显著提高了程序的复杂性。
/*
**把一串从标准输入读取的数字转换为整数。
*/
#include<stdio.h>
#include<ctype.h>
int read_int() {
int value;
int ch;
value = 0;
/*
**转换从标准输入读取的数字,当得到一个非数字字符时就停止。
*/
while( ( ch = getchar() ) != EOF && isdigit( ch ) ){
value *= 10;
value += ch - '0';
}
/*
**把非数字字符退回到流中,这样它就不会丢失。
*/
ungetc( ch, stdin );
return value;
}
程序15.2 把字符转换为整数 char_int.c
/*
**把一串从标准输入读取的数字转换为整数。
*/
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
int read_int( void );
int main( void ){
int value;
value = read_int();
printf( "value = %d\n", value );
return EXIT_SUCCESS;
}
int read_int( void ) {
int value;
int ch;
value = 0;
/*
**转换从标准输入读取的数字,当得到一个非数字字符时就停止。
*/
while( ( ch = getchar() ) != EOF && isdigit( ch ) ){
value *= 10;
value += ch - '0';
}
/*
**把非数字字符退回到流中,这样它就不会丢失。
*/
ungetc( ch, stdin );
return value;
}
/* 输出:
*/
每个流都允许至少退回一个字符。如果一个流允许退回多个字符,那么这些字符串再次被读取的顺序就以退回时的反序进行。注意,把字符退回到流中和写入到流中并不相同。与一个流相关联的外部存储并不受ungetc的影响。
警告:
“退回”字符和流的当前位置有关,所以如果用fseek、fsetpos或rewind函数改变了流的位置,所有退回的字符串都将被丢弃。