关键字const多才多艺。你可以用它在classes外部修饰global或namespace作用域中的常量,或修饰文件、函数或区块作用域中被声明static的对象。你也可以修饰classes内部的static和non-static成员变量。面对指针你也可以指出自身、指针所指物或两者都(或不)是const。
char* greeting = "Hello";
char* p = greeting; //non-const pointer,non-const data
const char* p = greeting; //non-const pointer,const data
char* const p = greeting; //const pointer,non-const data
const char* const p = greeting;//const pointer,const data
规则:如果const出现在星号的左边,表示被指物是常量;如果const出现在星号右边,表示指针本身是常量。
STL迭代器系以指针为根据模塑的,所以迭代器的作用就像一个T*指针。声明迭代器为const就像声明指针为const一样(即声明一个T* const指针),表示指针本身为const;如果你希望迭代器所指向的内容不被改变(即希望const T*指针),你就需要一个const_iterator:
std::vector<int> vec;
...
const std::vector<int>::iterator iter = vec.begin(); //iter就像一个T* const
*iter = 10; //ok
++iter; //错误
std::vector<int>::const_iterator cIter = vec.begin(); //cIter就像一个const T*
*cIter = 10; //错误
++iter;
const最具威力的用法是面对函数声明时的应用。在一个函数声明里内,const可以用于函数返回值、各参数、函数自身。
令函数返回一个常量,可以防止客户错误的操作临时变量,又不至于放弃安全性和效率。
const成员函数
将const用于成员函数的目的:第一,为了确认哪些该成员函数改动对象内容哪些函数不行;第二,它们使操作const对象成为可能,编写高效率的代码关键是pass by reference-to-const方式传递对象。
对于两个成员函数如果只有常量性不同,可以被重载。
成员函数如果是constI意味着两个流行概念:bitwise constness(又称physical constness)和logical constness。
bitwise const
成员函数只有在不更改对象的的任何成员变量(static除外)才可以说是const。不幸的是许多成员函数虽然不十足具备const性质却能通过bitwise测试,具体的说:一个更改了“指针所指物”的成员函数不能算是const,但如果只有指针(而非其所指物)属于对象,那么称此函数为bitwise const通过编译。假如如下一个TextBlock class:
class TextBlock
{
public:
...
char& operator[](std::size_t position) const //bitwise const声明,但其实不适当
{
return m_text[position];
}
private:
char* pText;
};
这个class的operator[]声明为const,但是返回却是reference。
const TextBlock ctb("Hello"); //声明const对象
char* pc = &ctb[0]; //调用const operator[]取得一个指针,指向ctb的数据
*pc = 'J'; //ctb现在变成了"Jello"
这代码没有错:你创建了一个常量对象并设定某值,而且只调用了const成员函数,但你终究还是改变了它的值。
logical constness
这派主张:一个const成员函数可以修改它所处理的对象某些bits,只有在客户端侦测不出来的情况下可以如此。
例如你有个CTextBlock class有可能高速缓存文本区块的长度以便应付询问:
class CTextBlock
{
public:
//...
std::size_t length() const;
private:
char* pText;
std::size_t textLength; //最近一次计算的文本区块长度
bool lengthIsValid; //目前是否有效
};
std::size_t CTextBlock::length() const
{
if(!lengthIsValid)
{
textLength = std::strlen(pText); //错误,在const成员函数中不能
lengthIsValid = true; //赋值给textLength和lengthIsValid
}
return textLength;
}
length的实现不是bitwise constness,因为textLenghth和lengthIsValid都被改变。解决的方法是:将变量加上mutable修饰符。
在const和non-const成员函数中避免重复
对于bitwise-constness不是我们想要的,可以用mutable解决,但它不能解决所有的const相关的难题。对于重载的的operator[]操作符不止单纯的返回一个reference指向某个字符,甚至执行边界检查、日志访问信息,还有其他内容等等。把所有的同时放入const和non-const operator[]中,导致代码重复,编译时间、维护和代码膨胀。
虽然类型转换是一个糟糕的方法,然而代码重复也不会令人愉快。我们可以用non-const operator[]直接复用const operator[]的代码
const char& operator[](std::size_t position) const
{
//...边界检查
//...日志访问
//...检查数据完整性
return m_text[position];
}
char& operator[](std::size_t position)
{
return const_cast<char&> //将op[]返回值的const移除
(static_cast<const TextBlock&>(*this)[position]); //为*this加上const调用const op[]
}
上述使用了两次转换动作:第一次用*this添加const(用于调用const operator[]版本,否则调用自身),第二次从const operator[]返回中移除const。
总结:
- 将某些东西声明为const可帮助编译器侦测错误的用法。const可以用于任何作用域的对象、函数参数、函数返回类型、成员函数体。
- 编译器强制施行了bitwise constness,但你编写程序时应该使用“概念上的常量性”。
- 当const和non-const成员函数有着等价的实现时,令non-const版本调用const版本复用代码。