2:幻数
幻数,用在这里时其含义是上下文里出现的裸字面常量(raw numeric literal),本来它们应该是具名常量(named constant)才对:
class Portfolio {
// ...
Contract *contracts_[10];
char id_[10];
};```
幻数带来的主要问题是它们没有(抽象)语义,它们只是个量罢了。一个“10”就是一个“10”,你看不出它的意思是“合同的数量”或是“标识符的长度”。这就是为什么当我们阅读和维护带有幻数的代码时,不得不一个个地去搞清楚每个光秃秃的量到底代表的是什么意思。没错,这样也能勉强度日,但带来的是不必要的精力浪费以及准确性的牺牲。
就拿上面这个设计得很差的表示公文包(`Portfolio`)的型别来说,它能够管理最多10个合同。当合同数愈来愈多的时候(10个不够用了),我们决定把合同数增加至32个(如果你对安全性和正确性很挑剔,那最好是改用STL中的`vector`组件)。我们立刻陷入了困境,因为必须一个个去检查那些用了`Portfolio`型别的源文件里出现的每一个字面常量“10”,并逐个甄别每个“10”是不是代表“最多合同数”这个意思。
实际情况可能会更糟。在一些很大的、长期的项目里,有时“最多合同数是10”这件事成了临时的军规,这个(远非合理的)知识被硬编码在某些根本没有包含`Portfolio`型别头文件的代码中:
`
for( int i = 0; i < 10; ++i )
// ...`
上面这个“10”是代表“最大合同数”的意思呢?还是“标识符的最大长度”?抑或是毫不相干的其他意思?
一堆臭味相投的字面常量要是不巧凑在了一块儿,史上最有碍观瞻的代码就这么诞生了:
if( Portfolio *p = getPortfolio() )
for( int i = 0; i < 10; ++i )
p->contracts_[i] = 0, p->id_[i] = '0';`
现在维护工程师可有事做了。他们不得不在Portfolio型别中出现的毫不相关的、但正好值相同的两个“10”之间费劲地识别出它们各自的意思并分别处理6。当这一切头疼的事有着极为简单的解决方案时,我们真的没有理由不去做:
class Portfolio {
// ...
enum { maxContracts = 10, idlen = 10 };
Contract *contracts_[maxContracts];
char id_[idlen];
};```
在其所在辖域有着明确含义的枚举常量同时还有着不占空间,也没有任何运行期成本的巨大优点。
幻数的一个不那么显而易见的坏处是它会以意想不到的方式降低它所代表的型别的精度,它们也不占有相应的存储空间7。拿字面常量40000来说,它的实际型别是平台相关的。如果int型别尺寸的内存能把它塞下,它就是int型别的。要是塞不下呢,它就成了long型别的。要是我们不想在平台移植的当口引狼入室(试想根据型别进行的函数重载解析规则在这里能把我们逼疯的情形),我们还是老老实实地自己指定型别吧,这比让编译器或平台替我们做这件事要好得远:
``
const long patienceLimit = 40000;``
另一个字面常量带来的潜在威胁来源于它们没有地址这件事。好吧,就算这不是个会天天发生的问题,但是有的时候将引用绑定到常量是有其作用的。
const long *p1 = &40000; // 错误!②
const long *p2 = &patienceLimit; // 没问题
const long &r1 = 40000; // 合法,不过常见错误44会告诉你另一些精彩故事
const long &r2 = patienceLimit; // 没问题幻数有百害而无一利。`
②译者注:字面常量无法取址,它们没有地址。
请使用枚举常量或初始化了的具名常量。
时间: 2024-09-19 01:50:03