第4章 类型和声明
4.3 字符类型
“由8bits表示的256个值可以解释为0~255,或者解释为-128~127,不幸的是,普通char类型选择那种解释是由具体实现决定的”[1]
所以,不要做过多的假设。如果你必须这样做,请使用显式的signed char或者unsigned char来声明变量。
4.4整数类型
“unsigned整数类型对于存储二进制bit数组的使用方式非常理想。但是,如果用unsigned而不用int仅仅因为想多一个bit来表示正整数,就不是什么好主意。”[2]
这句话轻描淡写的一笔带过,但是含义颇深。它再一次提醒我们,不要指望一种并没有看起来那么可靠的措施,来为我们提供安全保险。
举例来说,当我们声明一个如下的函数:
int func(unsigned arg);
的时候,其实我们的潜台词是:“哦,我要的参数的值都应该是大于零的。”不幸的是,事实的确如此。对于函数内部,arg肯定是大于0。那为什么危险?因为我们认为这种声明方式会给我带来某种类型安全,却恰恰相反。C++为int到unsigned提供了隐式转换,这意味着一个负整数可以悄无声息的在需要的时候变为一个正整数。假设程序由于某种异常调用了func(-1),此时在func()内部arg的值就可能是4294967295(在我的32位PC上)或者是一个其他的正整数值,函数将正常执行,甚至返回一个表示成功的值,但是其实际结果可能完全不是那么回事!程序将由此“驶上通往崩溃的快车道”。另一方面,正是由于声明时的潜台词,我们通常忽略了接口输入参数的安全检查,而交给unsigned类型这个脆弱的保护措施,况且,当我们使用了unsigned做声明的时候,我们也根本无法进行必要的安全检查,应为arg在函数内部一定为正!或者有些人依然会狡辩说,可以针对特定应用,来收紧检查条件,如月份我们可以检查if(arg>0 && arg<13)。请相信我,即使是说这话的人本身也会底气不足的,因为调用者完全可以传递一个负数,使得在函数内部的参数值落在安全范围之内。虽然概率很小,但是对于一个健壮的程序来说,这是不允许的。想想坐在屏幕前看着一些充满了magic number的调试信息,用整个下午的时间猜测每个数字是怎么来的情景,那种痛苦真是会令人崩溃。如果你不想成为这样可怜的程序员,请注意这个细节:大多数时候尽量使用signed而不是unsigned类型,尤其是作为函数接口的时候,并且做好每一次接口安全检查。终有一天你会发现,花费这点力气是值得的。
4.6 类型的大小
很多时候,清楚地知道每种类型的大小是有实际意义的。这点对于固定平台的程序员来说可能不是那么明显,但是对于那些经常更换目标平台的嵌入式程序员来说,就很实际了。况且,即便是前者,也应该意识到,任何一个成功的软件,几乎都面临着跨平台的考验。一个有远见的程序员,应该从开始就注重这些细节。
“C++对象的大小,使用char的大小的整数倍来表示的”。有些平台对char的实现并不一定用8位bit,还有些实现int也不一定是32位bit。不要对类型大小以及相互之间的关系做某种过多的假设。书上第四段列出了一些“肯定”的关系,除次之外的其他结论都是不稳固的。另外,C++也提供了专门研究类型大小、边界的库,可通过标准头文件 来调用。具体内容可参见库使用说明,这里提供一个很丑的示范程序,你可以直接拷贝到你的平台上,编译,运行,“挑战一下极限”。