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
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
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
* ignore 函数在此将把输入缓冲区中的数据清空。
* 这两个函数的具体用法请读者自行查询。 */
cin.ignore( std::numeric_limits
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* 的函数时,也不需要强制转换返回值。
评论