C/C++ 误区四:char c = getchar();误区五:检查 new 的返回值

C/C++ 误区四:char c = getchar();

    许多初学者都习惯用 char 型变量接收 getchar、getc,fgetc 等函数的返回值,其实这么做是不对的,并且隐含着足以致命的错误。getchar 等函数的返回值类型都是 int 型,当这些函数读取出错或者读完文件后,会返回 EOF。EOF 是一个宏,标准规定它的值必须是一个int 型的负数常量。通常编译器都会把 EOF 定义为 -1。问题就出在这里,使用 char 型变量接收 getchar 等函数的返回值会导致对 EOF 的辨认出错,或者错把好的数据误认为是 EOF,或者把 EOF 误认为是好的数据。例如:

        int c;  /* 正确。应该使用 int 型变量接收 fgetc 的返回值 */
        while ( (c = fgetc(fp)) != EOF )
        {
            putchar(c);
        }

如上例所示,我们很多时候都需要先用一个变量接收 fgetc 等函数的返回值,然后再用这个变量和 EOF 比较,判断是否已经读完文件。上面这个例子是正确的,把 c 定义为 int 型保证了它能正确接收 fgetc 返回的 EOF,从而保证了这个比较的正确性。但是,如果把 c 定义为 char 型,则会导致意想不到的后果

    首先,因为 fgetc 等函数的返回值是 int 型的,当赋值给 char 型变量时,会发生降级,从而导致数据截断。例如:

                 ---------------------------------
                 | 十进制|      int     |  char |
                 |--------|--------------|-------|
                 |   10   | 00 00 00 0A |   0A |
                 |   -1   | FF FF FF FF  |   FF  |
                 |   -2   | FF FF FF FE  |   FE  |
                 ---------------------------------

在此,我们假设 int 和 char 分别是 32 位和 8 位的。由上表可得,从 int 型到char 型,损失了 3 个字节的数据。而当我们要拿 char型和 int 型比较的时候,char 型会自动升级为 int 型。char 型升级为 int 型后的值会因为它到底是 signed char 还是 unsigned char 而有所不同。不幸的是,如果我们没有使用 signed 或者 unsigned 来修饰 char,那么我们无从知晓 char 到底是指 unsigned char 还是指 signed char,因为这是由编译器决定的。不过,无论 char 是 signed 的也好,unsigned的也罢,都不能改变使用 char 型变量接收 fgetc 等函数的返回值是错误的这个事实。唯一能改变的是该错误导致的后果。前面我们说了,char 型和 int 型比较时,char会自动升级为 int,下面我们来看看 signed char 和 unsigned char 在转换成 int 后,它们的值有什么不同:

                 ---------------------------------------
                 |  char |   unsigned    |  signed    |
                 |-------|---------------|-------------|
                 |  10   |  00 00 00 0A | 00 00 00 0A |
                 |  FF   |  00 00 00 FF  | FF FF FF FF |
                 |  FE   |  00 00 00 FE  | FF FF FF FE |
                 ---------------------------------------

由上表可知,当 char 是 unsigned 的时候,其转换为 int 后的值是正数。也就是说,假如我们把 c 定义为 char 型变量,而编译器默认 char 为 unsigned char,那么以下表达式将永远成立

        (c = fgetc(fp)) !=EOF  /* c 的值永远为正数,而标准规定 EOF 为负数 */

也就是说以下循环是一个死循环

        while ( (c = fgetc(fp))!= EOF )
        {
            putchar(c);
        }

    读到这里,可能有些读者朋友会说:“那么我明确把 c 定义为 signed char 型的就没问题了吧!”很遗憾,就算把 c 定义为 signed char,仍然是错误的。假设 fgetc 等函数读到一个字节的值为 FF,那么返回值就是 0000 00 FF。把这个值赋值给 c 后, c 的值变成 FF。然后 c 的值为了和 EOF 比较,会自动升级为 int 型的值,也就是 FFFF FF FF。从而导致以下表达式不成立

        (c = fgetc(fp)) !=EOF  /* 读到值为 FF 的字符,误认为 EOF */

也就是说以下循环在没有读完文件的情况下提前退出

        while ( (c = fgetc(fp))!= EOF )
        {
            putchar(c);
        }

    综上所述,使用 char 型变量接收 fgetc 等函数的返回值是错误的,我们必须使用 int 型变量接收这些函数的返回值,然后判断接收到的值是否 EOF。只有判断发现该返回值并非 EOF,我们才可以把该值赋值给 char 型变量。

    同理,C++ 中,用 char 型变量接收 cin.get() 的返回值也是错误的。不过,把 char 型变量当作参数传递给 cin.get 则是正确的。例如:

        char c =cin.get();  //错误,理由同上

        char c;
       cin.get(c);          // 正确

C/C++ 误区五:检查 new 的返回值

来源:蚂蚁的 C/C++ 标准编程 作者:Antigloss 等级:精品
发布于2007-09-25 23:14 被读2822次 【字体:大 中 小】

 

    首先澄清一下,这个误区仅对 C++ 成立,这里不过是沿用“C/C++ 误区”这个衔头罢了。

    我们都知道,使用 malloc/calloc 等分配内存的函数时,一定要检查其返回值是否为“空指针”(亦即检查分配内存的操作是否成功),这是良好的编程习惯,也是编写可靠程序所必需的。但是,如果你简单地把这一招应用到 new 上,那可就不一定正确了。我经常看到类似这样的代码:

        int* p = new int[SIZE];
        if ( p == 0 ) // 检查 p 是否空指针
            return -1;
        // 其它代码

    其实,这里的 if ( p == 0 ) 完全是没啥意义的。C++ 里,如果 new 分配内存失败,默认是抛出异常的。所以,如果分配成功,p == 0 就绝对不会成立;而如果分配失败了,也不会执行 if ( p == 0 ),因为分配失败时,new 就会抛出异常跳过后面的代码。如果你想检查 new 是否成功,应该捕捉异常

        try {
            int* p = new int[SIZE];
            // 其它代码
        } catch ( const bad_alloc& e ) {
            return -1;
        }

    据说一些老的编译器里,new 如果分配内存失败,是不抛出异常的(大概是因为那时 C++ 还没加入异常机制),而是和 malloc 一样,返回空指针。不过我从来都没遇到过 new 返回空指针的情况。

    当然,标准 C++ 亦提供了一个方法来抑制 new 抛出异常,而返回空指针:

        int* p = new (std::nothrow) int; // 这样如果 new 失败了,就不会抛出异常,而是返回空指针
        if ( p == 0 ) // 如此这般,这个判断就有意义了
            return -1;
        // 其它代码

 

你可能感兴趣的:(C/C++ 误区四:char c = getchar();误区五:检查 new 的返回值)