宏替换的工作是由预处理器来做的,它不会被视为语言的一部分,因此可能发生的情况是:
定义了如下宏提换语句:#define RATIO 1.653
也许在编译器处理源码前这句话就被预处理器移走了,该记号名称可能没有进入记号表(symbol table)内。于是当运用此常量但获得一个编译错误时,该信息可能提到1.653而不是RATIO,这可能对调试产生很大的干扰。
解决之道是使用常量表达式来替换上述宏语句:
const double Ratio = 1.653;
作为语言常量,肯定会被编译器所看到,因此肯定会进入到记号表内。
class专属常量
#define并不重视作用域,因此无法利用#define来创建一个class专属常量,也不能提供任何封装性,即没有private #define这样的形式,而const成员变量当然是可以封装的:
class Cost{
private:
static const double FudgeFactor; //声明
};
const double Cost::FudgeFactor = 1.35; //定义
有的编译器不支持在class内部定义static的变量,但是有时在编译程序的时候需要给class一个常量值,例如在class中声明数组,这时可以使用枚举类型来充当int使用:
class GamePlayer{
private:
enum {NumTurns = 5};
int score[NumTurns];
};
使用inline替代#define
另一个常见的#define的误用是以它实现宏,宏看起来像是函数,但不会招致函数调用产生的额外开销,例如:
//以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将被累加两次
CALL_WITH_MAX(++a,b+10); //a将被累加一次
以上a的累加次数会受到b的值影响,这种不确定性就是麻烦的根源。
用template inline函数将带来宏可以达到的效率以及一般函数的所有可预料行为和类型安全性:
template <typename T>
inline void callWithMax(const T& a,const T& b){
f(a > b ? a : b);
}
这里不需要在函数本体中为参数加上括号,也不用担心参数被计算多次,此外这种写法将callWithMax表明为一个真正的函数,它将遵守作用域和访问规则。
有了const、enum和inline,对预处理器的需求降低了,但并非完全消除,只是对于单纯常量,最好以const对象或者enums替换#define;对于像函数一样的宏,最好使用inline来代替。
尽可能使用const
const允许指定一个语义约束(“一个不该被改动”的对象),编译器会强制执行这项约束。
const使用范围也很大,可以用在classes外部的global或是namespace作用域中修饰常量,也可以修饰文件、函数或是区块作用域中被声明为static的对象,还可以修饰classes内部的static和非static的成员变量。
对于指针来说,如果关键字出现在星号左边,表示被指向的是常量;如果出现在星号右边,表示指针本身是常量;如果出现在星号两边,表示两者都是常量。
关于STL中的迭代器来说,声明迭代器为const表示的是这个迭代器是常量,即不能指向别的东西,想声明迭代器所指向的东西为常量应该使用const_iterator:
vector<int> v;
...
const vector<int>::iterator iter = v.begin();
*iter = 10; //正确
iter++; //错误,迭代器是const的
const vector<int>::const_iterator iter2 = v.begin();
*iter2 = 10; //错误,所指向的是const
iter2++; //正确
const还可以子啊面对函数声明时使用,在一个函数声明式内,const可以和函数返回值、各参数、函数自身(如果是成员函数的话)产生关联。
令函数返回一个常量值可以降低意外操作带来的影响比如在if判断中做比较的时候少写一个等号,就变成赋值操作了。
const成员函数
const作用于成员函数的目的是为却确认该成员函数可作用于const对象身上。其重要性基于两点:
1.使得class接口比较容易理解。得知哪个函数可以改动对象而哪些不可以很重要;
2.使得“操作const对象”成为可能。
需要注意的是在const成员函数中是不能使用赋值操作给成员变量进行赋值的,如果想要给成员变量赋值,可以将成员变量声明为mutable的:
class CTextBlock{
public:
std::size_t length() const;
private:
char* pText;
std::size_t textLength;
mutable bool lengthIsValid;
};
std::size_t CTextBlock::length() const{
if(!lengthIsValid){
textLength = std::strlen(pText); //错误
lengthIsValid = true; //正确
}
return textLength;
}
另外一种令人头疼的问题是在一个cosnt成员函数中所需要做的操作较多,这样的话,相当于两种版本(const和非const)会拥有大量重复的代码,这伴随而来的是编译时间、维护、代码膨胀的问题。这促使我们将常量性移除。
类型转换其实是不好的想法,但是代码重复也不是什么令人愉快的经验。以下是两种版本的避免代码重复的安全做法–即使需要转型:
class TextBlock{
public:
...
const char& operator[](std::size_t postion) const{
...
...
... //很多操作
return text[position];
}
char& operator[](std::size_t postion) {
return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]);
}
...
};
代码中有两个转型操作,传入的参数转型为const,返回的值转型为非const。值得注意的是使用const版本调用一个非const版本是不应该做的,因为const函数是承诺绝不改变对象的逻辑状态的,而非const函数没有这样的承诺,如果在const函数中调用了非const函数,就是冒了这样的风险,反向调用(在非const函数中调用const函数)才是安全的。