C++ 的几个误区

一>    标准中没有void main() /void main(int avgc,char** avgv)

         只能定义  :

           int main( void )
           int main( int argc, char *argv[] )
二>  1   为什么 fflush(stdin) 是错的

 

首先请看以下程序:

 

                   #include <stdio.h>

 

int main( void )

{

    int i;

    for (;;) {

        fputs("Please input an integer: ", stdout);

        scanf("%d", &i);

        printf("%d/n", i);

    }

    return 0;

}

 

这个程序首先会提示用户输入一个整数,然后等待用户输入,如果用户输入的是整数,程序会输出刚才输入的整数,并且再次提示用户输入一个整数,然后等待用户输入。但是一旦用户输入的不是整数(如小数或者字母),假设 scanf 函数最后一次得到的整数是 2 ,那么程序会不停地输出“Please input an integer: 2”。这是因为 scanf("%d", &i); 只能接受整数,如果用户输入了字母,则这个字母会遗留在“输入缓冲区”中。因为缓冲中有数据,故而 scanf 函数不会等待用户输入,直接就去缓冲中读取,可是缓冲中的却是字母,这个字母再次被遗留在缓冲中,如此反复,从而导致不停地输出“Please input an integer: 2”。

 

也许有人会说:“居然这样,那么在 scanf 函数后面加上‘fflush(stdin);,把输入缓冲清空掉不就行了?”然而这是错的!CC++标准里从来没有定义过 fflush(stdin)。也许有人会说:“可是我用 fflush(stdin) 解决了这个问题,你怎么能说是错的呢?”的确,某些编译器(如VC6)支持用 fflush(stdin) 来清空输入缓冲,但是并非所有编译器都要支持这个功能(linux 下的 gcc 不支持),因为标准中根本没有定义 fflush(stdin)MSDN 文档里也清楚地写着fflush on input stream is an extension to the C standardfflush 操作输入流是对 C 标准的扩充)。当然,如果你毫不在乎程序的移植性,用 fflush(stdin) 也没什么大问题。以下是 C99 fflush 函数的定义:

 

int fflush(FILE *stream);

 

如果 stream 指向输出流或者更新流update stream),并且这个更新流 最近执行的操作不是输入,那么 fflush 函数将把这个流中任何待写数据传送至 宿主环境(host environment)写入文件。否则,它的行为是未定义的。 原文如下:

int fflush(FILE *stream);

If stream points to an output stream or an update stream in which the most recent operation was not input, the fflush function causes any unwritten data for that stream to be delivered to the host environment to be written to the file; otherwise, the behavior is undefined.

 

其中,宿主环境可以理解为操作系统或内核等。

 

    由此可知,如果 stream 指向输入流(如 stdin),那么 fflush 函数的行为是不确定的。故而使用 fflush(stdin)  是不正确的,至少是移植性不好的。

 

 

2.       清空输入缓冲区的方法

 

 虽然不可以用 fflush(stdin),但是我们可以自己写代码来清空输入缓冲区。只需要在 scanf 函数后面加上几句简单的代码就可以了。         /* C 版本 */         #include <stdio.h> 

        int main( void )         {             int i, c;               for ( ; ; )             {                 fputs("Please input an integer: ", stdout);                 scanf("%d", &i);

             if ( feof(stdin) || ferror(stdin) )                 { /* 如果用户输入文件结束标志(或文件已被读完), */                   /* 或者发生读写错误,则退出循环               */                                 /* do something */                     break;                 }                 /* 没有发生错误,清空输入流。                 */                 /* 通过 while 循环把输入流中的余留数据“吃”掉 */                 while ( (c = getchar()) != '/n' && c != EOF ) ;                 /* 使用 scanf("%*[^/n]"); 也可以清空输入流, */

               /* 不过会残留 /n 字符。                          */

               printf("%d/n", i);             }              return 0;         }         /* C++ 版本 */         #include <iostream>         #include <limits> // 为了使用numeric_limits  

     using std::cout;         using std::endl;         using std::cin;         using std::numeric_limits;         using std::streamsize;  

     int main()         {             int value;             for ( ; ; )             {                 cout << "Enter an integer: ";                 cin >> value;                 if ( cin.eof() || cin.bad() )                 { // 如果用户输入文件结束标志(或文件已被读完),                   // 或者发生读写错误,则退出循环

                 // do something                     break;                 }                 // 读到非法字符后,输入流将处于出错状态,                 // 为了继续获取输入,首先要调用 clear 函数                 // 来清除输入流的错误标记,然后才能调用                 // ignore 函数来清除输入流中的数据。                 cin.clear();                 // numeric_limits<streamsize>::max() 返回输入缓冲的大小。                 // ignore 函数在此将把输入流中的数据清空。                 // 这两个函数的具体用法请读者自行查询。                 cin.ignore( numeric_limits<streamsize>::max(), '/n' );                 cout << value << '/n';             }

         return 0;         } 三>   void* 指针 的强制转化  malloc

首先要说的是,使用 malloc 函数,请包含 stdlib.h(C++ 中是 cstdlib),而不是 malloc.h 。因为 malloc.h 从来没有在 C 或者 C++ 标准中出现过!因此并非所有编译器都有 malloc.h 这个头文件。但是所有的 C 编译器都应该有 stdlib.h 这个头文件。

    在 C++ 中,强制转换 malloc() 的返回值是必须的,否则不能通过编译。但是在 C 中,这种强制转换却是多余的,并且不利于代码维护。

    起初,C 没有 void 指针,那时 char* 被用来作为泛型指针(generic pointer),所以那时 malloc 的返回值是 char* 。因此,那时必须强制转换 malloc 的返回值。后来,ANSI C(即C89) 标准定义了void 指针作为新的泛型指针。void 指针可以不经转换,直接赋值给任何类型的指针(函数指针除外)。从此,malloc 的返回值变成了 void* ,再也不需要强制转换 malloc 的返回值了。以下程序在 VC6 编译无误通过。

        #include <stdlib.h>         int main( void )         {             double *p = malloc( sizeof *p ); /* 不推荐用 sizeof( double ) */             free(p);             return 0;         }

    当然,强制转换malloc的返回值并没有错,但画蛇添足!例如,日后你有可能把double *p改成int *p。这时,你就要把所有相关的 (double *) malloc (sizeof(double))改成(int *)malloc(sizeof(int))。如果改漏了,那么你的程序就存在 bug 。就算你有把握把所有相关的语句都改掉,但这种无聊乏味的工作你不会喜欢吧!不使用强制转换可以避免这样的问题,而且书写简便,何乐而不为呢?使用以下代码,无论以后指针改成什么类型,都不用作任何修改。

        double *p = malloc( sizeof *p );

    类似地,使用 calloc ,realloc 等返回值为 void* 的函数时,也不需要强制转换返回值。

参考资料: ISO/IEC 9899:1999 (E) Programming languages — C 7.20.3.3 The malloc function ISO/IEC 9899:1999 (E) Programming languages — C P104 (6.7.2.2)

 

四>   char c= fgetc()

    许多初学者都习惯用 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,那么返回值就是 00 00 00 FF。把这个值赋值给 c 后, c 的值变成 FF。然后 c 的值为了和 EOF 比较,会自动升级为 int 型的值,也就是 FF FF 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);          // 正确

五>  new

 

    首先澄清一下,这个误区仅对 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;         // 其它代码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值