0.概述
- 将某些东西声明为const可帮助编译器侦测出错误用法。const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体。
- 编译器强制实施bitwise constness,但编写程序时应该使用“概念上的常量性”( conceptual constness) .
- const和 non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复。
1.const简介
参考概述第一条。
修饰指针和指针所指物
- 如果关键字const 出现在星号左边,表示被指物是常量(有些程序员会将关键字const写在类型之前,有些人会把它写在类型之后、星号之前。两种写法的意义相同);
- 如果出现在星号右边,表示指针自身是常量;
- 如果出现在星号两边,表示被指物和指针两者都是常量。
STL迭代器
STL迭代器的作用就像个T*指针。声明迭代器为const就像声明指针为const一样(即声明一个T* const 指针),表示这个迭代器不得指向不同的东西,但它所指的东西的值是可以改动的。
如果希望迭代器所指的东西不可被改动(即希望STL模拟一个const T*指针),需要const_iterator:
std::vector<int> vec;
...
const std::vector<int>::iterator iter=vec.begin(); //iter类比T*const
*iter=10; //正确
++iter; //错误
std::vector<int>::const_iterator cIter=vec.begin(); //cIter类比const T*
*cIter=10; //错误
++cIter; //正确
函数声明:
在函数声明中,const可以指向函数的返回值;也可以指向各个形参;对于成员函数,还可以指向整个函数:
- 令函数返回常量,可以降低因客户错误而造成的意外。
- 对于const参数,除非有需要改动参数,否则都声明为const。
- 下面详细展开const成员函数:
2.const成员函数
将const作用于成员函数上的目的是确定哪些成员函数可以作用于const对象上。这样做的意义有二:
- 使class接口容易被理解(哪个函数可以改动对象内容,哪个函数不可)
- 使操作const对象成为可能,有助于编写高效代码(改善C++代码效率的一个根本办法是以pass by reference-to-const的方式传递对象,而这样做的前提是有const成员函数来处理取得的const对象)
考虑下面的例子:
class TextBlock {
public:
...
const char& operator[](std::size_t position) const // operator[] for
{ return text[position]; } // const objects
char& operator[](std::size_t position) // operator[] for
{ return text[position]; } // non-const objects
private:
std::string text;
};
调用示例:
std::cout << tb[0]; // fine — reading a non-const TextBlock
tb[0] = ’x’; // fine — writing a non-const TextBlock
std::cout << ctb[0]; // fine — reading a const TextBlock
ctb[0] = ’x’; // error! — writing a const TextBlock
上述报错只因operator[]返回类型所致,operator[]调用动作本身没有问题。错误来源于试图对一个由const版本的operator[]返回的const char&进行赋值。
- bitwise constness:成员函数只有在不更改对象之任何成员变量( static除外)时才可以说是const。也就是说它不更改对象内的任何一个bit。
- logical constness:一个const成员函数可以修改它所处理的对象内的某些 bits,但只有在客户端侦测不出的情况下才得如此。
例:一个高速缓存文本区块的长度以便应付询问的类
class CTextBlock {
public:
...
std::size_t length() const;
private:
char *pText;
std::size_t textLength; // last calculated length of textblock
bool lengthIsValid; // whether length is currently valid
};
std::size_t CTextBlock::length() const
{
if (!lengthIsValid) {
textLength = std::strlen(pText); // error! can’t assign to textLength
lengthIsValid = true; // and lengthIsValid in a const
} // member function
return textLength;
};
length的实现当然不是 bitwise const,因为textLength和 lengthIsvalid都可能被修改。如果编译器坚持 bitwise constness怎么办?
解决方法:利用mutable释放掉non-static成员变量的bitwise constness约束。
class CTextBlock {
public:
...
std::size_t length() const;
private:
char *pText;
mutable std::size_t textLength; // 这些成员变量总是可以被更改
mutable bool lengthIsValid; // 即使是在const成员函数内
};
std::size_t CTextBlock::length() const
{
if (!lengthIsValid) {
textLength = std::strlen(pText); // 可
lengthIsValid = true; // 可
} // member function
return textLength;
};
3.在const和non-const成员函数中避免重复
如果成员函数执行很多操作,分别写const和non-const版本会造成大量的代码重复及伴随的编译时间、维护、代码膨胀等问题。
一种解决方案是将这些重复的代码移入另一个通常是private的成员函数并令const和non-const两个版本的函数调用它。但这样还是重复了一些代码:函数调用、两次return语句等。、
比较好的解决方案是实现operator[]函数的机能并使用它两次(一个调用另一个,非常量调用常量+转型操作),反之不行!
class TextBlock {
public:
...
const char& operator[](std::size_t position) const // same as before
{
...
...
...
return text[position];
}
char& operator[](std::size_t position)
{
return const_cast<char&>( //转除const
static_cast<const TextBlock&>(*this) //为*this加上const
[position];
);
}
};