条款二:尽量以const,enum,inline替换#define
例子:
1 | #define TEST 1.653 |
记号名称TEST也许从未被编译器看见;也许在编译器开始处理源码 之前它就被预处理器移走了。于是记号名称TEST 有可能没进入记号表 (symbol table)内。于是当你运用此常量但获得一个编译错误信息时,可能会带来困惑,因为这个错误信息也许会提到1.653 而不是TEST。 如果 TEST被定义在一个非你所写的头文件内,你肯定对1.653 以及它来自何处毫无概念,于是你将因为追踪它而浪费时间。这个问题也可能出现在记号式调试器(symbolic debugger)中,原因相同:你所使用的名称可能并未进入记号表(symboltable)。
解决之道是以一个常量替换上述的宏( #define) ;
1 | const double TEST = 1.653 |
作为一个语言常量,TEST 肯定会被编译器看到,当然就会进入记号表内。此外对浮点常量(floating point constant,就像本例)而言,使用常量可能比使用#define导致较小量的码,因为预处理器“盲目地将宏名称TEST替换为1.653”可能导致目标码(object code)出现多份1.653,若改用常量TEST绝不会出现相同情况。
以常量替换#define有两种特殊情况
1.定义常量指针
由于常量定义通常被放在头文件中(以便被不同的源码含入),因此有必要将指针(而不是指针所指之物)声明为const。例如若在头文件内定义一个常量的(不变的)char *字符串,必须写const两次
1 | const char * const authorName = "Scott Meyers" ; |
2.class专属常量
为了将常量的作用域限制于class内,必须让它成为class的一个成员;而为确保此常量至多只有一份实体,必须让他成为一个static成员:
1 2 3 4 5 6 | class GamePlayer{ private : static const int NumTurns = 5 //常量声明式 int scores[NumTurns]; //使用该常量 ... }; |
然而你所看到的是NumTurns的声明式而非定义式。通常C++要求你对你所使用的任何东西提供一个定义式,但如果它是个class专属常量又是static 且为整数类型(integral type,例如ints, chars, bools),则需特殊处理。只要不取它们的地址,你可以声明并使用它们而无须提供定义式。但如果你取某个class专属常量的地址,或纵使你不取其地址而你的编译器却(不正确地)坚持要看到一个定义式,就必须另外提供定义式如下:
1 | const int GamePlayer::NumTurns; //NumTurns的定义 |
需要把这个式子放进一个实现文件而非头文件。由于class常量已在声明时获得初值,因此定义时不可以再设初值。
#define并不重视作用域,所以我们无法利用#define创建一个class专属常量,也不能提供任何的封装性。
旧式编译器也许不支持在class内static成员在其声明式上获得初值。此外所谓的“in-class初值设定”也只允许对证书常量进行。如果编译器不支持上述语法,可以将初值放在定义式:
1 2 3 4 5 6 | class CostEstimate{ private : static const double FudgeFactor; //static class常量声明 ... //位于头文件内 }; const double CostEstimate::FudgeFactor = 1.35; //static class常量定义 位于实现文件内 |
唯一例外是在class编译期间需要一个class常量值,例如在上述的GamePlayer::scores的数组声明式中:编译器坚持必须在编译期间知道数组的大小。当不允许“static整数型class常量”完成“in class 初值设定”,可改用所谓的“the enum hack”补偿做法。
The enum hack
理论基础:一个属于枚举类型(enumerated type)的数值可权充int使用。于是GamePlayer可定义如下:
1 class GamePlayer{ 2 private: 3 enum{NumTurns = 5}; //"the enum hack"—令NumTurns 4 //成为5的一个记号名称 5 int scores[NumTurns]; //这就没问题了。 6 ... 7 };
enum hack 的行为某方面比较像#define而不像const,有时候这正是你想要的。
取一个const的地址是合法的,但取一个enum地址就不合法,而取一个#define的地址通常也不合法。
Enum 和 #define 一样不会导致非必要的内存分配
请记住:
- 对于单纯常量,最好以const对象或enum 替换#define。
- 对于形似函数的宏,最好改用inline函数替换#define。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现