先上总结:
- 对于单纯变量,最好以
const
对象或enum
替换#define
。 - 对于形似函数的宏(
macro
),最好改用inline
函数替换#define
。
现在我们来看看为什么会有如此总结。
1、为什么最好用const
替换#define
?
利用#define
定义宏常量的方式如下:
#define ASPECT_RATIO 1.653
对于宏ASPECT_RATIO
有可能并未进入记号表内,编译器并未开始处理就被预处理器移走。如果此变量出现编译错误,错误信息可能只提到1.653, 而不是ASPECT_RATIO
,如果它并不在自己定义的头文件中,这会让你很难定位追踪到它,从而浪费时间。
对于这种情况,可以使用const
替换#define
,
const double AspectRatio = 1.653
const
修饰的变量作为一个语言常量,肯定会进入记号表。且不会出现#define
导致目标码多出一份1.653的问题。
注:symbol table
(记号表)翻译成**“符号表”**应该好理解一些。针对计算机语言,在不同的地方,它的用处也不一样。有词法分析时分词用的符号表,用来把所有用到的标识符区分出来。也有做语法分析时用的语法元素的符号表。编译成目标文件时,也会产生用于内/外部符号定位/链接用的符号表。生成可执行文件时也有类似的符号表…不论在什么地方,符号表的基本都可以看成一个名称索引表,为了方便数据归类查找用的。参考:C++记号表是什么?-CSDN社区
关于使用const
常量关键字替#define有两种特别情况需要交代:
1、定义一个指向常量的常量指针时,我们需要写两次const
:
const char* const authorName = "Mai Bai De Da Di Zhu!"
此时我们可以使用std::string
进行替代:
const std::string authorName("Mai Bai De Da Di Zhu!")
2、对于类内部的静态常量静态成员变量,如:
class GamePlayer {
private:
static const int NumTurns = 5;
int scores[NumTurns];
...
}
通常情况下,类的静态成员不应该在内部进行初始化的,但是我们可以为静态成员提供const
整数类型的类内初始值(要求静态成员必须时字面常量表达式constexpr
,并且初始值也得是常量表达式。)
不过此时,
static const int NumTurns = 5;
只是NumTurns
的声明式,而不是定义式,通常我们也需要在类的外部定义一下NumTurns
,不过注意:如果我们在类的内部提供了初始值,在外部定义时就不能再指定别一个初始值了(如果编译器不支持在静态成员声明时初始化,则可以将初始化放在定义时)。
2、为什么最好用enum
替换#define
?
对于enum
,其数值可充当int
整型。enum
在某些方面挺像#define而非const
,例如你无法取其地址,就像#define
一样,而对于const
则可以。另外如果你不想别人使用指针或者引用指向你的某个整数常量,你就可以使用enum
。enum
还有具有实用主义的特征,其应用很广泛。
3、为什么最好用inline
替换#define
?
对于宏函数,其繁琐难懂的表达让人看了很烦,如:
#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))
尤其是要注意其函数定义中的参数,所有实参必须要加上小括号,并且每层计算逻辑都需要加小括号,就算如此,也很可能出现纰漏。
inline
(内联)函数的出现可使你很好解决这个问题:
template<typename T>
inline void callWithMax(const T& a, const T& b){
f(a > b ? a : b);
}
另外,无法利用#define
创建类的专属成员,无法提供任何封装性。因为#define
并不注重作用域,其一旦被定义,在其后的编译过程中就有效(除非在某处被#undef
)。而我们可利用inline
可定义类内的私有inline
函数。