条款03:尽可能使用const
-
const修饰变量。
场景:如果变量本身不应该被修改,应该使用const修饰。这样编译器可以进行保护,确保这个变量不会被修改。
- 关键字const出现在星号左边,表示被指物是常量。
- 关键字const出现在星号右边,表示指针自身是常量。
-
const修饰函数。
-
修饰参数时,和修饰一般变量相同。
-
修饰返回值,可以降低因客户错误而造成的意外。
A a, b, c; // 类类型 ... if (a * b = c) {//其实是想做一个比较动作,使用const修饰返回值可以避免这种错误, a*b的返回值是const, 变量c不能赋值给a*b。 ... }
若a和b都是内置类型,编译器会直接报错。而一个“良好的用户自定义类型”的特征是他们避免与内置类型不兼容。因此对operator*的定义应该如下:
const A operator*(const A& a, const A& b);
-
-
const修饰成员函数。
const修饰成员函数有2个好处:
-
可读性:使得接口容易被理解,一眼就可以看出这个接口能否修改对象。
-
const修饰的成员函数可以作用于const对象。
class Array { public: char& operator[](size_t pos) const //bitwise constness声明 { return a[pos]; } //但其实不恰当 private: char* a; }; const Array arr("Hello"); //声明一个常量对象 char *pc = &arr[0]; //调用const operator[]取得一个指针,指向arr的数据 *pc = 'J'; //arr现在有了“Jello”的内容。
还有一种logical constness:一个const成员函数可以修改它所处理的对象内的某些bits,但只有在客户端侦测不出的情况下才行:
class Array { public: size_t length() const; private: char *p; size_t length; //长度 bool lenIsVal; //长度是否有效 }; size_t Array::length() const { if (!lenIsVal) { length = strlen(p); //错误!在const成员函数内不能修改对象 lenIsVal = true; //错误!在const成员函数内不能修改对象 } return length; }
但是,C++对常量性的定义是bitwise constness的,所以这样的操作非法。
解决办法是使用mutable:
class Array { public: size_t length() const; private: char *p; size_t count; // 计数 mutable size_t length; // length总是会被更改,即使在const成员函数内 mutable bool lenIsVal; // lenIsVal总是会被更改,即使在const成员函数内 }; size_t CTextBlock::length() const { if (!lenIsVal) { length = strlen(p); //length可以在任何成员函数中修改,即使是const成员函数 lenIsVal = true; //lenIsVal可以在任何成员函数中修改,即使是const成员函数 count ++ ; //错误!const成员函数内,不能修改对象。 } return length; }
总的来说,上面提到了2种“修改”const成员函数中修改对象(修改const对象)的方法
最后,const和non-const版本的函数可能含有重复的代码,如果抽离出来单独成为一个成员函数还是有重复。如果希望去重,可以使用“运用const成员函数实现出其non-const孪生兄弟”的技术:class Array { public: const char& operator[](size_t pos) const { ... } char& operator[](size_t pos) { return const_cast<char&>( //移除返回值的const属性 static_cast<const CTextBlock&>(*this) //强转对象为const对象 [pos] //调用op[] ); } };
static_cast 相当于传统的C语言里的强制转换,该运算符把expression转换为new_type类型,用来强迫隐式转换,例如non-const对象转为const对象,编译时检查,用于非多态的转换,可以转换指针及其他,但没有运行时类型检查来保证转换的安全性。它主要有如下几种用法:
①用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。
进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;
进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的。
②用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
③把空指针转换成目标类型的空指针。
④把任何类型的表达式转换成void类型。
注意:static_cast不能转换掉expression的const、volatile、或者__unaligned属性。
-
注意:本文参考的是《effective c++》