Effective C++ 学习笔记——条款03:尽可能使用const

Effective C++ 学习笔记——条款03:尽可能使用const

const的功能:

  1. 用在class外部修饰global或namespace作用于中的常量;
  2. 修饰文件、函数、块区域作用域中被声明为static的对象;
  3. 用于修饰class内部的static和non-static成员变量;
  4. 用于指出指针自身、指针所指物,或两者都(不)是const。

const与指针:

  1. const出现在 * 左边,表示被指物为常量;

  2. const出现在 * 右边,表示指针自身是常量;

  3. 如果 * 两边均有const,表示被指物和指针两者都是常量。
    例如:

    const char* p;          //数据是const,数据不允许被改变
    char* const p;          //指针是const,指针不允许被改变
    const char* const p;    //数据与指针都是const,数据与指针都不可以被改变
    

记忆方法:

  1. const 在 * 左侧,称常量指针,即指向常量的指针——const修饰数据;
  2. 在 const 左侧,称指针常量,即指针是常量——const修饰指针。
  3. 注意,此处指的是 const 与 * 的相对位置,以下两种情况完全相同:
    const char* pw;         //都表示指向常量char的指针
    char const* pw;
    

const与迭代器:

迭代器:STL中的迭代器是以指针为基础塑造出来的,所以迭代器的作用相当于一个 T* 指针。

  1. 声明迭代器为 const 就相当于声明指针为 const,即 T* const 指针,表明迭代器不的指向其他数据,但所指向的指可以改变;
  2. 若希望迭代器所指的东西不可改变,即实现 const T* 指针,则需要设定为const_iterator,而不是 const iterator。
    例如:(第一条)
    const std::vector<int>::iterator it = v.begin(); //注意,此声明只表示迭代器本身是常量
    *it = 10;                                        //编译通过,迭代器是常量,但数据可以被修改
    ++it;                                            //编译失败!因为const迭代器不允许被改变!
    
    根据上述分析应修改为:(第二条)
    std::vector<int>::const_iterator it = v.begin();  //使用了const_iterator类型
    *it = 10;                                         //编译失败,数据不允许被改变!
    ++it;                                             //编译通过,迭代器本身可以被改变
    

const与函数

const对函数声明的应用:
在函数声明式内。const可以和函数返回值、参数、函数自身(如果是成员函数)产生关联。

  1. 另函数返回值为常量,可以降低因客户错误而造成的意外,提高安全性和高校性;
  2. const 参数,若无必要修改 local对象,应将其声明为 const local对象,避免出现错误修改的问题。
  3. 例如:
    在某处使用此乘法操作符时,误把比较操作符"==“打成了赋值操作符”=":
    class Rational{....};
    Rational operator*(const Rational& lhs, const Rational& rhs){...}
    Rational a,b,c;
    if(a*b = c){......}
    
    存在问题:由于 Rational 是自定义类型,因此编译器不会对其报错,因此会产生意想不到的问题,调试是也很难测试。
    修改方案:应将操作符的返回值定义为const:
    const Rational operator*(const Rational& lhs, const Rational& rhs){...}
    

const与成员函数

const用于成员函数的目的:确保成员函数作用于const对象上。

  1. 是 class 接口更加直观,可以准确保证那些函数为只读,是否可修改;
  2. 用 const 修饰的对象只能调用 const 修饰的成员函数,因为不被 const 修饰的成员函数可能会修改其他成员数据,打破 const 关键字的限制。
  3. 注意:若两个成员函数只有常量性(const)不同,可以被重载。
    因此,需要同时声明有const和没有const的成员函数,重载operator[]函数,并对不同版本函数给予不同的返回类型,就会令 const / non-const 得到不同的结果。
    例如:
    const char& operator[](size_t pos) const;
    char& operator[](size_t pos);
    对于某自定义的类Text:
    Text t("Hello");
    const Text ct("Hello");
    std::cout<<t[0];            //调用了不加const修饰的索引操作符
    std::cout<<ct[0];           //调用了const版本, 但如果只有不加const的操作符,将会报错discard qualifier
    t[0] = 'x';                 //成立,但注意此索引操作符必须声明为引用才可以支持赋值操作
    ct[0] = 'x';                //错误!常量不能被修改
    

注意:const对象大多用于 passed by pointer-to-const 或者 passed by reference-to-const 的传递结果。
如果函数的返回值类型为内置类型,则改动返回值并不会改变其真实值,只会修改其副本。

成员函数的常量性

const成员函数有两种主流观点(或者说通常用法):bitwise constness 和 logical constness。
bitwise const:const成员函数必须用于不改变任何成员变量的情况下,const 成员函数不可以更改对象内任何 non-static 成员变量。
但存在如下情形,即使修改了某个数据,也可以通过编译器的检测:

const Text ct("Hello");        //构造某常量对象
char* pc = &ct[0];             //取其指针
*pc = 'J';                     //通过指针修改常量对象,编译不会报错,结果为"Jello"

数据常量性还有另一个局限性,例如:

class Text{
  public:
    std::sizt_t length() const;
  private:
    char* pText;
    std::size_t length;
    bool lengthValid;
....
};
std::size_t Text::length() const{
  if(!lengthValid){                      //做某些错误检测
    length = std::strlen(pText);
    lengthValid = true;
  }
  return length;                         //这行才是代码核心
}

在这段代码中,length()函数要做某些错误检测,因此可能会修改成员数据。即使真正的功能核心只是返回字符长度,编译器依然认为你可能会修改某些成员数据而报错。
logical constness:允许某些数据被修改,只要这些改动不会反映在外。
可以通过 mutable 关键字(mutable 释放掉 non-static 成员变量的 bitwise constness 约束)来解决 bitwise const 存在的问题:

mutable std::size_t length;
mutable bool lengthValid;

注意:除 mutable 之外,静态成员(static)也可以被const成员函数修改。

在定义常量与非常量成员函数时,避免代码重复

可能大家会有所困惑,既然两个版本的成员函数都要有,为什么又要避免重复?
其实在这里指的是函数的实现要避免重复。试想某函数既要检查边界范围,又要记录读取历史,还要检查数据完整性,这样的代码复制一遍,既不显得美观,又增加了代码维护的难度和编译时间。
因此,我们可以使用非常量的函数来调用常量函数。

const char& operator[](std::size_t pos) const{....}
char& operator[](std::size_t pos){
  return
    const_cast<char&>(                              //const_cast去掉const关键字,并转换为char&
      static_cast<const Text&>(*this)[position];    //给当前变量加上const关键字,才可以调用const操作符
  );
}

为了避免无限递归调用当前非常量的操作符,我们需要将(*this)转换为const Text&类型才能保证安全调用const的操作符,最后去掉const关键字再将其返回,巧妙避免了代码的大段复制。
注意:如果使用相反的方法,用const函数来调用non-const函数,就可能会有未知结果,因为这样相当于non-const函数接触到了const对象的数据,就可能导致常量数据被改变。

总结

  1. const 是一个十分多功能的关键字,可以用在指针和迭代器以及其reference指涉的对象上、用在函数参数和返回类型上、局部变量或成员函数上,等等;
  2. 将某些函数、变量声明为 const 可以帮助编译器侦测出错误用法;
  3. const 可被施加于任何作用于内的对象、函数参数、函数返回类型、成员函数本体;
  4. 编译器强制实施 bitwise constness,但编写程序时应当使用“概念上的常量性”multable;
  5. 当 const 和 non-const 成员函数有着实质等价的实现时,令 non-const 版本调用 const 版本可避免代码重复。

你可能感兴趣的:(Effective,C++学习笔记,c++)