《Effective C++》是一本轻薄短小的高密度的“专家经验积累”。本系列就是对Effective C++进行通读:
"这个条款或许改为”宁可以编译器替换预处理器“比较好。如:
#define PI 3.14
因为你使用宏定义时,它也许并不会被编译器看见,其可能在编译器开始处理源码之前就被预处理器移走了。
如,我们可以使用 const 定义一个常量来替换宏:
const double PI= 3.14;
const替换#define之后的好处:
1. 定义常量指针
定义常量指针。由于常量定义通常被放在头文件中,因此有必要将指针(而不是指针所指之物)声明为const;
例如如果要在头文件内定义一个常量的char* 字符串,你必须写两次const:
const char* const authorName = "Scott Meyers";
关于const的使用,条款3会有完整的讨论
当然,C++中的string对象会比char*-based更加合适,所以上述的authorName最好定义为如下的形式:
const std::string authorName("Scott Meyers");
2. 定义class专属常量
为了将常量的作用域(scope)限制于class内,你必须让它成为class的一个成员(member);而为确保此常量至多只有一份实体,你必须让它成为static成员;
下面你需要使用static为类创建一个常量。例如你在一个类中定义一个static成员:class GamePlayer { private: static const int NumTurns = 5; //常量声明式 int scores[NumTurns]; //使用该常量 ... };
- 1
- 2
- 3
- 4
- 5
- 6
上述你看见class内部的NumTurns是声明而非定义。通常 C++ 要求你为任何东西提供一个定义,但是如果它是 class 的静态成员(class专属常量又是static)且为整数类型(int、、char、bool等),则需要特殊处理;
只要不取它们的地址,你就可以声明并使用它们而无须提供定义。但如果你取某个 class 专属常量的地址,那么你就必须在类外提供一个定义(重点),如下:
const int GamePlayer::NumTurns; //NumTurns的定义
倘若把这个式子放在一个实现文件而非头文件。由于class常量已经在class内部声明了初值(上面为5),因此定义的时候就不用设定初值了。
NOTE:无法利用#define创建一个class专属常量,因为#defines 并不重视作用域。一旦宏被定义,它就在其后的编译过程中有效(除非在某处被#undef)。这意味着#defines不仅不能够用来定义 class 专属常量,也不能提供任何封装性(private、protected、public),而当然const 成员变量可以被封装的,NumTurns就是。
前面我们介绍了定义一个类静态成员。但是旧的编译器不支持上面的语法,它们不允许static成员在其声明式上获取初值(即:在类内给成员赋初值),因此你需要在类内声明,在类外定义,你可以将初值放在定义式,如:
class GamePlayer{
private:
static const int NumTurns; //常量声明
int scores[NumTurns];
}
GamePlayer::NumTurns=1.35;//实现文件内
虽然上面我们将 NumTurns 常量声明在类内,定义在类外。但是如果你的编译器在编译时告知不允许以静态成员 NumTurns 来初始化scores数组。那么需要使用enum来替换常量成员。如:
class GamePlayer{
private:
enum { NumTurns = 5; }; //令NumTurns成为5的一个记号名称
int scores[NumTurns];
}
这里的理论基础是,一个属于枚举类型的数值可权充ints被使用。
使用enum的理由:
1、enum的行为与const相比,其比较接像#define。例如:
① 不能取#define宏定义的地址,enum也不能,但取一个const的地址是合法的
② 不能获得一个指针/引用指向于某个整数常量,enum可以实现这个约束(见条款18对于“通过撰码时的决定实施设计上的约束条件”)
③ 有的编译器不会为“整型const对象”设定另外的存储空间(除非你创建一个指针或引用指向该对象),但是有的编译器却可能会。但是enum和#define就不会导致非必要的内存
2、enum更实用。许多代码用了它。事实上,"enum hack"是“template metaprogramming”(模板元编程见条款48)的基础技术
一个#define误用情况是以它实现宏(macros)。宏看起来像函数,但不会带来函数调用的额外开销。如,下面的宏调用了一个函数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的递增次数居然取决于它和谁比较,这想想非常糟糕。
如果用 inline 函数来替换上面的 #define 这就变得简单多了,下面我们使用一个内联函数模板(见条款30)来替换上面的宏,使得代码更易懂,不混乱
template<typename T>
inline void callWithMax(const T& a,const T& b)
{
f(a>b?a:b);//调用函数f
}
使用inline比#define的好处
1、使用#define时需要强烈注意"()"的使用,因此一不小心就会产生意想不到的结果,而inline函数就不需要
2、函数遵守作用域和访问规则,而#define没有。因此你也可以书写一个属于class的内联函数,而宏不可以
一个事物存在即有它的用处,好比自天生我才必有用,虽然通过上述我们对预处理器的需要降低了,但它(特别是#define)并不是无用的。因为在很多地方还需要用到它们(例如#include包含头文件、#ifdef/#ifndef扮演者控制编译的角色)。
期待大家和我交流,留言或者私信,一起学习,一起进步!