条款2 尽量用const,enum,inline代替#define

	这个条款改为“尽量把工作交给编译器而不是预编译器”更恰当,因为或许#define不被视为语言的一部分,这正是问题所在,如下:
	#define  DATA  1.6
	编译器也许根本就接触不到这个DATA符号名 ,可能它在编译器对源代码进行编译之前就被预处理器替换掉了。So, 这一名字很可能不会列在符号表(symbletabal)中,如果你在代码中使用了这常量遇到一些错误,却往往很难找到问题的所在,因为出错信息也许会提到1.6而不提到DATA。如果DATA 是在某个你不知情的头文件中定义的,那么寻找 1.6的出处可以说是大海捞针,跟踪这一数值上将浪费很多时间。在符号调试器中也会出现同样的问题,原因是一样的:你在编程时使用的名字可能并没有加入符号表中。
	解决的方法是:使用常量来代替宏定义
	const double DATA=1.6;      //宏名一般大写

	显然,作为一个语言常量,DATA肯定会被编译器看到,当然就会进入记号表内。另外,对于浮点数来说,使用常量比#define会生成更小的目标代码。这是因为预处理会对目标代码中出现的所有宏DATA复制一份1.6,而使用常量的DATA永远只有一份。
当使用常量替换#defines时,有两种特殊情况。一、定义常量指针(constant pointers),因为常量定义通常被放在头文件内(方便不同源文件包含)。要将指针定义为const的,通常情况下也要把指针所指的内容定义为constant的,比如说,在一个头文件中定义一个 char* 的字符常量时,你需要写两次 const :
	const char* const Name=”lsfreeing”;

	在条款3中将全面介绍 const 的含义和用法,尤其是在其与指针混合使用时的一些细节问题。但是,在这里提醒你使用 string 对象要比使用其祖先“ char* ”好得多,知道这一点是很有意义的。上述的 Name 最好以这样的形式定义:
<span style="font-family:Microsoft YaHei;font-size:14px;"><span style="white-space:pre">	</span>const std::string Name("lsfreeing");</span>
第二个特殊情况关系到类内部的常量。为了将常量的作用域限制在一个类里,必须将这个常量作为类的成员;为了限制常量为一份,必须将其声明为 static 成员:
class lsfreeing
{
private:
static const int Number=5;  //常量声明
int scores[Number];         //使用该常量
};
上面的 Number只是声明,而不是定义。通常情况下, C++ 要求你为所有要用到的所有东西做出定义,但是这里有一个例外:类内部的静态常量如果是整型(比如整数、字符型、布尔型)则不需要定义。只要不需要得到它们的地址,可以只声明而不提供定义。如果你需要得到类常量的地址;即使你不需要这一地址,而编译器坚持你必须为这个常量做出定义,这两种情况下我们应该用下面的形式提供其定义:
const int lsfreeing::Number;   //Number的定义
                               //下面将讨论为什么没有赋值


这段代码应该放在一个实现文件中。这是因为类常量的初始值已经在其声明的时候给出了(比如说, Number在声明时就被初始化为 5 ),而在定义的时候不允许为其赋初值

	要注意的是,不能用 #define 来创建一个类内部的静态常量,这是因为 #define 不关心作用域。一旦定义一个宏,在编译时它将影响到所有其它代码(除非某处使用 #undef 取消这个宏定义)。这意味着 #define 不能用来定义类内部的常量,同时也无法带来任何封装效果,就是我们所说的:“私有的” #define不存在。但const 数据成员可以得到封装,上面DATA就是一个例子。

	早期的编译器不支持上面代码的语法,因为那时候在声明一个静态的类成员时为其赋初值是非法的。与此同时,只有整型数据才可以在类内部进行初始化,并且只有常量才能得到初始化。在这种情况下不能使用上面的语法,我们可以在定义的时候为其赋初值:
class lsfreeing{
private:
static const double DATA;  //静态类声明
…        //在头文件中进行
       }
constdouble lsfreeing::DATA=1.35;     //静态类常量的定义,位于实现文件内


上面几乎是我们要了解的全部内容。但也有例外:当你在编译一个类的时候,你可能需要这个类内部的一个常量值,比如说前述的lsfreeing::scores 数组的声明(编译器可能需要在编译时了解数组大小)。编译器在这时违背了为类内部的静态整型常量赋初值的规范,补救的方法是可以使用“ enum”。这一方法的理论基础是:枚举类型数据是 int 型的,所以 lsfreeing也可以这样定义:
class lsfreeing{
private:
enum{Number=5};    //使Number成为一个符号名,值为5
int scores[Number];    //可以正常运行
}
基于多个理由, enum hack值得我们了解。首先, enum hack的行为更像一个 #define 而不是 const ,在某些情况下这更符合的要求。比如说,取得一个 const 的地址是合法的,但是取 enum 的地址则是非法的,取#define 的地址同样非法。如果不想让其他人获得整形常量的指针或引用,使用枚举类型是一个好方法。(参见第 18 项)。优秀的编译器不会为“整数型const变量”分配额外的存储空间(除非创建一个指向该对象的指针或引用),但不够优秀的编译器可能如此。与 #define 类似, enum 不会带来不必要的内存开销。

	了解 enum hack的第二个理由纯粹是实用主义。许多代码都用,所以你看到它时必须要认得。事实上, enum hack是模板元编程的一个基本技术。
	再次回到预处理, #defined 的 另一个用法(这样做很不好,但这非常普遍)就是将宏定义得和函数一样,但不会带来函数调用的开销。下面例子中的宏定义使用 a 和 b 中 更大的参数调用了一个名为 f 的 函数:
 
<span style="white-space:pre">	</span>// 使用 a 和 b 中 更大的一个调用函数
	#define   MAX(a, b)   f((a) > (b) ? (a) : (b))


这样的宏有很多缺点。当这样的宏,我们必须为宏内部所有的参数加上括号。否则,即使做调用会出现问题。即使为每个实参加了括号,也会发生一个问题,比如下面的代码:
inta=5,b=0;
MAX(++a,b);     //a自增2次
MAX(++a,b+10);   //a自增一次
在这里调用f之前,a的自增次数取决于它和谁比较。

幸运的是,我们可以使用内联函数的模板,此时你可以得到宏的高效,并且一切都是可预知和安全的:
template<typenameT>
inline void Max(const T& a, const T& b)
// 因为我们不知道 T 的类型是什么,因此我们通过引用传递 const 参数。参见第 20 项
{
  f(a > b ? a : b);
}


这一模板创建了一类函数,其中每一个函数都会得到同一类型的两个对象,使用其中较大的一个来调用函数 f 。可以看到,在函数内部不需要为参数加括号,不需要担心参数会被多次操作。与此同时,由于Max 是一个真实的函数,它遵循作用域和访问规则,比如类可以拥有私有的内联函数。而在这些问题上宏无法实现
尽管有了const 、 enum 、 inline 这些新特征,预处理器(尤其是 #define )的作用就越来越小,但#include 仍是程序中的主角, #ifdef/#ifndef 在控制编译过程还有着举足轻重的地位。

需要记住的:
 	 对于简单的常量,应该尽量使用 const 对象或枚举类型数据,避免使用 #define 。
  	对于类似程序的宏,尽量使用内联函数,避免使用 #define 。

                
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值