正文

C/C++ 常见误区 2006-02-07 02:54:00

【评论】 【打印】 【字体: 】 本文链接:http://blog.pfan.cn/love0you0forever/10005.html

分享到:

C/C++ 常见误区

    在此论坛上发现了一些特别的问题,这些问题在其他地方并不存在,猜想是因为这里以学生为主,而

学校的教材和教师与IT发展脱节严重。

1. C++虽然主要是以C的基础发展起来的一门新语言,但她不是C的替代品,不是C的升级,C++和C是兄弟

关系。没有谁比谁先进的说法,更重要的一点是C和C++各自的标准委员会是独立的,最新的C++标准是

C++98,最新的C标准是C99。因此也没有先学C再说C++的说法,也不再(注意这个"不再")有C++语法是C

语法的超集的说法。

2. C++/CLI 和 C# 是微软的,它们与C和C++没有任何关系,虽然部分语法相似。但哪两种语言不相似呢

?都是abc这26个字母。

3. 不要使用TC/TC++/BC/CB等古老的编译器来学习C/C++,因为它们太古老了,不支持新的C/C++标准。不

要使用CBX/VC++6.0/VC2005等对C/C++标准支持不好的编译器,虽然这些编译器适合工作,但不适合学习

,因为它们中的语法陷阱很多。记住唯一适合学习的编译器是gcc/mingw。[antigloss注:Dev-C++ 使用

的编译器就是gcc & g++]

4. 不要用""代替<>来包含系统头文件,虽然有些编译器允许你这样做,但它不符合C/C++标准。
错误的示例:#include "stdio.h",#include "iostream"。[antigloss注:<> 用于包含标准头文件和系

统头文件,"" 用于包含自定义头文件。标准似乎没有明确规定不准用 "" 包含标准头文件和系统头文件

。使用 "" 包含标准头文件或者系统头文件只能说是一种不良风格。]

5. 不要将main函数的返回类型定义为void,虽然有些编译器允许你这样做,但它不符合C/C++标准。不要

将函数的int返回类型省略不写,在C++中要求编译器至少给一个警告。错误的示例:void main() {},

main() {} [antigloss注:C99和C++98都要求编译器对省略int至少发出一个警告]

6. 不要把VC++中的 #include "stdafx.h" 贴出来,它是预编译头文件。如同上菜时不要把厨师也放到托

盘中。

7. [C++]不要#include ,不要#include ,因为它们已经被C++标准明确的废弃

了,请改为 #include 和 #include 。规则就是:
    a. 如果这个头文件是旧C++特有的,那么去掉.h后缀,并放入std名字空间,
        比如 iostream.h 变为 iostream。
    b. 如果这个头文件是C也有的,那么去掉.h后缀,增加一个c前缀,比如 string.h
        变为 cstring;stdio.h 变为 cstdio, 等等。
BTW:不要把string、cstring、string.h三个头文件搞混淆
BTW:windows.h不是C/C++的标准文件,因此它的命名C/C++不管。

8. 不要再写 char* p = "XXX" 这种语句,要写成 const char* p = "XXX",编译器之所以让前者通过编

译是为了兼容以前的大量的旧代码。
BTW:const TYPE* p 和 TYPE const* p 是一样的,风格不同而已。
BTW:C语言中也有const关键字。

9. 不要在同一条语句中包含一个变量的多个++/--,因为它们的解析在C/C++标准中没有规定,完全取决

于编译器的个人行为。

10. C/C++ 是平台无关性语言,因此系统相关的 process/GUI 等不在标准 C/C++ 库中。比如

graphics.h 和 windows.h 等是由某个编译器提供的,而不是由C/C++ 提供的。

11. C/C++只是语言,而且是平台无关性语言。论坛上有部分人甚至认为C就是dos,C++就是windows,那

么请问linux是什么?

12.[C++]面向对象曾经是设计C with class(C++的前身)的主要目的,但C++不是,C++是一个多典范语

言。主要支持过程调用、基于对象、面向对象、泛式编程这四种编程典范。当然还支持functional,

generative,metaprogramming等典范。

13. 语法学家不是文学家,所以当你学会了一门计算机语言时,你还需要学习数据机构和算法,还需要掌

握工具和平台API的用法。

14. C/C++ 是通用语言,因此语法很复杂,你应当裁减成适合你自己的语法集合,比如裁减成 better C

和 ADT。

