1.const:
#define P 5.2
记号名称P也行从来没被编译器看见;也许在编译器开始处理源码之前它就被预处理器移走了。于是记号名称P有可能没有进入记号表(symbol table)中。当你使用该常量但是编译出现错误时,有可能错误信息会提示5.2而非P(在编译之前,会进行预处理。P在预处理时期会被全部替换为5.2),这样有可能会令问题难以追踪。原因就是名称P可能从未进入记号表中。
const double P = 5.2;
作为一个语言常量,P肯定会被编译器看到,会进入记号表中。若使用该常量但是编译出错时,便可以直接定位到常量P。
记号表/符号表(symbol table):用于语言翻译器中的数据结构。在符号表中,程序源代码中的每个标识符都和它的声明或使用信息绑定在一起,比如其数据类型、作用域以及内存地址。
有两种特殊情况需要说明:
a.定义常量指针:
由于定义常量通常被放在头文件中,因此有必要将指针声明为const。例如:
const char* const name = “hugo”;
b.class专属常量:
为了将常量的作用域限制于class中,必须让它成为class的一个成员。而为了确保此常量最多只有一份,需要声明该常量为static常量,以确保不会为了class的每一个对象都持有一份该常量:
class Hugo {
private:
static const int num = 6;
};
#define不能够定义class专属常量,也无法提供任何的封装性,而const是可以做到的。
2.enum
当有如下情况:
class Hugo {
public:
...
private:
static const int len = 10;
int nums[len];
};
当你的编译器(错误的)不允许“static 整数型class常量”完成“in class初值设定”,可改用所谓的“the enum hack”补偿做法。其理论基础是:“一个属于枚举类型的数值可权充ints使用”。
可以修改上述程序如下:
class Hugo {
public:
...
private:
enum { len = 5 };
int nums[len];
};
enum hack:
a.其行为某方面来说比较像#define而不是const。比如是否可取地址(定义一个const变量实际也是一个变量,const只限定它不能被修改,所有变量都可在程序运行期间获取其地址;enum类型中的枚举项只是enum类型声明的一部分,它不是被定义出来的变量,无法对其取址;#define定义的是宏,其在预处理结束后就已经被替换掉,所以不能对其取址)。如果你不想让别人获得一个pointer或者reference来指向你的某个整数常量,enum可以实现这个约束。enum和#define一样高绝不会导致非必要的内存消耗。
b.enum hack也是模板元编程的基础技术。
3.inline
对于形似函数的宏:#define MAX(a, b) ( (a) > (b) ? (a) : (b))。其形似函数,实际为宏,该方式避免了函数调用带来的额外开销。但是,在某些时候,该方式可能会导致不易察觉的错误:
#define MAX(a, b) ( (a) > (b) ? (a) : (b))
int a = 0, b = 5;
MAX(a, ++b) // 7,因为b已经被累加了两次
MAX(a + 10, ++b) //b = 6,被累加了一次
此间b的累加次数取决于被用来和谁比较。这种情况下,便可以使用inline函数代替此处的#define:
template<typename T>
inline T get_max(const T& a, const T& b)
{
return a > b ? a : b;
}
这种方式不仅有着#define般的效率,还具有了#define形式所不具备的类型安全性和行为可预测性。并且由于get_max()是一个真正的函数,它遵守作用域和访问规则,这也是#define无法实现的。