《Effective C++》是一本轻薄短小的高密度的“专家经验积累”。本系列就是对Effective C++进行通读:
const 语法虽然变化很多,但并不高深莫测,如果关键字 const 出现在星号左边,表示被指向的变量为常量,如果出现在星号右边,则表示指针本身是常量,因为指针也是变量嘛。关于 const 关键字的更多的知识可以参考C++ const关键字,这篇文章。
STL 迭代器系以指针为根据塑模出来,所以迭代器的作用就像个T*指针。声明迭代器为 const 就像声明指针为 const 一样。
std::vector vec;
...
const std::vector::iterator iter = vec.begin(); // iter就像个T* const
*iter = 10; //正确,改变iter所指物
++iter; //错误,iter是const
同理:
现在,你再回头看,就能够发现,咿,没错,就和const+指针用法是一样的。
const最具威力的用法是面对函数声明时的应用。在一个函数声明式内,const可以和函数返回值,各参数、函数自身(如果是成员函数)相关联。
令函数返回一个常量值,往往可以降低因客户错误而造成的意外,而又不至于放弃安全性和高效性。
举个例子,下面是一个有理数(详情见条款24)的operator*声明式:
class Rational{...};
const Rational operator*(const Rational& lhs, const Rational& rhs);
返回一个 const 类型可以防止以下的错误情况发生:
① 一个有理数的乘积是不可能改变的,因此赋值操作也没有意义,所以我们将operator*的返回值设置为const:
Rational a,b,c;
...
(a*b)=c;//将a*b的结果重新赋值了一个值
对于 const 参数,没有特别新颖的概念,你应该在必要使用它们的时候使用它们,除非你需要改动参数。
将const实施在成员函数的目的,是为了确认该成员函数可作用于 const 对象身上。这一类成员函数之所以重,基于两个理由:
①它们使class接口比较容易理解,因为可以得知哪个函数可以改动对象而哪个函数不行,很重要。
②它们使**“操作const对象”成为可能。这对编写高效代码是个关键(见条款20,改善C++程序效率的一个根本办法是以const引用**方式传递对象,而这个技术的前提是,我们有const成员函数可用来处理const对象)
需要注意到的是,两个成员函数如果只是常量性不同可以被重载。即,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[] 并对不同版本的返回类型就可以令 const 和 non-const 获取不同处理:
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;
};
logical constness(逻辑常量)
logical constness阵营的人认为,const虽然不可以在const函数中改变成员变量(static除外)的值,但是允许在某些不知情的情况下改变了类中成员变量的值。
例如上面CTextBlock类的的operator[]函数内部虽然没有改变成员变量的值,但是可以通过其返回值进行更改
CTextBlock cctb("Hello");char *p= cctb[0]; //取得其返回值
p = 'J'; //改变了
到这里,你就可以对bitwise constness与logical constness的观点了解的很清晰了,前者就是站在不更改对象内的任何一个bit一方的,这种论点只需编译器寻找成员变量的赋值动作就能被反驳,后者则是站在能够修改对象内的某些bits一方的。
上面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;
}
对于"bitwise constness"非我所欲的问题,mutable是个解决办法,但它并不能解决所有 const 难题。举个例子:
改变上面的TextBlock类,实际代码中operator[]中不仅仅返回一个字符的引用,还可能在之前执行边界检测、志记数据访问、检验数据完整性等,因此代码量会增加。
可以看到这样的代码比较臃肿,当然你也可以把执行边界检测、志记数据访问、检验数据完整性等操作封装于一个private成员函数中,然后使用operator[]调用这个函数,但你依旧重复了一些代码,如函数调用,两次 return 语句等。
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[]的机能一次并使用它两次(将两种类型的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( //将operator[]返回值中的const移除static_cast(*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版本可避免代码重复。
期待大家和我交流,留言或者私信,一起学习,一起进步!