一、为什么不推荐使用#define
#define ASOECT_RATIO 1.653
#define的缺点
-
①宏的名称是不会被编译器看见的,因为在程序的预处理阶段,宏会被替换为其对应的数值/表达式,因此宏对于编译器来说是不存在“名称”这一概念的。
- ②因为宏没有名称这一概念,因此宏的名称是不会进入符号表中(symbol table),宏在符号表中只是其对应的数值/表达式。于是当你调试时(例如使用gdb或其他调试工具),是看不到宏的名称的。例如上面的“ASOECT_RATIO”宏在符号表中只是一个数值1.653。因此当你在调试时如果你这个宏出现了问题,但是很难发现问题,因为其只是一个数字。如果这个“ASOECT_RATIO”宏定义在非你缩写的头文件中,那么就会更难发现问题了
二、以const替换宏
const double AspectRatio = 1.653;
以const替换宏之后的好处
- ①常量肯定是会被编译器看到的,因此也就会进入符号表中
-
②减少了目标码:代码中用到宏“ASOECT_RATIO”的地方,都会被替换为1.653,因此会导致目标码增多。改用常量之后就减少了目标码
三、以const替换宏之后的两种特殊情况
①常量指针、指针常量、常量指针常量
- 如果你是定义常量指针。由于常量定义通常被放在头文件中,因此有必要将指针声明为const
- 例如如果要在头文件内定义一个常量的char*字符串,你必须写两次const:
const char* const authorName = "Scott Meyers";
const std::string authorName("Scott Meyers");
②定义class的专属常量(类的静态成员)
-
无法利用#define创建一个class专属常量,因为#define是不重视作用域的。一旦宏被定义,它就在其后的编译过程中有效(除非在某处被#undef)。这意味着#defines不仅不能够用来定义class专属常量,也不能提供任何封装性(private、protected、public)
- 下面你需要使用static为类创建一个常量。例如你在一个类中定义一个static成员:
class GamePlayer{
private:
static const int NumTurns = 5; //常量声明式
int scores[NumTurns];
}
-
class内部的NumTurns是声明而非定义(重点)。一般C++要求你为任何东西提供一个定义,但是如果其是class的静态成员且为整数类型(int、、char、bool等),则需要特殊处理
-
只要不取它们的地址,你可以声明并使用它们而无须提供定义。但如果你取某个class专属常量的地址,那么你就必须在类外提供一个定义(重点),如下:
/*如果把这个式子放在一个实现文件而非头文件。由于class常量已经
在class内部声明了初值(上面为5),因此定义的时候就不用设定初值了*/
const int GamePlayer::NumTurns; //NumTurns的定义
四、以enum替换#define
- 在“三”中我们介绍了定义一个类静态成员。但是旧的编译器不支持上面的语法(即:在类内给成员赋初值),因此你需要在类内声明,在类外定义,例如下面的代码是对上面GamePlayer的改进
class GamePlayer{
private:
static const int NumTurns; //常量声明式(通常位于头文件内)
int scores[NumTurns];
}
GamePlayer::NumTurns=1.35;//定义(可以位于头文件/实现文件内)
为什么使用enum?
- 虽然上面我们将NumTurns常量声明在类内,定义在类外。但是如果你的编译器在编译时告知不允许以静态成员NumTurns来初始化scores数组。那么需要使用enum来替换常量成员。例如,下面改写GamePlayer类
class GamePlayer{
private:
enum { NumTurns = 5; }; //令NumTurns成为5的一个记号名称
int scores[NumTurns]; //编译通过
}
使用enum的两个理由
- ①enum的行为与const相比,其比较接近于#define。例如:
- 不能取#define宏定义的地址,enum也不能
- 不能获得一个指针/引用指向于某个整数常量,enum可以实现这个约束(见条款18对于“通过撰码时的决定实施设计上的约束条件”)
- 有的编译器不会为“整型const对象”设定另外的存储空间(除非你创建一个指针或引用指向该对象),但是有的编译器却可能会。但是enum和#define就不会导致非必要的内存
-
②enum更实用。许多代码用了它。事实上,enum是“模板元编程”的基础技术(见条款48)
五、以“inline函数”替换“#define调用函数”
先来看看一个使用#define调用函数带来的错误
#define CALL_WITH_MAX(a,b) f((a)>(b)?(a):(b))
- 但是我们以下面两种方式调用了宏,明显看到了错误之处
int a=5,b=0;
CALL_WITH_MAX(++a,b); //a被累加2次
CALL_WITH_MAX(++a,b+10);//a被累加1次
以inline函数来替换上面的#define
- 下面使用一个内联函数模板(见条款30)来替换上面的宏
template<typename T>
inline void callWithMax(const T& a,const T& b)
{
f(a>b?a:b);//调用函数f
}
上面使用inline比#define的好处
- ①使用#define时需要强烈注意"()"的使用,因此一不小心就会产生意想不到的结果,而inline函数就不需要
- ②函数遵守作用域和访问规则,而#define没有。因此你也可以书写一个属于class的内联函数,而宏不可以
六、总结
-
预处理器(特别是#define)并不是无用的。因为在很多地方还需要用到它们(例如#include包含头文件、#ifdef/#ifndef扮演者控制编译的角色等)
-
请记住:
- ①对于单纯常量,最好以const对象或enum替换#define
- ②对于形似函数的宏,最好改用inline函数替换之
江南、董少
博客专家
发布了1384 篇原创文章 · 获赞 930 · 访问量 29万+
他的留言板
关注