1.尽量用const代替#define
1#define ASPECT_RATIO 1.643
记号ASPECT_RATIO可能从未被编译器看见,这是因为宏展开在预处理阶段展开,预处理后的编译阶段它已经不存在了。记号ASPECT_RATIO有可能没有进入记号表内,于是当你运用此常量时得到一个提及1.643的编译错误信息时,可能会引起困惑。当记号ASPECT_RATIO不是被定义在你所写的头文件中,你会对1.643毫无概念。
解决上面问题的方法是:用一个常量替换上述的宏。
1const double AspectRatio = 1.643 // 注意:全部大写名称常用于宏
作为语言常量(并非是字面常量,语言常量实质是一个变量,只不过它不能被修改而已),AspectRatio一定会被编译器看见,当然会进入记号表中。就上例来说,使用常量可能比使用#define导致较小量的码,因为宏展开时,预处理器会盲目地将记号ASPECTRATIO替换为1.643,可能导致目标码出现多份1.643。
当使用const替换#define时,有两种特殊情况值得研究一下:
(1).定义常量指针
(2).类class的专属常量
(1).定义常量指针
由于常量定义式通常被放在头文件中,便于被不同的源码包含。因此有必要将指针(不仅仅是指针所指之物)声明为const。此时,就需要定义一个指向常量的常指针,它的作用是类似于用宏定义了一个常量。如下例所示:
1const char * const authorName = "CurryCoder"; 1// 常量指针 2const int *p; 3int const *p; 4 5// 指针常量 6int * const p; 7 8// 指向常量的常指针 9const int* const p; // 此句等同于定义了一个常量 const int IntApp = p;
(2).class专属常量
为了将常量的作用域限制在一个类体内,必须让它成为这个类的一个成员。同时,为了确保此常量至多只有一个实体,必须让它成为一个static成员。如下例所示:
1class Player{ 2private: 3 static const int NumTurns = 5; 4 int scores[NumTurns]; 5 .... 6};
上例中,你所看到的是NumTurns的声明式而非定义式,通常C++要求你对你所使用的任何东西提供一个定义式,但如果它是一个class专属常量又是static且为整数类时,需要特殊处理。只要你不取它们的地址,你可以声明并使用它而无需提供定义式。但如果你取某个class专属常量的地址,或者即使你取其地址而你的编译器却要坚持看到一个定义式,你就必须另外提供定义式:
1const int Player::NumTurns; // 定义式
一定要将上面的定义式放入一个实现文件中,而非是头文件中。因为class常量已经在声明式中获得了初始值。于是,定义式不可以再设置初值。
注意:无法利用#define创建一个class专属的常量,因为宏没有作用域的概念,不能提供任何封装性。一旦宏被定义,它就在其后的编译过程中有效(除非在某处被#undef)。
旧的编译器也许不允许static成员在其声明式上获得初值。在类的内部设置static数据成员的初始值,也仅仅只允许对整型常量允许。如果你的编译器不支持上述语法,可以在类的外部进行进行static数据成员的初始化(通常情况下,也采用这种方式进行静态数据成员的初始化)。如下例所示:
1class Person{ 2private: 3 string name; 4 static const int age = 28; // 只有const int静态数据成员才能在声明式上获得初始值 5 static const string address; // const string静态数据成员不可以在声明式上获得初始值,只能在类外进行初始化 6 // static const int numTurns = 5; 7 enum {numTurns = 5}; // 使用枚举类型代替类内进行初始化 8 int scores[numTurns] = {99, 93, 90, 84, 73}; 9 10 11 12public: 13 Person(const string& nm): name(nm){} 14 static string PrintAddress(){ 15 return address; 16 } 17 18 void Print(){ 19 cout << name << " is " << age; 20 } 21}; 22 23const string Person::address = "NanJing";
2.用enum代替const
只有一种例外的情况:当你在class编译期间需要一个class常量值。例如,上述的Player::scores的数组声明中,编译器坚持必须在编译期间知道数组的大小。此时,如果你的编译器不允许static const int NumTurns = 5;,可以改用所谓的the enum hack补偿做法。如下例所示:
1class Player{ 2private: 3 enum { 4 NumTurns = 5; 5 }; 6 int scores[NumTurns]; 7 .... 8};
enum hack的行为某方面来说比较像#define而不像const,因为取一个const的地址是合法的(const变量实际也是变量,它只不过不能被修改而已),但取一个enum的地址是不合法的(enum类型中的枚举项只是类型声明的一部分,不是定义出来的变量),而取一个#define的地址也是不合法的(它是预处理的东西,预处理后的编译阶段它已经不存在)。如果你不想让别获得一个pointer或referencce指向你的某个整型常量,enum可以帮你实现这个约束。
3.尽量用enum代替#define
常见的#define误用的情况是以它实现宏。宏看起来像函数,但不会招致函数调用带来的额外开销。下例中这个宏夹带着宏实参,调用函数f。当你写成下面的宏时,必须记住为宏中的所有实参加上小括号。否则,别人在表达式中调用这个宏时可能会很麻烦。
1#define CALL_WITH_MAX(a, b) f((a) > (b)) ? (a) : (b)) 2 3int a = 5, b = 0; 4CALL_WITH_MAX(++a, b); 5CALL_WITH_MAX(++a, b+10); // 函数f之前,a的递增次数取决于它被拿来与谁比较
解决上述问题,只需写出template inline函数模板即可。它不需要在函数内部中为参数加上小括号,也不需要担心参数被更改。如下例所示:
1template<typename T> 2inline void callWithMax(const T& a, const T& b){ 3 f(a > b) ? a : b; 4}
4.总结
有了const、enum和inline我们对预处理器的需求降低了,但并非完全消除。#include仍然是必需品,而#ifdef/#ifndef也继续扮演控制编译的重要角色。请记住:
(1).对于单纯常量,最好以const对象或enums替换#define
(2).对应形似函数的宏,最好改用inline函数替换#define