条款三
--17/1/16
这个条款主要是介绍了const在c++的各个应用,看完之后真的是受益匪浅。
(1)首先const的修饰可以确保指针或者指向的东西是不是const,和我们在条款二看到的常量指针和指针常量类似,一下是具体定义:
T * const:const pointer,non-const data 也就是我们所说的指针常量;
const T *:non-const pointer,const data 也就是我们所说的常量指针;
值得注意的一点是写在类型前和类型后是一样的,也就是const T* p和T const*p是一样的,都是常量指针,T是不变的对象。
在STL里面的指针也有类似的形式:
a.为迭代器声明指针,指的是T*const,也就是不允许改变指针指向的地址,写法如下:
const std::map<int,int>::iterator it = mp.begin();
b.希望仅是迭代来遍历想要的内容,不允许修改具体容器里面的值,也就是const T*这时候需要的是STL提供const_iterator,写法如下:
std::map<int,int>::const_iterator it = mp.begin();
以上就是const在指针和迭代器的一个重要的应用。
(2)const在函数声明的应用也是应用广泛并且要提高注意的地方。令函数返回一个常量值可以提高安全性和高效性。比如这样的句子是会通过编译的:
1 class R { 2 public: 3 R() {} 4 }; 5 6 R operator * (const R& l, const R& r) { 7 R t; 8 return t; 9 } 10 11 int main() { 12 R a, b, c; 13 (a * b) = c; 14 return 0; 15 }
虽然这样写脑洞大了点,但是这是一个不可被忽视的问题,将函数声明为const能够增加安全性同时也可以被编译器捕捉这样的“无聊之举”?
(3)const成员函数中的应用是本条款花费大篇来通说的内容,我们希望这个成员函数不可修改类的相关值,我们会为这个函数加上const的字样。可以依据const与否来进行重载,const成员调用const函数,non-const调用non-const版,这个在Essential c++也提到过相关知识。
这时候作者就讨论两个派别的const成员函数:
1)bitwise constness,要求每一位的是constness,也就是const函数里面不允许有任何的修改操作,所以编译器着重检查的是类似赋值的操作。
下面这段代码却不是真正意义上的bitwise constness
1 class R { 2 3 public: 4 5 R(const char *p) { 6 7 str = new char[strlen(p) + 1]; 8 9 strcpy(str, p); 10 11 } 12 13 char& operator [] (int _index) const { 14 15 return str[_index]; 16 17 } 18 19 void print() const { 20 21 printf("%s\n", str); 22 23 } 24 25 private: 26 27 char *str; 28 29 }; 30 31 32 33 int main() { 34 35 R r("zhenhao"); 36 37 r.print();//输出 zhenhao 38 39 char *p = &r[0]; 40 41 *p = ‘h‘; 42 43 r.print();//输出 hhenhao 44 45 return 0; 46 47 }
2)因此第二种就是logical constness一个const成员函数是可以修改里面的相关变量的,也就是逻辑上是“不可变的”就好了。这个问题在Essential C++里面就特别讨论过,使用mutable关键字将你想要放在const成员函数里面的变量可以变,而不是所有变量都不能变。
最后作者做了一个总结我觉得不错:编译器严格实行bitwise constness,但是写程序是应该遵守的是logical constness。
(4)巧妙使用转换避免const和non-const的重复,当我们实现两种版本的函数时,如果实现机制没有什么区别,那么可以利用转换来避免这个重复。这个避免的实现在non-const的成员函数内,将non-const成员转换成const(安全的),然后调用相应的const函数之后返回的值利用const_cast来进行转换回来即可。注意强调的是不可以将const转换成non-const,因为这不仅在逻辑上不成立,而且实现起来也麻烦。