条款03:尽可能使用 const
const允许你指定一个语义约束,也就是指定一个“不该被改动”的对象。
const如果出现在星号左边,表示被指物是常量,如果出现在星号右边,表示指针自身是常量:如果出现在星号两边,表示被指物和指针都是常量。
如果被指物是常量,有些程序会将关键字const写在类型之前,有些人会把它写在类型之后,星号之前。两种写法意义相同:
void f1(const widget* pw) void f2(widget const* pw) //f2获得一个指针指向一个常量widget对象f1也是
对于迭代器也是一样:
std::vector<int>vec const std::vector<int>::iterator iter = vec.begin(); *iter = 10 ; //没问题,改变的是iter所指之物 ++iter ; //错误,const不可被改变 std::vector<int>const_iterator citer = vec.begin(); *citer = 10; //错误,const 不可被改变 ++citer; //OK
const威力最大的地方是面对函数的应用,在一个函数声明内,const可以和函数返回值,各参数,函数自身,产生关联。
返回一个const会增加程序的安全性和高效性。
class Rational {....}; const Rational operator* (const Rational& lhs, const Rational& rhs);
上面的函数要求返回一个const对象,可以使如下代码无法通过编译:
Rational a,b,c; if(a * b = c) { .... }
也许只是单纯的书写错误,不易察觉,但是可以用const检测出来。
至于const参数,和local const 一样,你应该在必要使用的时候使用它们,除非你有需要改动参数或者local对象,否则请将它声明为const.
const 成员函数
将const实施于成员函数的目的,是为了确认该成员函数可作用于const对象身上。它们使得class接口比较容易理解。并且使操作const对象成为可能。改善C++程序效率的一个根本办法是以pass by reference-to-const方式传递对象。而此技术的前提是我们有const成员函数可以用来处理取得的const对象。
class TextBlock { public: ... const char& operator[](std::size_t position) const {return text[position];} //operator[] for const object char& operator[]{std::size_t position}{return text[position];} //operator[] for non-const object private: std::string text; };
TextBlock 的operator[]可被这么使用:
TextBlock tb("Hello"); std::cout << tb[0]; //调用non-const TextBlock::operator[] const TextBlock ctb("World"); std::cout << ctb[0];
或者
void print(const TextBlock& ctb) { std::cout << ctb[0]; ... }
所以:
std::cout << tb[0]; //OK tb[0] = 'x'; //OK std::cout <<ctb[0]; //OK ctb[0] = 'x'; //ERROR ,write a const TextBlock
错误起因于企图对一个“由const 版之operator[]返回”的const char& 施行赋值操作。
也请注意,non-const operator[]的返回类型是个reference to char ,不是char,如果operator[]只是返回一个char,下面的句子就无法通过编译;
tb[0] = 'c';
因为如果函数的返回类型是个内置类型,那么改动函数返回值从来就是不合法的。(仔细想想,我从来没遇到过修改返回值的情况。。。)。
BitWise const:成员函数只有在不更改任何成员变量(static 除外)的时候,才能说是const.正是C++对常量的定义。但是:
class CTextBlock { public: ... char& operator[](std::size_t position) const {return pText[position];} private: char* pText; };
这个class 不适当的将operator声明为const函数,而该函数返回一个reference指向对象内部值。operator[]实现代码并不更改pText,于是编译器可以通过,并认为它是bitwise。但是:
const CTextBlock cctb("Hello"); char* pc = &cctb[0]; //调用const operator[]取得一个指针,指向cctb的数据 *pc = 'J'; //cctb现在是Jello
因为更改的是指向的数据而不是指针本身,所以是可以通过编译的。
这种情况导出了logical constness:可以修改const成员函数内的某一些bits,但只有在客户端侦测不出的情况下才如此:
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 } return textLength; }
使用 mutable释放掉non-static成员变量的bitwise constness约束:
mutable std::size textLenghth; mutable lengthisValid;
在cosnt 和non-const成员函数中避免重复
将某些东西声明为const可帮助编译器侦测出错误用法,const可被施加于任何作用域内的对象,函数参数,函数返回类型,成员函数。
编译器强制实施bitwise constness,但你编写程序时应该使用“概念上的常量性”
当const和non-const 成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复(但可能出现。。。两次类型转换)