<!-- /* Font Definitions */ @font-face {font-family:宋体; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-alt:SimSun; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 135135232 16 0 262145 0;} @font-face {font-family:宋体; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-alt:SimSun; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 135135232 16 0 262145 0;} @font-face {font-family:Calibri; panose-1:2 15 5 2 2 2 4 3 2 4; mso-font-charset:0; mso-generic-font-family:swiss; mso-font-pitch:variable; mso-font-signature:-520092929 1073786111 9 0 415 0;} @font-face {font-family:"/@宋体"; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 135135232 16 0 262145 0;} /* Style Definitions */ p.MsoNormal, li.MsoNormal, div.MsoNormal {mso-style-unhide:no; mso-style-qformat:yes; mso-style-parent:""; margin:0cm; margin-bottom:.0001pt; text-align:justify; text-justify:inter-ideograph; mso-pagination:none; font-size:10.5pt; mso-bidi-font-size:11.0pt; font-family:"Calibri","sans-serif"; mso-ascii-font-family:Calibri; mso-ascii-theme-font:minor-latin; mso-fareast-font-family:宋体; mso-fareast-theme-font:minor-fareast; mso-hansi-font-family:Calibri; mso-hansi-theme-font:minor-latin; mso-bidi-font-family:"Times New Roman"; mso-bidi-theme-font:minor-bidi; mso-font-kerning:1.0pt;} .MsoChpDefault {mso-style-type:export-only; mso-default-props:yes; font-family:"Calibri","sans-serif"; mso-bidi-font-family:"Times New Roman"; mso-bidi-theme-font:minor-bidi;} /* Page Definitions */ @page {mso-page-border-surround-header:no; mso-page-border-surround-footer:no;} @page WordSection1 {size:595.3pt 841.9pt; margin:72.0pt 90.0pt 72.0pt 90.0pt; mso-header-margin:42.55pt; mso-footer-margin:49.6pt; mso-paper-source:0; layout-grid:15.6pt;} div.WordSection1 {page:WordSection1;} -->
在此论坛上发现了一些特别的问题,这些问题在其他地方并不存在,猜想是因为这里以学生为主,而学校的教材和教师与 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 < iostream.h> ,不要 #include < string.h> ,因为它们已经被 C++ 标准明确的废弃了,请改为 #include < iostream> 和 #include < cstring> 。规则就是:
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" ,编译器之所以让前者通过编译是为了兼 容以前的大量的旧代码。 [antigloss 注:这段话对 C++ 而言是正确的。但是,目前的 C99 标准似乎并没有定义 "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 函数的定义。
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/C++ 里面定义的东西! char *envp[] 是某些编译器提供的扩展功能,用于获取系统的环境变量。因为不是标准,所以并非所有编译器都支持,故而移植性差,不推荐使用。
C/C++ 误区二: fflush(stdin)
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 ); ’ ,把输入缓冲清空掉不就行了? ” 然而这是错的! C 和 C++ 的标准里从来没有定义过 fflush ( stdin )。也许有人会说: “ 可是我用 fflush ( stdin ) 解决了这个问题,你怎么能说是错的呢? ” 的确,某些编译器(如 VC6 )支持用 fflush ( stdin ) 来清空输入缓冲,但是并非所有编译器都要支持这个功能( linux 下的 gcc 就不支持),因为标准中根本没有定义 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 函数将把这个流中任何待写数据传送至宿主环境( 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;
}
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 < 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)
C/C++ 误区四: char c = getchar();
getchar 由宏实现: #define getchar() fgetc(stdin) 。 getchar 有一个 int 型的返回值 . 当程序调用 getchar 时 . 程序就等着用户按键 . 用户输入的字符被存放在键盘缓冲区中 . 直到用户按回车为止 ( 回 车字符也放在缓冲区中 ). 当用户键入回车之后 ,getchar 才开始从 stdio 流中每次读入一个字符 .getchar 函数的返回值是用户输入的第一个字 符的 ASCII 码 , 如出错返回 -1, 且将用户输入的字符回显到屏幕 . 如用户在按回车之前输入了不止一个字符 , 其他字符会保留在键盘缓存区中 , 等待后续 getchar 调用读取 . 也就是说 , 后续的 getchar 调用不会等待用户按键 , 而直接读取缓冲区中的字符 , 直到缓冲区中的字符读完为后 , 才等待用户按 键 .
getch 与 getchar 基本功能相同 , 差别是 getch 直接从键盘获取键值 , 不等待用户按 回车 , 只要用户按一个键 ,getch 就立刻返回 , getch 返回值是用户输入的 ASCII 码 , 出错返回 -1. 输入的字符不会回显在屏幕上 .getch 函数常用于程序调试中 , 在调试时 , 在关键位置显示有关 的结果以待查看 , 然后用 getch 函数暂停程序运行 , 当按任意键后程序继续运行 . 这个版本忽略了个重点, getch() 是非缓冲输入函数,就是不能用 getch ()来接受缓冲区已存在的字符,如以下 C++ 程序, int i;while ( cin>>i);cin.clear();getchar(); 运行时如果输入 1 2 3 a 时必须用 getchar ()才能在后面程序获得正常输入,即使先前已经恢复流了,此处用 getch ()是万万不行的。 另外补充个函数, getche() ,这个函数与前两上类似,功能也相近,都是输入一个字符,返回值同样是输入字符的 ASCII 码,但不同的是,此函数在输入后立即从控制台取字符,不以回车为结束 ( 带回显 )
许多初学者都习惯用 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); // 正确
C/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;
// 其它代码