条款03:尽量使用 const
char greeting[] = "hello";
char *p = greeting; // non-const pointer,non-const data
const char *p = greeting; // non-const pointer,const data
char * const p = greeting; // const pointer, non-const data
const char * const p = greeting; // const pointer, const data
STL迭代器以指针为基础塑模出来,所以迭代器的作用就像 T * 指针.声明迭代器为 const 就像声明指针为 const 一样(即声明一个 T *const 指针),表示这个迭代器不得指向不同的对象,但它指向的对象的值是可以改变的. 如果希望迭代器所指的对象不可被改动(即 const T *指针),需要的是 const_iterator:
std::vector<int> vec;
...
const std::vector<int>::iterator iter = vec.begin(); // iter的作用像个T *const
*iter = 10; // ok. 改变iter所指对象
++iter; // error. iter是const
std::vector<int>::const_iterator cIter = vec.begin(); // cIter的作用像个const T *
*cIter = 10; // error. *cIter是const
++cIter; // ok. 改变cIter
const 最具威力的用法是面对函数对象时的应用.在一个函数声明式内,const 可以和函数返回值,各参数,函数本身产生关联.
class Rational { ... };
const Rational operator *(const Rational &lhs, const Rational &rhs);
为什么返回一个 const 对象?原因是如果不这样客户就能实现这样的暴行:
Rational a, b, c;
(a * b) = c;
许多程序员会无意间这样做,只因为单纯的打字错误:
if (a * b = c) // 实际上是==
如果a和b是内置类型,这样代码当然不合法.而一个"良好的用户自定义类型"的特征就是它们避免无端地与内置类型不兼容. 将operator *的返回值声明为 const ,可以预防那个"没有意义的赋值动作".
class TextBlock {
public:
const char & operator[](std::size_t position) const {
return text[position];
}
char & operator[](std::size_t position) {
return text[position];
}
private:
std::string text;
};
TextBlock的operator[]可以这样使用:
TextBlock tb("hello");
std::cout << tb[0];
const TextBlock ctb("world");
std::cout << ctb[0];
真实程序中 const 对象大多用于pass by pointer-to-const 或 pass by reference-to-const 的传递结果.上述的ctb例子太造作,下面这个比较真实:
void print(const TextBlock &ctb) {
std::cout << ctb[0];
}
只要重载operator[]并对不同版本给予不同的返回类型,就可以令 const 和non-const TextBlock获得不同的处理:
tb[0] = 'x'; // ok
ctb[0] = 'x'; // error
那是因为,如果函数的返回类型是个内置类型,那么改动函数返回值从来就不合法.纵然合法,C++以by value返回对象这一事实意味着改动的其实是tb.text[0]的一个副本,不是tb.text[0]自身.
class CTextBlock {
public:
char& operator[](std::size_t position) const {
return pText[position];
}
private:
char *pText;
};
const CTextBlock cctb("hello");
char *pc = &cctb[0];
*pc = 'J';
创建一个常量对象并设以某值,而且只对它调用 const 成员函数.但是终究改变了它的值.
这种情况导出所谓的logical constness.他们认为:一个 const 成员函数可以修改它所处理的对象内的某些bits,但只有在客户端侦测不出的情况下才得如此.例如CTextBlock class 有可能高速缓存(cache)文本区块的长度以便应付询问:
class CTextBlock {
public:
std::size_t length() const;
private:
char *pText;
std::size_t textLength;
bool lengthIsValid;
};
std::size_t CTextBlock::length() const {
if (!lengthIsValid) {
textLength = std::strlen(pText); // error!
lengthIsValid = true; // error!
} // const成员函数内不能给成员变量赋值
return textLength;
}
textLength和lengthIsValid的修改对 const CTextBlock对象而言是可接受的,但是编译器不同意.
解决的办法很简单: 利用C++的一个与 const 相关的摆动场:mutable(可变的).mutable 释放掉non-static 成员变量的bitwise constness约束:
class CTextBlock {
public:
std::size_t length() const;
private:
char *pText;
mutable std::size_t textLength; // 这些成员变量可能会被更改
mutable bool lengthIsValid; // 即使在const成员函数内
};
std::size_t CTextBlock::length() const {
if (!lengthIsValid) {
textLength = std::strlen(pText); // ok
lengthIsValid = true; // ok
}
return textLength;
}