Item 3:尽量使用常量Const
总述:使用常量好的的地方在于它定义了一个语义上的常量—一个不能修改的特别对象,并且编译器会强制它是常量。常量是多功能的,可以修饰全局变量和局部变量,同样能修饰静态static变量、函数、指针等。
Const With Point常量与指针
在学c/c++时候我们最头疼的就是区分一些概念和定义,比如常量指针,指针常量,常量指针常量...,下面我们来看这三者怎么区分的。
char greeting[]="hello";
char *p1=greeting; //non-const pointer,non-const date
const char *p2=greeting; //non-const pointer,const date
char * const p3=greeting; //const pointer,non-const date
const char * const p4=greeting; //const pointer,const date
p1是char型指针;p2是指针常量,即不能通过p2改变指针地址所对应的值;p3是常量指针,既不能改变p3地址,p4是常量指针常量,即不能改变指针地址也不能通过指针改变值。C++创始人Bjanre给出的记忆方法是从右往左读,const *p表示指针(‘*’)常量(“const”),
*const p表示常量指针。值得注意的是const char *p与char const *p是完全一样的,读者应该适应这两种写法。
STL iterator是一种广义指针,因此iterator表现得和指针T*一样。定义iterator const表示常量指针,即iterator不允许指向其他地方;定义const_iterator表示指针常量,即不允许iterator改变它对应地址的值。具体用法如下:
std::vector<int> vec;
...
const std::vector<int>::iterator iter=vec.begin();//iter act like T* const
*iter=10;//ok,changes what iter points to
++iter; //error!iter is const
std::vector<int>::const_iterator cIter=vec.begin();//cIter act like const T*
*cIter=10;//error! *cIter is const
++cIter; //ok,changes cIter
关于迭代器iterator的讨论见
Const With Function
const最常用的就是修饰函数,在函数声明中限定函数返回类型,参数乃至整个函数。
对函数参数进行const限定的好处在于函数内不会改变参数值,这一点对按值引用和指针参数特别有用。这一部分以一些反例来说明问题。
Example1 返回类型限定为const能避免错误
有如下声明:
class Rational{...};
const Rational operator *(const Rational &lhs,const Rational &rhs)
{...}
很多人都疑惑为什么要限定放回类型为const,其实如果不限定为const的话,就可能会发生错误。
你可能不会写如下代码
Rational a,b,c;
...
(a*b)=c;
但是你很有可能写如下代码
if(a*b=c)...
当然,你的本意是if(a*b==c),但由于笔误,很有可能就写成if(a*b=c),不过现代编译器会给出警告。良好的编程习惯是限定operator*的返回类型为const,这样const就能阻止你对operator*的返回结果赋值。尽量使用const吧,这样你就能避免把’==’写成’=’。
Example2 const成员函数都可以用于const对象和非const对象,而非const成员函数只能用于非const 对象。
class X{
int aa;
public:
void update(){a++;}
int value() const {return aa;}
void cheat() const {aa++}//error:aa is const
}
int g(X o1,const X & o2){
o1.update();//fine:非const对象调用非const成员函数}
o2.update();//error:const对象只能调用const成员函数
return o1.value()+o2.value();//fine:非const对象和const对象都能调用const成员函数
}
Example3 强制去掉const,又名const不一定表现得和const一样。Bjanre在《c++语言的设计与演化》中提到:C++关心的是检查偶然的错误,而不是防止刻意的欺骗;我们不认为编译系统有责任去防止程序员明确地做突破类型系统的事。
class cTextBlock{
public:
...
char & operator[](std::size_t position) const
{return pText[position];}
private:
char *pNext;
};
const cTextBlock cctb("Hello");
char *pc=&cctb[0];
*pc='J';//cctb now is "Jello"
上面的cctb为const类型,但是通过指征*pc改变了它的值,const表现出非const效果。
Example4 const可变性与强制的平衡-mutable关键字。const是强制使变量不可变,但是有些时候你又想要某些改变,这就是mutable关键字引入的原因。
class cTextBlock{
public:
...
std::size_t length() const
private:
char *pText;
std::size_t textLength;//mutable std::size_t textLength
bool lengthIsValid;//mutable bool lengthIsValid
};
std::size_t cTextBlock::length()const
{
if(!lengthIsValid){
textLength=std::strlen(pText);
lengthIsValid=true;
}
return textLength
}
我们知道length()const是不能改变类成员值的,所以上面代码是错误的,解决方法是把成员函数length()声明为非const或者是像注释写的把textlength和lenhgthIsValid声明为mutable.
Example5 强制类型转换const_cast
这一个看不明白,先放一放,以后写。