这个条款其实还可以说成将编译器的工作替换预处理器的工作。
我们可以做出下面的事情
#define PI 3.1415926
但是,由于中中原因,这条语句在预处理的时候可能会被忽略,造成错误。
也许编译器会提示一些奇怪的错误信息,并且在追踪错误时难度也会提升。
根据编译原理,在宏定义常量时编译器并不会将其添加到记号表中,因为宏的本质仅仅是替换,这就导致了如上所说。
const
解决之道之一是用const定义
const int pi = 3.1415926;
因为这是一个却却实实的常量变量 ,编译器首先会看到这个常量。因此,在使用时追踪错误可能会加的方便。
而且使用#define会导致更大的代码量,预处理器只会无脑的替换宏,会导致多份的PI出现。而该改用常量则不会出现相同的情况。
关于const的使用,有两种特殊情况
指针
为了保证指针指向的东西不能改变,并且指针本身也是不能改变的应当这样使用
const char* const str = "something";
关于const的进一步的讨论,在条款3马上就会提。
这里应当提醒以下,对与定义char* - base 使用std::string替换要或更佳的好,得益于C++为我们提供的模板类string,在编写程序时const修饰字符串使用string替代char* - base。
const std::string str("something");
对于类成员使用const
应当写成这样的形式
class GamePlayer
{
strtic const int NumTurns = 5; //这里为什么可以这样写下面说
double score[NumTurns];
};
应当使用static修饰,为什么?
存在与类中的常量,是不想让其该变的,那么每一个对象都存储一个又有什么意义呢?使用静数据成员即可。
关于直接在类中常量式生命一个静态常量?通常C++要求为所用的任何东西都提供一个定义式。
请注意:无法用#define创建一个专属于class的常量,因为宏定义无视作用域,预处理器只是将#define下面的宏替换为替换体。也就是说,没有private #define, protect #define这样的宏。除非宏被#undef否则他将一直存在。这样做破坏了类的封装性,有悖于OO思想。
旧式的编译器可能不支持上面所说的办法,那么请还是老老实实的使用类内声明,类外初始化。
class GamePlayer
{
const int NumTurns;
};
...
const int GamePlayer::NumTurns = 5;
枚举类型
enum类型行为的某方面比较像#define而不像const,有时候这正是你想要的。比如不能提取enum内标签的地址。如果不想外部获得一个指向对象的某个常量整形的引用或者指针,enum可以实现这样的需求。
inlie函数
如果你了解一些宏的特性,就会知道下面的代码会有什么问题了
#define Max(a,b) func((a)>(b) ? (a) : (b))
...
int a =5;
int b = 0;
int max_num = Max(++a,b); //a selfup twice
int max_num = Max(++a,b+10); //a selfup once
另外,宏函数还会隐式的带来许多令人难以捉摸的问题,从某种方面来说,宏函数是糟糕的。
如果你想要避免这些问题请使用模板内联函数,其在保证泛型的同时避免了一些宏函数令人头疼的问题。
template <typename T>
inline T(const T &a, const T &b)
{
func(a > b ? a : b);
}
模板函数产出函数群,是在泛型方面,#define只能望其项背,而且,模板函数也是普通函数,因此可以u像宏函数那样破坏类的封装性(private/procted/public inline template<T> func)。
虽然有了上面的一些解决办法,我们对预处理的要求降低了,但是并没有完全消除,#include仍是必需品,而条件编译也继续扮演着重要的角色。目前还达不到预处理器全面引退的时候,但你应该明确地给予它更长更频繁的假期。
请注意: