我们先来看看下面这个程序在VC++6.0中的运行情况(说明, 我自己已经建立好了test.txt文件夹, 并写了一些内容):
#include <stdio.h> int main() { char c; FILE *fp = fopen("test.txt", "r"); if(NULL == fp) { return 1; } while(EOF != (c = fgetc(fp))) { putchar(c); } fclose(fp); printf("\n"); return 0; }在VC++6.0中运行的结果是:
abcdefg
看起来没有问题, 那么在其他系统中运行呢? 我试了几个环境, 发现都是OK的, 但是, 上面程序是有问题的。
先从逻辑上来说吧, 在正常情况下, fgetc函数返回的范围就是char的范围, 因此正常情况下用char c;中的c去存这个返回的值, 是足够的。 但是, 当已经达到文件尾或者fgetc函数出错的时候, 就会返回一个错误值, 那怎么去存错误值呢? 显然c的范围必须必char的范围要大才可, 所以用char c;是不正确的。
从实际上来说, 我们知道, 在大多数系统中char被默认为signed char, 所谓范围是-128-127. 所以, 如果已经达到文件尾或者fgetc函数出错, 则会返回-1, 而EOF的值也通常是-1, 所以, 在上面的程序中, while循环可以顺利退出。 但是, 在有的系统中, char被默认为unsigned char, 此时, 如果fgetc函数返回-1, 一旦赋值给c后, c就是个正值了, 于是上面的while陷入了死循环。因此, 应该用int c;来存-1.
要说明的是, 在所有系统中, int被默认为signed int, 但char却比较特别, 不一定是signed char, 尽管绝大多是时候是。(这一论断在《C++编程思想》的第一卷中有介绍, 请参考第三章"C++中的C"中的叙述)
可见, 为了程序的可移植性, 应该用int c; 其实, 你看看, fgetc的原型中, 返回值的类型就是int.
“我的疑惑”:我们再来仔细思考一下, 比如在VC++6.0中, 改为int c;后就不会有问题吗? 我把char形的-1写入到文件中, 读取的也是-1, 那岂不是错判为“fgetc函数遇到了文件尾或者出错”了? 那while循环岂不是不正确地结束了? 带着这些疑问, 我们先来看看下面这个简单的程序;
#include <stdio.h> int main() { FILE *fp = fopen("test.txt", "w"); if(NULL == fp) { return 1; } char ch = -1; fputc(ch, fp); // 实际上fputc的原型要求ch为int, 不过不影响我做测试, 因为char在VC++6.0中的范围是-128-127. fclose(fp); int c; fp = fopen("test.txt", "r"); if(NULL == fp) { return 1; } c = fgetc(fp); printf("%d", c); // 是255, 而不是-1哈 fclose(fp); printf("\n"); return 0; }上述结果中, c的值是255, 而不是-1, 这就说明, 在正常情况下, fgetc从文件中读出的不可能是负值, 因为ascii值不可能是负值。 而我们知道, 一旦达到文件尾或者fgetc函数出错, 会返回-1, 可见, 正确的值和错误的值是完全区分开了的。 进而可知, 上面“我的疑惑”不再疑惑。
我们再来反思一下, char c; c = fgetc(fp);在VC++6.0中真的不存在问题吗?上菜:
#include <stdio.h> int main() { FILE *fp = fopen("test.txt", "w"); if(NULL == fp) { return 1; } fputc(128, fp); fclose(fp); char c; fp = fopen("test.txt", "r"); if(NULL == fp) { return 1; } c = fgetc(fp); printf("%d", c); // 是-128, 而不是128哈, 所以即使在VC++6.0中,char c;也是错误的 fclose(fp); printf("\n"); return 0; }
综上所述: 必须用int c; , 而类似的函数还有getc, getchar.
希望大家在以后的笔试面试中注意这点, 在实际项目中也多加注意。最后强调一句, char可以为负, 但ascii值却没有负值。