1 const和指针的关系
对于我这个c++初学者来说,指针搭配常量让人有点小头疼。在描述一些概念比如“指向const的const指针”更让人大脑一时转不过来。
看书中的例子:
char greeting[] = "HELLO"
char* p = greeting; //non-const pointer,non-const data
char* const p = greeting; //const pointer,non-const data
const char* p = greeting; //non-const pointer,const data
const char* const p = greeting; //const pointer,const data
记住书中总结的三句话可以理清const和指针的关系。
const 出现在*左边,表示被指物是常量 const char* p 等价于 char const *p
const 出现在*右边,表示指针自身是常量
const出现在*两边,被指物和指针都是常量
下面通过迭代器的例子来说明const和指针的关系:
std::vector<int> vec;
const std::vector<int>::iterator iter = vec.begin(); //等价于声明一个T* const指针 指针自身不可改变,被指物可以改动。
*iter = 10; //OK 改变被指物
++iter; //error iter是const
为了改变指针,可以采用const_iterator,如下:
const std::vector<int>::const_iterator cIter = vec.begin(); //等价于const T*
*cIter = 10 ;//error! 被指物是const
++cIter; //OK 改变指针自身
2 声明为const可以帮助编译器侦测出错误用法。const 可以被加到任何作用于内的对象,函数参数,返回值,成员函数本体。
看下面的例子:
const Rational operator*(const Rational& l,const Rational& r) //重载了operator* 的操作
调用时:
Rational a,b,c
if((a*b)= c) //为什么会有这种写法?其实本意是(a*b)== c)好不好! 使用const可以避免(a*b)被重新复制。
3 两个成员函数如果是常量性不同,可以被重载。
例:
class TextBlock{
public:
const char& operator[](std::size_t pos) const //①const object
{
return text[pos];
}
char& operator[](std::size_t pos) //②non-const object
{
return text[pos];
}
}
使用时:
TextBlock tb("hello"); // 匹配②non-const函数
cout << tb[0];
void print(const TextBlock& ctb) // 匹配①const函数
{
cout << ctb[0];
}
4 编译器强制实施bitwise constness,但是在编码时应使用概念上的常量性(conceptual constness)。
bitwise constness阵营的人相信:成员函数只有在不更改对象的任何成员变量(static除外)时才可以说是const。
也就是说const成员函数不可以更改任何non-const成员变量。
不幸的是:如果一个更改了“指向物”的成员函数不能算是bitwise,但是如果其指针属于对象,那么此函数为bitwise可以被编译器编译。
看下面的例子:
class CTextBlock{
public:
char& operator[](std:size_t position) const
{return pText[position];}
private:
char* pText; //指针属于对象的成员变量
};
该函数不适当地声明了一个const函数,而函数返回一个引用指向内部值。 由于operator[]实现代码并不更改pText,因此编译器认为是bitwise const。
但是以下操作终究还是可以改变它的值:
const cTextBlock cctb("hello");
char* pc = &cctb[0];
*pc ='J';
以上的情况编译器不会报错,但是终究还是改变了值。于是导出了logical constness,他们认为一个const成员函数可以修改它所处理的对象内的某些bits,但只在客户端侦测不出的情况下才得如此。
下面这个例子CTextBlock class实现高速缓存文本区块的长度以便应付询问:
class CTextBlock{
public:
std::size_t lenth() const;
private:
char* pText;
std::size_t textLenth; //最后一次计算的文本区块长度。non-const都可能被修改
bool lenthIsValid; //目前的长度是否有效。non-const都可能被修改
};
std::size_t CtextBlock::lenth() const;
{
if(!lenthIsValid){
textLenth = std::strlen(pText); //error! 在const成员函数中不能赋值给textLenth和lengthIsValid,解决方法:mutable
lenthIsValid = true;//error!
}
return textLenth;
}
上面的解决方法可以使用mutable(可变的),它能放掉non-static成员变量的bitwise constness约束。
class CTextBlock{
public:
std::size_t lenth() const;
private:
char* pText;
mutabe std::size_t textLenth; //最后一次计算的文本区块长度。non-const都可能被修改
mutabe bool lenthIsValid; //目前的长度是否有效。non-const都可能被修改
};
5 当const和non-const成员函数有着等值等价的实现时,令non-const版本调用const版本可以避免代码重复。
令non-const operator[]调用其const兄弟可以避免代码重复的安全做法如下:
class TextBlock{
public:
const char& operator[](std::size_t pos) const
{
。。。//各种等价check
return text[pos];
}
char& operator[](std::size_t pos)
{
return //调用const将op[],将返回值的const转除????
const_cast<char&>(
static_case<const TextBlock&>(*this)[pos]
);
}
}
说明:两次转型
(1)<const TextBlock&>(*this) 为了避免调用自己无穷递归,将*this从原始类型TextBlock& 转为const TextBlock& 。使用static_case进行强迫安全转型。
(2)从const operator[]的返回值中移除const
注:可以在non-const中调用const,但是反之从const中调用non-const存在不安全性,不提倡。