15. C/C++是通用语言,因此只含通用的库,你应该丰富自己需要的库,比如汽车工业协会有自己的C/C++

函数/类/模板库。

 

C/C++ 误区一:void main()


        很多人甚至市面上的一些书籍,都使用了void main( ) ,其实这是错误的。C/C++ 中从来没有

定义过void main( ) 。C++ 之父 Bjarne Stroustrup 在他的主页上的 FAQ 中明确地写着 The

definition void main( ) { /* ... */ } is not and never has been C++, nor has it even been C.

( void main( ) 从来就不存在于 C++ 或者 C )。下面我分别说一下 C 和 C++ 标准中对 main 函数的

定义。
     “The C programming Language(《C 程序设计语言》)用的就是 main( )。”
--- 这是因为第一版的C语言只有一种类型,那就是int,没有char,没有long,没有float,…………
既然只有一种类型,那么就可以不写,后来的改进版为了兼容以前的代码于是规定:不明确标明返回值的

,默认返回值为int,也就是说 main()等同于int main(),而不是等同于void main()。
在C99中,标准要求编译器至少给 main() 这种用法来个警告。
 

1. C

        在 C89 中,main( ) 是可以接受的。Brian W. Kernighan 和 Dennis M. Ritchie 的经典巨著

The C programming Language 2e(《C 程序设计语言第二版》)用的就是 main( )。不过在最新的 C99

标准中,只有以下两种定义方式是正确的:

           int main( void )

           int main( int argc, char *argv[] )

(参考资料:ISO/IEC 9899:1999 (E) Programming languages — C 5.1.2.2.1 Program startup)

        当然,我们也可以做一点小小的改动。例如:char *argv[] 可以写成 char **argv;argv 和

argc 可以改成别的变量名(如 intval 和 charval),不过一定要符合变量的命名规则。

        如果不需要从命令行中获取参数,请用int main(void) ;否则请用int main( int argc, char

*argv[] ) 。

        main 函数的返回值类型必须是 int ,这样返回值才能传递给程序的激活者(如操作系统)。

        如果 main 函数的最后没有写 return 语句的话,C99 规定编译器要自动在生成的目标文件中(

如 exe 文件)加入return 0; ,表示程序正常退出。不过,我还是建议你最好在main函数的最后加上

return 语句,虽然没有这个必要,但这是一个好的习惯。注意,vc6不会在目标文件中加入return 0; ,

大概是因为 vc6 是 98 年的产品,所以才不支持这个特性。现在明白我为什么建议你最好加上 return

语句了吧!不过,gcc3.2(Linux 下的 C 编译器)会在生成的目标文件中加入 return 0; 。

 

2. C++

        C++98 中定义了如下两种 main 函数的定义方式:

                  int main( )

                  int main( int argc, char *argv[] )

(参考资料:ISO/IEC 14882(1998-9-01)Programming languages — C++ 3.6 Start and termination)

        int main( ) 等同于 C99 中的 int main( void ) ;int main( int argc, char *argv[] ) 的

用法也和 C99 中定义的一样。同样,main 函数的返回值类型也必须是int。如果main函数的末尾没写

return语句,C++98 规定编译器要自动在生成的目标文件中加入 return 0; 。同样,vc6 也不支持这个

特性,但是 g++3.2(Linux 下的 C++ 编译器)支持。

 

3. 关于 void main

        在 C 和 C++ 中,不接收任何参数也不返回任何信息的函数原型为“void foo(void);”。可能

正是因为这个,所以很多人都误认为如果不需要程序返回值时可以把main函数定义成void main(void) 。

然而这是错误的!main 函数的返回值应该定义为 int 类型,C 和 C++ 标准中都是这样规定的。虽然在

一些编译器中,void main 可以通过编译(如 vc6),但并非所有编译器都支持 void main ,因为标准

中从来没有定义过 void main 。g++3.2 中如果 main 函数的返回值不是 int 类型,就根本通不过编译

。而 gcc3.2 则会发出警告。所以,如果你想你的程序拥有很好的可移植性,请一定要用 int main 。

 

4. 返回值的作用

        main 函数的返回值用于说明程序的退出状态。如果返回 0,则代表程序正常退出,否则代表程

序异常退出。下面我们在 winxp 环境下做一个小实验。首先编译下面的程序:

           int main( void )

           {

                  return 0;

           }

然后打开附件里的“命令提示符”,在命令行里运行刚才编译好的可执行文件,然后输入“echo %

