尽量使用const、enum、inline,避免使用#define

 
尽量把工作交给编译器而不是预编译器,因为 #define 的内容不属于语言自身的范畴。这是 #define 的众多问题之一,请看下面的代码:
#define ASPECT_RATIO 1.653
编译器也许根本就接触不到这个符号名 ASPECT_RATIO ,它在编译器对源代码进行编译以前也许就被预处理器替换掉了。于是,ASPECT_RATIO 这一名字很可能不会列在符号表中。如果在代码中使用了这常量,也许就会遇到一些很不容易察觉的错误,而往往又很难找到问题所在,因为出错信息只会涉及 1.653 ,而对 ASPECT_RATIO 则只字不提。如果 ASPECT_RATIO 是在某个不知情的头文件中定义的,那么寻找 1.653 的出处对于我们来说就是大海捞针了,我们将在跟踪这一数值上浪费很多时间。在符号调试器中同样的问题也会出现,原因和上述问题是一致的:我们在编程时使用的名字可能没有加入符号表中。
解决的办法是:使用常量来代替宏定义:
 
const double AspectRatio = 1.653; // 宏的名字通常使用大写字母,
     // 于是常量名这样定义
作为语言层面的常量, AspectRatio 最终会被编译器所看到,并且会确保进入符号表中。另外,对于浮点数而言,使用常量较 #define 而言会生成更小的目标代码。这是由于预处理器会对目标代码中出现的所有宏 ASPECT_RATIO 复制出一份 1.653 ,然而使用常量 AspectRatio 时永远不会多于一份。
 
使用常量代替 #define 时有两个特殊情况值得注意。第一个是要把指针定义为常量。因为常量定义一般都放在头文件中(许多不同的源码文件会包含这些头文件),要将指针定义为 const 的,这一点很重要,通常情况下也要将指针所指的内容定义为 const 。比如说,在一个头文件中定义一个 char* 的字符常量时,需要写两次 const :
const char * const authorName = "Scott Meyers";
使用 string 对象要比使用其祖先“ char* ”好得多,知道这一点是很有意义的。上述的 autherName 最好以这样的形式定义:
const std::string authorName("Scott Meyers");
 
第二个特殊情况关系到类内部的常量。为了将常量的作用域限制在一个类里,必须将这个常量作为类的成员;为了限制常量份数不超过一份,须将其声明为 static 成员:
class GamePlayer {
private:
 static const int NumTurns = 5;      // 常量声明
 int scores[NumTurns];               // 该常量的用法
 ...
};
上面所看到的是 NumTurns 的声明,而不是定义。通常情况下, C++ 要求我们为所有要用到的所有东西做出定义,但是这里有一个例外:类内部的静态常量如果是整型(比如整数、字符型、布尔型)则不需要定义。只要我们不需要得到它们的地址,就可以只声明它们而不提供定义。如果需要得到类常量的地址;或者即使不需要这一地址,而我们的编译器错误地坚持必须为这个常量做出定义,这两种情况下我们应该以下面的形式提供其定义:
const int GamePlayer::NumTurns;     // NumTurns 的定义,
    // 下边会告诉我们为什么不为其赋值
应该把这段代码放在一个实现文件中。这是因为类常量的初始值已经在其声明时给出了(比如说, NumTurns 在声明时就被初始化为 5 ),而在定义的时候不允许为其赋初值。
 
顺便要注意一下,不可能使用 #define 来创建一个类内部的静态常量,这是因为 #define 不关心域的问题。一旦定义了一个宏,在编译时它将影响到所有其它代码(除非在某处使用 #undef 取消了这个宏的定义)。这不仅意味着 #define 不能用来定义类内部的常量,同时还说明它无法给我们带来任何封装效果,也就是说,“私有的” #define 这类东西是不存在的。然而 const 数据成员可以得到封装, NumTurns 就是一个例子。
早期的编译器可能不会接受上面代码的语法,这是因为那时候在声明一个静态的类成员时为其赋初值是非法的。与此同时,只有整型数据才可以在类内部进行初始化,并且只有常量才能得到初始化。在这种情况下不能使用上述的语法,可以在定义的时候为其赋初值:
class CostEstimate {
private:
 static const double FudgeFactor;       // 静态类常量的声明
 ...                                  // 应在头文件中进行
};
 
