《C++覆辙录》——1.2:幻数

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

《C++覆辙录》——1.2:幻数的相关文章

《C++覆辙录》——导读

前言 C++覆辙录 本书之渊薮乃是近20年的小小挫折.大错特错.不眠之夜和在键盘的敲击中不觉而过的无数周末.里面收集了普遍的.严重的或有意思的C++常见错误,共计九十有九.其中的大多数,(实在惭愧地说)都是我个人曾经犯过的. 术语"gotcha"1有其云谲波诡的形成历史和汗牛充栋的不同定义.但在本书中,我们将它定义为C++范畴里既普遍存在又能加以防范的编码和设计问题.这些常见错误涵盖了从无关大局的语法困扰,到基础层面上的设计瑕疵,再到源自内心的离经叛道等诸方面. 大约10年前,我开始在

《C++覆辙录》——常见错误1:过分积极的注释

第1章 基础问题 C++覆辙录 说一个问题是基础的,并不就是说它不是严重的或不是普遍存在的.事实上,本章所讨论的基础问题的共同特点比起在以后章节讨论的技术复杂度而言,可能更侧重于使人警醒.这里讨论的问题,由于它们的基础性,在某种程度上可以说它们普遍存在于几乎所有的C++代码中. 常见错误1:过分积极的注释 很多注释都是画蛇添足,它们只会让源代码更难读,更难维护,并经常把维护工程师引入歧途.考虑下面的简单语句: a = b; // 将b赋值给a 这个注释难道比代码本身更能说明这个语句的意义吗?因而

《C++覆辙录》——第2章 语法问题2.1:数组定义和值初始化的语法形式混淆

第2章 语法问题 C++覆辙录C++语言的语法和词法结构博大精深.此复杂性的一部分是从C语言那里继承而来的,另一部分则是为支撑某些特定的语言特性所要求的. 本章中我们将考察一组语法相关的头疼问题.其中有些属于常见的手误,但是错误的代码仍然能够通过编译,只不过会以出人意料的方式运行罢了.另外一些则是由于一段代码的语法结构及它们的运行期行为不再互为表里.其余的部分,我们主要研究语法层面的灵活余地带来的问题:明明是一字不差的代码,不同的软件工程师能从中得出大相径庭的结论来. 2.1:数组定义和值初始化

《C++覆辙录》——1.5:对引用的认识误区

1.55:对引用的认识误区 对于引用的使用,主要存在两个常见的问题.首先,它们经常和指针搞混.其次,它们未被充分利用.好多在C++工程里使用的指针实际上只是C阵营那些老顽固的杰作,该是引用翻身的时候了. 引用并非指针.引用只是其初始化物的别名.记好了,能对引用做的唯一操作就是初始化它.一旦初始化结束,引用就是其初始化物的另一种写法罢了(凡事皆有例外,请看常见错误44).引用是没有地址的,甚至它们有可能不占任何存储: int a = 12; int &ra = a; int *ip = &r

《C++覆辙录》——1.9:使用糟糕的语言

1.9:使用糟糕的语言 当一个更大的世界入侵了C++社群原本悠然自得的乐土之时,它们带来了一些足堪天谴的语言和编码实践.本节乃是为了厘清返璞归真的C++语言所使用的正确适当.堪称典范之用语和行为. 用语 表1-1列出了最常见的用语错误,以及它们对应的正确形式. 表1-1 常见用语错误及其对应正确用语 没有什么所谓"纯虚基类".纯虚函数是有的,而包含有或是未能改写(override)此种函数的类,我们并不叫它"纯虚基类",而是叫它"抽象类". C+

《C++覆辙录》——1.3:全局变量

1.3:全局变量 很难找到任何理由去硬生生地声明什么全局变量.全局变量阻碍了代码重用,而且使代码变得更难维护.它们阻碍重用是因为任何使用了全局变量的代码就立刻与之耦合,这使得全局变量一改它们也非得跟着改,从而使任何重用都不可能了.它们使代码变得更难维护的原因是很难甄别出哪些代码用了某个特定的全局变量,因为任何代码都有访问它们的权限. 全局变量增加了模块间的耦合,因为它们往往作为幼稚的模块间消息传递机制的设施存在.就算它们能担此重任,从实践角度来说8,要从大型软件的源代码中去掉任何全局变量都几乎不

《C++覆辙录》——1.7:无视基础语言的精妙之处

1.7:无视基础语言的精妙之处 大多数C++软件工程师都自信满满地认为自己对所谓C++的"基础语言",也就是C++继承自C语言的那部分了如指掌.实际情况是,即使经验丰富的C++软件工程师,有时也会对最基础的C/C++语句和运算符的某些妙用一无所知. 逻辑运算符不能算难懂,对吗?但刚入行的C++软件工程师却总是不能让它们物尽其用.你看到下面的代码时是不是会怒从胆边生? bool r = false; if( a < b ) r = true;``` 正解如下: bool r = a

《C++覆辙录》——1.4:未能区分函数重载和形参默认值

1.4:未能区分函数重载和形参默认值 函数重载和形参默认值之间其实并无干系.不过,这两个独立的语言特征有时会被混淆,因为它们会模塑出语法上非常相像的函数用法接口.当然,看似一样的接口其背后的抽象意义却大相径庭: class C1 { public: void f1( int arg = 0 ); // ... };``` // ... C1 a; a.f1(0);a.f1();`型别C1的设计者决定给予函数f1()一个形参的默认值.这样一来,C1的使用者就有了两个选择:要么显式地给函数f1()一

《C++覆辙录》——2.9:自反初始化

2.9:自反初始化 在以下的代码里,var的值变成了多少? int var = 12; { double var = var; // ... }``` 未有定义.C++语言中,某个名字在它的初始化对象被解析到之前就进入了其辖域的话,在初始化对象引用到这个名字时,它引用到的不是别的,正是这个刚刚被声明的对象.没有几个软件工程师会写出像上面这么莫名其妙的声明代码,但也许复制.粘贴的手法会让你陷入困境: int copy = 12; // 某深藏不露的变量// ...int y = (3x+2copy