ERRORLEVEL%”,回车,就可以看到程序的返回值为 0 。假设刚才编译好的文件是 a.exe ,如果输入“a

&& dir”,则会列出当前目录下的文件夹和文件。但是如果改成“return -1”,或者别的非 0 值,重新

编译后输入“a && dir”,则 dir 不会执行。因为 && 的含义是:如果 && 前面的程序正常退出,则继

续执行 && 后面的程序,否则不执行。也就是说,利用程序的返回值,我们可以控制要不要执行下一个程

序。这就是 int main 的好处。如果你有兴趣,也可以把 main 函数的返回值类型改成非 int 类型(如

float),重新编译后执行“a && dir”,看看会出现什么情况,想想为什么会出现那样的情况。顺便提

一下,如果输入 a || dir 的话,则表示如果 a 异常退出,则执行 dir 。

 

5. 那么 int main( int argc, char *argv[], char *envp[] ) 呢?

    这当然也不是标准 C 里面定义的东西!char *envp[] 是某些编译器提供的扩展功能,用于获取系统

的环境变量。因为不是标准,所以并非所有编译器都支持,故而移植性差,不推荐使用。
 
C/C++ 误区二:fflush(stdin)


1.       为什么 fflush(stdin) 是错的

首先请看以下程序:

                   #include

 

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);’,把输入缓冲清空掉不就

行了?”然而这是错的!C和C++的标准里从来没有定义过 fflush(stdin)。也许有人会说:“可是我用

fflush(stdin) 解决了这个问题,你怎么能说是错的呢?”的确,某些编译器(如VC6)支持用 fflush

(stdin) 来清空输入缓冲,但是并非所有编译器都要支持这个功能(gcc3.2不支持),因为标准中根本没

有定义 fflush(stdin)。MSDN 文档里也清楚地写着fflush on input stream is an extension to the C

standard(fflush 操作输入流是对 C 标准的扩充)。当然,如果你毫不在乎程序的移植性,用
fflush(stdin) 也没什么大问题。以下是 C99 对 fflush 函数的定义:

int fflush(FILE *stream);

如果stream指向输出流或者更新流(update stream),并且这个更新流
最近执行的操作不是输入,那么fflush函数将把任何未被写入的数据写入stream
指向的文件(如标准输出文件stdout)。否则,fflush函数的行为是不确定的。
fflush(NULL)清空所有输出流和上面提到的更新流。如果发生写错误,fflush
函数会给那些流打上错误标记,并且返回EOF,否则返回0。

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

 

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

 虽然不可以用 fflush(stdin),但是我们可以自己写代码来清空输入缓冲区。只需要在 scanf 函数后面

加上几句简单的代码就可以了。

              /* C 版本 */

#include

 

int main( void )

{

    int i, c;

    for (;;) {

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

        if ( scanf("%d", &i) != EOF ) { /* 如果用户输入的不是 EOF */

            /* while循环会把输入缓冲中的残留字符清空 */

            /* 读者可以根据需要把它改成宏或者内联函数 */

            /* 注:C99中也定义了内联函数,gcc3.2支持 */

            while ( (c=getchar()) != '\n' && c != EOF ) {

                  ;

            } /* end of while */
        }

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

    }

    return 0;

}

 

/* C++ 版本 */

#include

#include // 为了使用numeric_limits

 

using std::cout;

using std::endl;

using std::cin;

 

int main( )

{

       int value;

       for (;;) {

              cout << "Enter an integer: ";

              cin >> value;

             /* 读到非法字符后,输入流将处于出错状态,

               * 为了继续获取输入,首先要调用clear函数

               * 来清除输入流的错误标记,然后才能调用

               * ignore函数来清除输入缓冲区中的数据。 */          

              cin.clear( );

              /* numeric_limits::max( ) 返回缓冲区的大小。

               * ignore 函数在此将把输入缓冲区中的数据清空。

       * 这两个函数的具体用法请读者自行查询。 */

              cin.ignore( std::numeric_limits::max( ), '\n' );

              cout << value << '\n';

       }

       return 0;

}

 
C/C++ 误区三:强制转换 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
        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* 的函数时,也不需要强制转换返回值。
 

阅读(4046) | 评论(0)


版权声明:编程爱好者网站为此博客服务提供商,如本文牵涉到版权问题,编程爱好者网站不承担相关责任,如有版权问题请直接与本文作者联系解决。谢谢!

评论

暂无评论
您需要登录后才能评论,请 登录 或者 注册