55

条款03: 尽可能使用 const

const是个奇妙且非比寻常的东西。在指针和迭代器上;在指针、迭代器、references指涉的对象上;在函数参数和返回类型上;在local 变量上,在成员函数身上;

一. const 是C++中的对于变量语义约束(不可修改),编译器会强制实行这项约束,只要该值不可被改变(事实),就应该去进行约束

#include<vector>
const int val1=5;
static int val2=5;
const int *pointer1 =&val1; 	//底层 const 通过pointer1 访问指向的元素可读不可写
int* const pointer2 =&val2;		//顶层 const 不可改变pointer1 指向

vector<int> vec;
vector<int>::iterator iter1=vec.begin();		//上层 const 不可改变迭代器 指向
vector<int>::const_iterator iter2=vec.begin();	//顶层 const 通过迭代器访问元素可读不可写
什么是顶层const?什么是底层const?
1 、指向常量的指针:代表不能改变其指向内容的指针。声明时const可以放在类型名前后都可,拿int类型来说,声明时:const int和int const 是等价的。声明指向常量的指针也就是底层const,下面举一个例子:

int num_a = 1;
int const  *p_a = &num_a; //底层const
//*p_a = 2;  //错误,指向“常量”的指针不能改变所指的对象

注意:指向“常量”的指针不代表它所指向的内容一定是常量,只是代表不能通过解引用符(操作符*)来改变它所指向的内容。上例中指针p_a指向的内容就不是常量,可以通过赋值语句:num_a=2;  来改变它所指向的内容。

2 、指针常量:代表指针本身是常量,声明时必须初始化,之后它存储的地址值就不能再改变。声明时const必须放在指针符号*后面,即:*const 。声明常量指针就是顶层const,下面举一个例子:

int num_b = 2;
int *const p_b = &num_b; //顶层const
//p_b = &num_a;  //错误,常量指针不能改变存储的地址值
其实顶层const和底层const很简单,一个指针本身添加const限定符就是顶层const,而指针所指的对象添加const限定符就是底层const。
----------------------------
感觉书里面说的更好,如果关键字const出现在星号左边,表示被指物是常量;如果出现在星号右边,表示指针自身是常量;如果出现在星号两边,表示被指物和指针都是常量。

二. const 最具威力的是面对函数声明时的应用, const可以与函数返回值,各参数,函数自身(成员函数)产生关联

  • 令函数返回一个常量值,可以降低因客户操作而造成的(意外错误)

    namespace eapp
    {
        class Rational
        {
            const Rational operator* (const Rational &lhs,const Rational &rhs)
                //重载成员函数返回一个常量值
        	{
            	return Rational();
       		}
        }
        void test()
        {
            Rational a,b,c;
            if(a*b = c) {}//防止客户的暴行,就不会引起编译错误,但事实上代码是错误的。
        }
    }
    
  • const 实施成员函数: 确认该成员函数可作用于 const 对象身上。
    第一:使得 class接口更加容易被理解,(得知哪些函数可以改动对象内容哪些不行)
    第二:使得操作 const 对象成为可能, (pass by reference-to-const 方式传递对象),用const成员函数处理const对象(用魔法打败魔法)。

成员函数如果只是常量性不同,可以被重载

class TextBlock
{
    private:
    string text_;
    public:
    const char &operator[](size_t position) const {return text_[position];}
    char &operator[](size_t position){return text_[position];}//比上面少了两个const
    
}

如何实施对对象进行const限制的措施:

  1. bitwise const阵营:不更改对象内的任何一个 bit (太过于强硬)
  2. bitwise constness阵营: 对对象的成员变量实施 const(编译器的做法)
  3. logical constness(重要): 一个const成员函数可以修改它所处理的对象的某些 bits,但请确保客户端侦测不出 (实现办法是利用C++的一个与const相关的摆动场: mutable)
class CTextBlock1
{
    private:
    	char* text_;
    public:
    	//
    char &operator[](size_t position)const{return text_[position];}
};
class CTextBlock2
{
    private:
    	char* text_;
    	mutable size_t text_length_;	//使用mutable, 实现 logical constness,规定一些bit可以被修改
    	mutable bool text_isvaild_;
    public:
    	size_t length() const;
};
size_t CTextBlock2::length() const
{
    if(!text_isvaild_)
    {
        text_length_=strlen(text_);
        text_isvaild_=true;
    }
    return text_length_;
}

在const 和 non-const 成员函数中避免重复(写出重复的代码)

//目的是使用operator[]的机能一次并使用它两次。
class CTextBlock3
{
    private:
    	char* text_;
    public:
    	const char &operator[](size_t position)const{return text_[position];}
    	char &operator[](size_t position){
            return const_cast<char&>(
                static_cast<const CTextBlock3&>(*this)[position]
            )};//P25详细剖析。
    
};

总结:

  1. 将某些东西声明为const能帮助编译器去甄别错误用法
    (任意作用域对象,函数参数,函数返回类型,成员函数本体)
  2. 编译器强制实施 bitwise constness,但我们编写程序时应该使用 “概念上的常量性”
  3. 当const 和 non-const成员函数有着实质等价的实现时,令 non-const 版本调用const版本可避免代码重复。
上一篇:55. 跳跃游戏


下一篇:剑指 Offer 55 - II. 平衡二叉树