const double                             // 静态类常量的定义
 CostEstimate::FudgeFactor = 1.35;     // 应在实现文件中进行
上面几乎是我们所要了解的全部内容了。但是在某时刻还有可能会发生一个小的意外:当编译一个类时,我们可能需要这个类内部的一个常量的值,比如说前述的 GamePlayer::scores 数组的声明(编译器可能会坚持在编译时了解数组的大小)。编译器在这时违背了为类内部的静态的整型常量赋初值的规范,那么有什么办法补救呢?可以使用“enum 黑客手段”。这一技术利用了这一事实:枚举类型数据都是 int 型的,所以 GamePlayer 也可以这样定义:
class GamePlayer {
private:
 enum { NumTurns = 5 };       // “ enum 黑客”
                              // 使 NumTurns 成为一个符号名,其值为 5
 
 int scores[NumTurns];       // 可以正常工作
 ...
};
从许多角度讲,了解 enum 黑客手段是很有好处的。首先, enum 黑客的行为更像一个 #define 而不是 const ,在某些情况下这更符合我们的要求。比如说,可以合法地取得一个 const 的地址,但是取 enum 的地址则是非法的,而去取 #define 的地址同样不合法。如果不想让其他人得到我们的整形常量的指针或引用,那么使用枚举类型便是强制实施这一约束的一个很好的方法。粗心大意的编译器也许会为这类对象分配多余的内存,但也一定不会情愿。与 #define 类似, enum 不会带来不必要的内存开销。
了解 enum 黑客的第二个用处纯粹是实用主义的。许多代码都在这样做,所以看到它时必须要认得。事实上,enum 黑客是模板元编程的一个基本技术。
 
回到预处理器的问题, #defined 的另一个用法(这样做很不好,但这非常普遍)就是将宏定义得和函数一样,但不会带来函数调用的开销。下面例子中的宏定义使用 a 和 b 中 更大的参数调用了一个名为 f 的 函数:
// 使用 a 和 b 中 更大的一个调用函数
#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))
这样的宏会带来数不清的缺点,想起来就让人头疼。
无论什么时候,只要你写下了这样的宏,你必须为宏内部所有的参数加上括号。否则,其他人在某些语句中调用这个宏的时候总会遇到麻烦。即使做了正确的定义,古怪的事情也会发生:
int a = 5, b = 0;
CALL_WITH_MAX(++a, b);          // a 自加两次
CALL_WITH_MAX(++a, b+10);       // a 自加一次
在这里,调用 f 以前 a 自加的次数竟取决于它和谁进行比较!
幸运的是,我们不需要把精力放在这些毫无意义的事情上。我们可以使用内联函数的模板,此时可以得到宏的高效,并且一切都是可预知的,类型安全的:
template<typename T>
inline void callWithMax(const T& a, const T& b)
// 因为我们不知道 T 的类型是什么,因此我们通过引用传递 const 参数
{
 f(a > b ? a : b);
}
这一模板创建了一族函数,其中每一个函数都会得到同一类型的两个对象,使用其中较大的一个来调用函数 f 。可以看到,在函数内部不需要为参数加括号,不需要担心参数会被多次操作。与此同时,由于 callWithMax 是一个真实的函数,它遵循作用域和访问权的规则,比如类可以拥有私有的内联函数。然而宏在这些问题上就望尘莫及了。
 
C++ 为我们提供了 const 、 enum 、 inline 这些新特征,预处理器(尤其是 #define )的作用就越来越小了,但是这并不是说可以完全抛弃它。 #include 仍是程序中的主角, #ifdef/#ifndef 在控制编译过程还有着举足轻重的地位。说“预处理器该退休了”还为时过早,但是还是要经常给它放放长假。
 
需要记住的
# 对于简单的常量,应该尽量使用 const 对象或枚举类型数据,避免使用 #define 。
#对于类似程序的宏,尽量使用内联函数,避免使用 #define 。
 
 
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值