编程提示 I
整理自《高质量编程指南——C++/C语言(第三版)》,林锐,2007
第四章【提示4-1】:
要区分初始化和赋值的不同,前者发生在对象(变量)创建的同时,而后者是在对象创建后进行的。要区分什么是编译器的责任,什么是程序员的责任,不可错把程序员的责任推给编译器,否则结果可能出乎意料!例如全局变量的初始化、数据类型的隐式转换、类的隐含成员的初始化等都是编译器的责任,而局部变量的初始化、强制类型转换、类的非静态数据成员的初始化等都是程序员的责任。
【提示4-2】:
字节是内存编址的最小单位。因为语言必须支持对一个变元(基本类型或复合类型的变量或对象)进行取地址运算(&),而且这个地址必须是有效的内存单元的地址,所以最小的对象(包括空对象)也至少会占据1字节的内存空间。请使用sizeof()确定数据类型在不同系统上的大小。
【提示4-3】:
注意要分清void类型指针和NULL指针值之间的区别。NULL是可以赋值给任何类型指针的值0,在C语言环境中它的类型为void*,而在标准C++语言环境中由于允许从0到任何指针类型的隐式转型,因此NULL就是整数0.即:
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
一个void*类型的指针是一个合法的指针,常用于函数参数中来传递一个函数与其调用者之间约定好类型的对象地址,例如在线程函数中;而一个值等于NULL的指针虽也是一个合法的指针,但不是一个有效的指针。
【提示4-4】:
无论是C还是C++程序,都不要使用默认数据类型。一定要明确指出函数每一个形参的类型和返回值类型。
【提示4-5】:
从本质上来说,C++/C不会直接对两个类型不同的操作数进行运算,如果操作数类型不同,编译器就会试图运用隐式类型转换规则或者按照用户要求进行强制类型转换。类型转换并不是改变原来的类型和值,而是生成了新的临时变元,其类型为目标类型。
【提示4-6】:
标准C语言允许任何非void类型指针和void类型指针之间进行直接的相互转换。但在C++中,可以把任何类型的指针直接指派给void类型指针,因为void*是一种通用指针;但是反过来将void类型指针直接指派给任何非void类型指针,除非进行强制转换。因此,在C语言环境中我们就可以先把一种具体类型的指针如int*转换为void*类型,然后再把void*类型转换为double*类型,而编译器不会认为这是错误的。然而这样的做法确实存在着不易察觉的安全问题(内存扩张和截断等),这是标准C语言的一个缺陷。
【提示4-7】:
(1)不可以把基类对象直接转换为派生类对象,无论是直接赋值还是强制转换,因为这不是“自然的”;
(2)对于基本类型的强制转换一定要区分值的截断与内存截断的不同;
(3)如果你坚持要使用强制转换,必须同时确保内存访问的安全性和转换结果的安全性;
(4)如果确信需要数据类型转换,请尽量使用显式的(即强制)数据类型转换,让人们知道发生了什么事,避免让编译器静悄悄地进行隐式的数据类型转换。
【提示4-8】:
尽量避免做违反编译器类型安全原则和数据保护原则的事情,例如在有符号数和无符号数之间的转换,或者把const对象的地址指派给非const对象指针,等等。
【提示4-9】:
(1)避免使用前导“_”和“__”来定义你自己的标识符,因为语言及其实现使用它来定义一些内部名称或预定义的宏,如果你也使用它,就可能造成命名冲突;
(2)给标识符起一个有意义的名字,要能够“望文生义”。如果是变量,最好能体现出它的值的类型(例如使用类型名缩写作为前缀)。这样的标识符具有“自说明”能力,
【提示4-10】:
要区分“换行”与“回车”的语义。首先,他们的ASCII码值不同。“换行”字符一般用于文件,即把从键盘输入的“回车”字符转换为“换行”字符来保存而不是直接保存“回车”字符;“换行”还用于程序的输出控制,即输出一个“换行”字符以指示终端输出从新行开始。而“回车”是键盘功能,用于输入控制,例如代替“鼠标左击”和表示输入的结束或从新行输入,它不能输出。因此要记住:输出“换行”,输入“回车”。不过有些字符输入函数可以把键盘输入的“回车”字符自动转换为“换行”字符返回,例如getchar()。
【提示4-11】:
如果代码行中的运算符比较多,用括号确定表达式中每一个子表达式的计算顺序,避免使用默认的优先级。
【提示4-12】:
当单独对一个变量使用++、--运算符时,他们的前置版本和后置版本效果一样;只有当变量用在较复杂的表达式中时,它们的前置版本和后置版本才具有不同的效果。另外,要当心那些视觉上不易分辨的操作符,以免发生书写错误。我们经常会把“==”误写成“=”,像“||”、“&&”、“<=”、“>=”这类符号也很容易发生“丢1”失误。然而编译器却不一定能自动指出这类错误。
【提示4-13】:
不要把数学中的表达式与计算机语言支持的表达式相混淆。
例如:
if (a < b < c) // a < b < c是数学表达式而不是程序表达式
并不表示:
if ((a < b) && (b < c))
而是成了令人费解的if( (a < b) <c )。
【提示4-14】:
能够在编译时求值的程序元素是否需要分配运行时的存储空间呢?要看它是什么类型的程序元素。例如基本数据类型的字面常量、枚举常量、sizeof()、常量表达式等就不需要分配存储空间,因此也没有存储类型;但是字符串常量、const常量(尤其是ADT/UDT的const对象)都要分配运行时的存储空间,即有特定的存储类型。
【提示4-15】:
不要编写太复杂的复合表达式。
例如: i = a >= b && c < d && c + f <= g + h; // 复合表达式过于复杂
不要编写多用途的复合表达式。
例如: d = (a = b + c) + r;
该表达式既求a值又求d值。应该拆分为两个独立的语句:
a = b + c;
d = a + r;
【提示4-16】:
根据布尔类型(boolean)的语义,0为“假”,任何非0值都是“真”。可用TRUE和FALSE来表示“真”和“假”两个概念。语言实现必须通过可比的值来区分二者,比如0和非0就可以担当此任。但是TRUE的值究竟是什么并没有统一的标准,不同的语言可能采用不同的方案。具体到C++语言,标准化以前的某些实现并不支持bool这种内置类型,也没有TRUE/FALSE这两个内置常量。if语句判断其条件表达式的真假并不是通过把它的计算结果转换为布尔类型的临时变量来进行的,而是将其结果直接和0进行相等性比较,如果不等于0则表示真,否则为假。许多语言都采用了这个比较通用的做法。标准化后的某些C++实现可能对if语句的处理做了增强或者调整,因为现在bool是标准类型了。由于历史的原因,Visual C++将TRUE定义为1,而Visual Basic则将TRUE定义为-1.
【提示4-17】:
如果两个同符号浮点数之差的绝对值小于或等于某一个可接受的误差(即精度),就认为它们是相等的,否则就是不相等的。精度根据具体应用要求而定。不要直接用“==”或“!=”对两个浮点数进行比较,虽然C++/C语言支持直接对浮点数进行==和!=的比较操作,但是由于它们采用的精度往往比我们实际应用中要求的精度高,所以可能导致不符合实际需求的结果甚至错误。
【提示4-18】:
(1)switch没有自动跳出的功能,每个case字句的结尾不要忘了加上break,否则当表达式与某一个case子句匹配并执行完它的语句序列后,将接着执行下面case子句的语句序列,这就导致了多个分支重叠(除非有意让多个分支共享一段代码)。Break语句只是一个“jmp”指令,其作用就是跳到switch结构的结尾处;
(2)不要忘记最后那个default子句。即使程序真的不需要default处理,也应该保留语句default:break;,这样做并非多此一举,而是为了防止别人误以为你忘了default处理,以及处于清晰性和完整性的考虑。
【提示4-19】:
标准C++/C语言允许在for语句中声明(定义)变量,并且它的作用范围为该for语句块内。不过有些语言实现可能没有遵循这个语义,例如Visual C++就会把这样声明的变量挪到for语句块外,从而它的作用域与for语句相同,在for语句块结束后它仍然有效。这一点要引起注意。(补充一下,用Visual C++ 6.0做MFC的时候碰到过该问题,但是到了Visual C++ 200x之后就不存在了)
由于网上没有该书的电子文档,所以小C只能手录,部分内容作了说明,人人网首发。
《提示》系列文摘所列都是一些需要注意的地方,觉得对编程新手来说很有价值,编程老手也可以作为参考,人人网藏龙卧虎,出现编程高手也再正常不过了,当作浮云就行了~