《Effective C++》阅读笔记三:聊聊#define与const,enum和inline

来源:互联网 发布:159魔天宫输出数据 编辑:程序博客网 时间:2024/06/18 14:14

C++程序员良好编程条款:宁可以编译器替换预处理器

实际上,#define甚至可以不被视为语言的一部分,这正是它的问题所在:

#defien ASPECT_RATIO 1.63

实际上,对编译器而言,符号ASPECT_RATIO是不可见的!是的,对于编译器而言,不存在ASPECT_RATIO,只存在1.63,因为在编译器开始处理源代码之前,符号ASPECT_RATIO就被预处理器移走了。于是,符号ASPECT_RATIO并没有进入符号表(symbol table)内。另一方面,正因为宏定义对编译器不可见,也就无法获得类型检查等有助于代码规范的帮助。

解决的方法是用以下常量替换上述宏定义:

const double Aspect_Ratio = 1.63;

作为一个语言常量,Aspect_Ratio肯定会被编译器看到,并记录到符号表中。用const定义的常量会比用#define定义的宏代码具有更少的内存消耗。因为对于宏定义,预编译器“盲目地将宏名ASPECT_RATIO替换成1.63”,导致目标程序中存在多份常量1.63。而改用const定义后,仅仅只会占用一份内存。

当用const常量代替#define时,有两种特殊情况:

1.定义常量指针(const pointers)

在头文件中定义一个常量的char*指针的字符串,可以这样写:

const char* const cAuthorName = "Jeremy Chen";

这句话表示cAuthorName是一个指向常量字符串“Jeremy Chen”的常量指针,即指针指向不能变,指针指向的内容也不能变。当然,在C++中通常用专门的string对象更加方便合适:

const std::string authorName("Jeremy Chen");

2. 作为class的专属常量

为了将常量的作用域限制在class内部,必须将const常量定义成class的成员变量。而要确保这个常量只有一份内存拷贝,必须将其声明为static成员:

class GamePlayer{private:static const int NumTurns = 5;// 如果你的编译器不支持在声明时赋值,也可以将赋初值放到定义语句中。但这种情况不适用于表达数组大小!int score[NumTurns];// 如果用常量表示数组大小,而编译器不支持声明赋值,则必须要用enum代替...};

注意,这里仅仅提供了NumTurns的声明而非定义!C++通常要求在使用一个变量前提供对象的定义。但如果其是一个class专属常量又是static且为整数类型(不仅包括int,还包括char,bool),则可以特殊处理。只要不取该变量地址,可以仅仅只是声明它而不定义就使用该变量!而一旦你需要取得这个常量的地址,或编译器需要看到一个定义式的时候,就必须另外提供定义:

const int GamePlayer::NumTurns;// 定义,这通常都是必须的

注意,请将这句语句放在实现文件中而非头文件中由于已经在声明时获得初值,所以定义时不需要再设初值!另外,我们无法利用#define创建一个class专属的常量,因为#define 并不重视作用域。这意味着#define不仅不能用来定于class专属常量,也不能对它提供任何封装性,更不存在所谓的private #define这样的东西。而这些const常量都可以做到。

如果编译器不允许声明赋值,那么改用所谓的“the enum hack”是可行的替换选项之一:

class GamePlayer{private:enum { NumTurns = 5};// 枚举初始化,常量int scores[NumTurns];...};

关于“the enum hack”提2点:1. “the enum hack”的行为从某些方面来说更像#define而不是const。例如,不能取enum的地址。 2. 作为合格的C++程序员,你还是应该认识这种稍显奇怪的用法的,毕竟有一部分比较老式的代码中存在这样的写法。

关于宏#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次

对于以上代码,调用f的递增次数取决于“它和谁发生了比较”!理解这一点只需要记住一点,宏#define所作的全部工作,就是进行文本替换!所以,把++a作为一个整体替换即可理解。

要解决这个问题,比较好的方法是用template inline函数:

template<typename T>inline void callMax(const T& a, const T&b){f(a > b ? a : b);}

最后需要指出的是,尽管宏#define在很多场景中可以用更新更好的技术替换,但在某些场景中它依然是不可或缺的,比如用于控制编译条件的#ifndef ... #define... #endif,所以,预处理器依然有很重要的作用,只是需要用在合适的场景中。

0 0
原创粉丝点击