引言
学习C语言中你肯定遇到过这样的代码;
char ch;
while ((ch = getchar()) != EOF)
putchar(ch);
因为getchar()是从键盘上获取一个字符,所以很多人都会用char类型的变量来接受getchar的返回值,看起来没问题,但是这样写很可能导致循环永不终止,或者也可能提前结束。
函数与EOF介绍
getchar()在读取字符时,正常情况下把该字符从unsigned char转换为int类型返回,若出错或读到文件结尾则返回EOF。
EOF是什么呢? EOF就是End Of File 文件结束标志,在C语言中被宏定义为 -1(hex:0xffffffff)
char ch = getchar()错在哪里?
看到这,你可能会说:既然EOF只是值为-1的负整数,这不是在char的表示范围-128~127内吗?用char保存返回值有何不可?
-1真的一定在char的表示范围内吗?我们可以在头文件limits.h中看到如下定义:
/* Minimum and maximum values a `signed char' can hold. */
#define SCHAR_MIN (-128)
#define SCHAR_MAX 127
/* Maximum value an `unsigned char' can hold. (Minimum is 0.) */
#define UCHAR_MAX 255
/* Minimum and maximum values a `char' can hold. */
#ifdef __CHAR_UNSIGNED__
#define CHAR_MIN 0
#define CHAR_MAX UCHAR_MAX
#else
#define CHAR_MIN SCHAR_MIN
#define CHAR_MAX SCHAR_MAX
#endif
由此可见,在头文件内并没有规定char是有符号的,所以char类型是否有无符号是由编译器决定的。
当char是无符号的时候,-1就不在char的表示范围内,这时使用char保存返回值也就有问题了。实际上即使你所用的编译器默认定义char是有符号的,上面编写的代码也会带来意想不到的结果,下面我们来分情况讨论。
1. char默认是无符号的
这种情况会导致循环无法终止。假定我们遇到错误/读到文件末尾,getchar函数返回EOF,这时由于ch是char类型,只有一个字节,而EOF是int类型,有四个字节却要保存在一个字节中,EOF的值会被截断,ch也就等于0xff,又因为ch要与EOF比较,ch就要整形提升成为0x000000ff(因为ch是无符号的,也就是0扩展),永远不可能等于EOF,循环也就永远不会终止。
2. char默认是有符号的
这种情况可能导致循环提前终止。同样假定我们遇到错误/读到文件末尾,getchar函数返回EOF,前面都一样,但是在与EOF比较的时候,由于ch是有符号的,其扩展得0xffffffff,等于EOF。因此循环是可以终止的,但是如果我们在遇到错误/读到文件末尾之前读到了一个值为0xff的字节,循环就会同碰到EOF一样终止。
正确的代码
int ch;
while ((ch = getchar()) != EOF)
putchar(ch);
这种情况就不会有问题。即使我们在遇到错误/读到文件末尾之前读到了一个值为0xff的字节,由于ch是int类型,而getchar是将读到的字符由unsigned char转为int作返回值,则该字节的值经符号扩展再赋值给ch,ch就等于0x000000ff,不会等于EOF,也就不会提前终止了。
而且,当我们查看getchar的介绍时,我们会发现getchar()定义的返回值也是int类型的
int getchar(void);
综上,这就是为什么要用int类型而不是char类型来保存C语言字符读取函数的返回值。