effective C++读书笔记 条款二 以编译器替换预处理器

来源:互联网 发布:手机英语新闻软件 编辑:程序博客网 时间:2024/06/06 00:35

写C++代码时,你可能会写下这样的语句

#define ASPECT_RATIO 1.653

而记号名称  ASPECT_RATIO 编译器可能从未看见;因为可能在编译器开始处理源码之前就被预处理器移走了。于是记号名称 ASPECT_RATIO 有可能没有进入符号表(symbol table)内。于是当你运用此常量但获得了一个编译错误信息时,可能会带来困惑:这个编译错误信息提到是1.653而不是 ASPECT_RATIO 。如果 ASPECT_RATIO 被定义在一个非你所写的头文件中,你肯定对1.653以及它来自哪里毫无概念,于是你将因为追踪它浪费时间。


解决的方法是用一个常量替换上述的#define:

const double  AspectRatio =  1.653;   // 大写名称通常用于宏

         //因此这里改变名称写法


作为一个语言常量,AspectRatio肯定会进入符号表,因此当出现编译错误时,就不会出现和使用#define相同的情况。


以const替换#define时,有两种特殊情况值得说说:

第一是定义常量指针,由于常量定义式通常被放在头文件中,因此有必要将指针声明为const。例如要在头文件内定义一个常量的char *字符串,你必须写const两次:

const char* const authorName = "Scott Meyers";

值得提醒的是,string对象通常比char*合适,所以上述authorName往往定义成这样好些

const std::string authorName("Scott Meyers");

第二是class专属常量。为了将常量作用域限制于class内,你必须让它成为class的一个成员:而为确保此常量至多只有一份实体,你必须让它成为一个静态成员

class GamePlayer{private:      static const int NumTurns = 5;      int scores[NumTurns];}

(注:类里面的静态成员只会被声明一次,就是说,无论你声明了多少个这个类的对象,他们都共享一个静态成员,静态成员函数也是一样的道理,且静态成员函数只能够访问静态成员变量,即,静态成员函数并不具有普通成员函数具有的this指针,而this指针就是成员函数能够访问类中非静态成员的原因,且,除了类对象能够调用静态成员函数以外,类本身也可以,而非静态成员函数可以任意地访问静态成员函数和静态数据成员。只有静态的常量整形数据成员才可以在类定义中初始化,非常量的静态数据成员及非整形的常量都必须在类体外初始化)

如果你的编译器坚持要看到一个你所声明的静态整形成员变量的定义式,你就把这个式子放入一个实现文件而非头文件。


const int GamerPlayer::NumTurns;

由于该常量在class中声明时已经获得初值,因此定义时不可以再设置初值。


顺带一提,我们无法利用#define创建一个class专属常量,因为#define不重视作用域。一旦宏被定义,它在其后的编译过程中就有效(除非在某处被#undef)。这就意味#define不仅不能够用来定义class专属常量,也不能提供任何封装性,也就是说没有所谓private #define这样的东西。而当然const成员变量是可以被封装的,NumTurns就是。


如果你的编译器不允许static成员在声明式上获得初始值(当然,此处的初始值也仅仅指整数常量),你可以把初值放在定义式

class CostEstimate{private:       static const double FudgeFactor;}const double CostEstimate::FudgeFactor=1.35;


这样做的唯一例外是当你在class编译期间需要一个class常量值,例如在上述的GamePlayer::scores的数组声明式中(是的,编译器坚持编译期间必须知道数组的大小)。这时候万一你的编译器不予许static成员在声明式上获得初始值,可以改用枚举常量的方法,即enum hack。


class GamePlayer{private:       enum {NumTurns=5};       int scores[NumTurns];}
enum hack的行为的行为比较像#define而不是const,例如取一个const的地址是合法的,但取一个enum的地址是不合法的,而取#define的地址也是不合法的。


对#define的另一种常见误用是以他实现宏,宏看起来像函数,但不会招致函数调用带来的额外开销,下面这个宏夹带着宏实参,调用函数f()

//以a和b较大的值调用f#define CALL_WITH_MAX(a,b) f((a)>(b)? (a):(b))

这般的长相的宏有太多缺点,你必须记得为宏中所有实参加上括号,否则在表达式中调用这个宏时可能出现麻烦。不过,纵使你记得为实参加上括号,有时也还是会出现不可思议的事情

int a=5,b=0;CALL_WITH_MAX(++a,b); //a被累加2次CALL_WITH_MAX(++a,b+10); //a被累加1次
在这里,调用f之前,a的递增次数竟然取决于它被用来和谁比较!   宏定义只是简单的字符替换,因此使用内联函数是更好的做法。



template<typename T>inline void callWithMax(const T&a,const T&b){f(a>b)?  a:b;}


这样就不用在函数本体为参数加上括号,也不用担心参数被计算多次……等等。





0 0
原创粉丝点击