条款1:尽可能用const和inline而不用#define 这个条款最好称为:“尽可能用编译器而不用预处理”,由于#define常常被认为好象不是语言自己的一部分。这是问题之一。再看下面的语句: #define ASPECT_RATIO 1.653 编译器会永远也看不到ASPECT_RATIO这个符号名,由于在源码进入编译器以前,它会被预处理程序去掉,因而ASPECT_RATIO不会加入到符号列表中。若是涉及到这个常量的代码在编译时报错,就会很使人费解,由于报错信息指的是1.653,而不是ASPECT_RATIO。若是ASPECT_RATIO不是在你本身写的头文件中定义的,你就会奇怪1.653是从哪里来的,甚至会花时间跟踪下去。这个问题也会出如今符号调试器中,由于一样地,你所写的符号名不会出如今符号列表中。 解决这个问题的方案很简单:不用预处理宏,定义一个常量: const double ASPECT_RATIO = 1.653; 这种方法颇有效。但有两个特殊状况要注意。 首先,定义指针常量时会有点不一样。由于常量定义通常是放在头文件中(许多源文件会包含它),除了指针所指的类型要定义成const外,重要的是指针也常常要定义成const。例如,要在头文件中定义一个基于char*的字符串常量,你要写两次const: const char * const authorName = "Scott Meyers "; 关于const的含义和用法,特别是和指针相关联的问题,参见条款21。 另外,定义某个类(class)的常量通常也很方便,只有一点点不一样。要把常量限制在类中,首先要使它成为类的成员;为了保证常量最多只有一份拷贝,还要把它定义为静态成员: class GamePlayer { private: static const int NUM_TURNS = 5; // constant eclaration int scores[NUM_TURNS]; // use of constant ... }; 还有一点,正如你看到的,上面的语句是NUM_TURNS的声明,而不是定义,因此你还必须在类的实现代码文件中定义类的静态成员: const int GamePlayer::NUM_TURNS; // mandatory definition; // goes in class impl.file 你没必要过于担忧这种小事。若是你忘了定义,连接器会提醒你。 旧一点的编译器会不接受这种语法,由于它认为类的静态成员在声明时定义初始值是非法的;并且,类内只容许初始化整数类型(如:int, bool, char 等),还只能是常量。 在上面的语法不能使用的状况下,能够在定义时赋初值: class EngineeringConstants { // this goes in the class private: // header file static const double FUDGE_FACTOR; ... }; // this goes in the class implementation file const double EngineeringConstants::FUDGE_FACTOR = 1.35; 大多数状况下你只要作这么多。惟一例外的是当你的类在编译时须要用到这个类的常量的状况,例如上面GamePlayer::scores数组的声明(编译过程当中编译器必定要知道数组的大小)。因此,为了弥补那些(不正确地)禁止类内进行整型类常量初始化的编译器的不足,能够采用称之为“借用enum”的方法来解决。这种技术很好地利用了当须要int类型时可使用枚举类型的原则,因此GamePlayer也能够象这样来定义: class GamePlayer { private: enum { NUM_TURNS = 5 } // "the enum hack " — makes // NUM_TURNS a symbolic name // for 5 int scores[NUM_TURNS];// fine }; 除非你正在用老的编译器(即写于1995年以前),你没必要借用enum。固然,知道有这种方法仍是值得的,由于这种能够追溯到好久之前的时代的代码但是不常见的哟。 回到预处理的话题上来。另外一个广泛的#define指令的用法是用它来实现那些看起来象函数而又不会致使函数调用的宏。典型的例子是计算两个对象的最大值: #define max(a,b) ((a) > (b) ? (a) : (b)) 这个语句有不少缺陷,光想一想都让人头疼,甚至比在高峰时间到高速公路去开车还让人痛苦。 不管何时你写了象这样的宏,你必须记住在写宏体时对每一个参数都要加上括号;不然,别人调用你的宏时若是用了表达式就会形成很大的麻烦。可是即便你象这样作了,还会有象下面这样奇怪的事发生: int a = 5, b = 0; max(++a, b);// a 的值增长了2次 max(++a, b+10); // a 的值只增长了1次 这种状况下,max内部发生些什么取决于它比较的是什么值! 幸运的是你没必要再忍受这样愚笨的语句了。你能够用普通函数实现宏的效率,再加上可预计的行为和类型安全,这就是内联函数(见条款33): inline int max(int a, int b) { return a > b ? a : b; } 不过这和上面的宏不大同样,由于这个版本的max只能处理int类型。但模板能够很轻巧地解决这个问题: template <class T> inline const T& max(const T& a, const T& b) { return a > b ? a : b; } 这个模板产生了一整套函数,每一个函数拿两个能够转换成同种类型的对象进行比较而后返回较大的(常量)对象的引用。由于不知道T的类型,返回时传递引用能够提升效率(见条款22)。 顺便说一句,在你打算用模板写象max这样有用的通用函数时,先检查一下标准库(见条款49),看看他们是否是已经存在。好比说上面说的max,你会惊喜地发现你能够后人乘凉:max是C++标准库的一部分。 有了const和inline,你对预处理的须要减小了,但也不能彻底没有它。抛弃#include的日子还很远,#ifdef/#ifndef在控制编译的过程当中还扮演重要角色。预处理还不能退休,但你必定要计划给它常常放长假。