-
如果通过const的位置判断const
- STL的迭代器就是一个很好介绍const的例子
三、const在函数返回值、参数中的应用
- ①如果想希望可以通过迭代器修改值,并且改动迭代器的位置,那么可以使用iterator类型的迭代器
- ②如果我们希望可以通过迭代器修改值,但是希望迭代器指针不能进行移动。那么可以在"iterator"迭代器类型的前面加上一个const
- ③如果我们希望通过迭代器获取值但是不能改变值,并且迭代器指针可以进行移动,那么可以使用“const_iterator”类型的迭代器
- ④如果我们既不希望通过迭代器改变值,迭代器指针也不能进行移动,那么可以在“const_iterator”前面加上const
std::vector<int> vec; //iter1类型为int* std::vector<int>::iterator iter1 = vec.begin(); *iter1 = 1; //正确 iter1++; //正确 //iter2类型为int* const const std::vector<int>::iterator iter2 = vec.begin(); *iter2 = 1; //正确 iter2++; //错误 //iter3类型为const int* std::vector<int>::const_iterator iter3 = vec.cbegin(); *iter3 = 1; //错误 iter3++; //正确 //iter4类型为const int* const const std::vector<int>::const_iterator iter4 = vec.cbegin(); *iter4 = 1; //错误 iter4++; //错误
在返回值的应用
- 令函数返回一个常量值,往往可以降低因客户错误而造成的意外
- 举个例子,下面是一个有理数的operator*声明式:
class Rational{}; const Rational operator*(const Rational& lhs, const Rational& rhs);
- 返回一个const类型可以防止以下的错误情况发生,一个有理数的乘积是不可能改变的,因此赋值操作也没有意义,所以我们将operator*的返回值设置为const:
Rational a,b,c; //do something (a*b)=c;//将a*b的结果重新赋值了一个值
- 在参数中的应用:同理,在上面的operator*中,我们将两个有理数进行相乘,但是乘数本身不会改变,因此将参数设置为const
- 使用const成员函数的两个理由:
- ①它们使class接口比较容易理解,因为可以得知哪个函数可以改动对象而哪个函数不行
- ②它们可以“操作const对象”。这对编写高效代码是个关键(见条款20,改善C++程序效率的一个根本办法是以const引用方式传递对象,而这个技术的前提是,我们有const成员函数可用来处理const对象)
五、bitwise constness、logical constnessconst成员函数可以被重载
- const成员函数与非const成员函数可以构成重载
- 例如下面的class被设计用来表现一大块文字:
class textBlock { public: char& operator[](std::size_t position){ return text[position]; } const char& operator[](std::size_t position)const { return text[position]; } private: std::string text; };
- 那么根据对象类型的不同,对调用不同的operator[]函数
textBlock tb("Hello"); std::cout << tb[0]; //调用非const版本的operator[] tb[0] = 'x'; //正确 const textBlock ctb("World"); std::cout << ctb[0]; //调用const版本的operator[] ctb[0] = 'y'; //错误
- const对象大多被用于常量指针形式或常量引用的形式传递给函数,因此也可以作为一个函数传参的例子:
void print(const textBlock& ctb) { std::cout << ctb[0]; //调用const版本的operator[] }
- const成员函数的使用由两种流行的概念:bitwise constness(又称physical constness)和logical constness
bitwise constness(位常量)
- bitwise constness阵营的人认为,成员函数只有在不更改对象任何成员变量(static除外)时才可以说是const
class CTextBlock { public: //没有改变成员变量的值 char& operator[](std::size_t position)const { return pText[position]; } private: char *pText; };
六、mutable关键字logical constness(逻辑常量)
- logical constness阵营的人认为,const虽然不可以在const函数中改变成员变量(static除外)的值,但是允许在某些不知情的情况下改变了类中成员变量的值
- 例如上面CTextBlock类的的operator[]函数内部虽然没有改变成员变量的值,但是可以通过其返回值进行更改
CTextBlock cctb("Hello"); char *p= cctb[0]; //取得其返回值 p = 'J'; //改变了
- 上面bitwise constness思想导致不能在const成员函数内更改任何成员变量(static除外)的值。但是有些成员变量时可以改变的,那么可以使用mutable关键字
- 例如下面的length const函数,我们可以在其中更改两个成员变量的值
class CTextBlock
{
public:
std::size_t length()const;
private:
char *pText;
mutable std::size_t textLength;
mutable bool lengthIsValid;
};
std::size_t CTextBlock::length()const
{
if (!lengthIsValid) {
textLength = std::strlen(pText);
lengthIsValid = true;
}
return textLength;
}
七、在const和non-const成员函数中避免重复
先来看看代码重复的class样例
- 改变上面的TextBlock类,实际代码中operator[]中不仅仅返回一个字符的引用,还可能在之前执行边界检测、志记数据访问、检验数据完整性等,因此代码量会增加
- 可以看到这样的代码比较臃肿,当然你也可以把执行边界检测、志记数据访问、检验数据完整性等操作封装于一个private成员函数中,然后使用operator[]调用这个函数,但是代码还是重复了
class TextBlock { public: const char& operator[](std::size_t position)const { //...边界检测 //...志记数据访问 //...检验数据完整性 return text[position]; } char& operator[](std::size_t position) { //...边界检测 //...志记数据访问 //...检验数据完整性 return text[position]; } private: std::string text; };
- 现在要做的就是:将两种类型的operator[]封装为一种。也就是说,你必须令其中一个调用另一个。这促使我们将常量性移除
代码转型缩减
- 下面我们将non-const成员函数中的代码更改了
class TextBlock { public: const char& operator[](std::size_t position)const { //...边界检测 //...志记数据访问 //...检验数据完整性 return text[position]; } char& operator[](std::size_t position) { return const_cast<char&>( //将operator[]返回值中的const移除 static_cast<const TextBlock&>(*this) //为*this加上const [position] //调用const operator[] ); } private: std::string text; };
- 两次类型转换:
- 第一次:static_cast,我们在non-const operator[]中调用const operator[],但是non-const operator[]若只是单纯调用operator[],会递归调用自己(无限循环),因此,我们需要明确指出调用的是const operator[],这里使用static_cast将*this将TextBlock&换换位const TextBlock&类型
- 第二次:const_cast,从const operator[]的返回值中移除const
- 虽然这样的语法比较奇特,不容易理解,但是对于“避免代码重复”来说,这样的损失是值得的
- 为什么不在const成员函数中调用non-const版本:因为编译器不允许,在const成员函数中是不允许调用non-const成员函数的
- ①将某些东西声明为const课帮助编译器侦测处错误用法。const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体
- ②编译器强制实施bitwise constness,但你编写程序时应该使用“概念上的常量性”
- ③当const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复