【effective C++】03、尽可能使用const

const指定一个语意约束,编译器会强制实施这个约束,告诉编译器和程序员某值应该保持不变。

1、const修饰变量

const可以修饰在class外部的global或namespace作用域中的常量;可以修饰文件、函数、区块作用域中声明为static的对象;也可以修饰class内部的static和non-static成员变量。
还可以修饰指针或指针所指物,根据const位置的不同,修饰的也不同(判断的诀窍是:const出现在*的左端还是右端,左端修饰指针所指物,右端修饰指针本身):

char[] greeting = "hello";
char* p = greeting;

//下面的两行,指针所指物是const,而指针本身是non-const
const char* p = greeting;
char const * p = greeting;

//指针所指物是const,而指针本身是non-const
char* const p = greeting;

//下面两行指针所指物是const,指针也是const
const char* const p = greeting;
char const * const P = greeting;

2、const修饰的迭代器

STL的迭代器是根据指针来实现的,所以迭代器的作用像一个T*的指针。
1、当声明迭代器用const 修饰时:表示这个迭代器不得指向其他的东西,但是所指东西的值是可以改变的。
2、当迭代器由const_iterator来定义时:表示这个迭代器所指东西的值是不可以改变的。
3、当声明迭代器用const 修饰并由const_iterator来定义时:表示这个迭代器所指东西的值是不可以改变的。
例子如下:

std::vector<int> vec;
//...

//作用同 T * const
const std::vector<int>::iterator iter = vec.begin();
*iter = 10;     //正确!可以改变所指物
++iter;         //错误!iter是const

//作用同 const T * 或者 T const *
std::vector<int>::const_iterator cIter = vec.begin();
*cIter = 10;    //错误!*cIter是const
++cIter;        //正确!可以改变指针

const std::vector<int>::const_iterator ccIter = vec.begin();
*ccIter = 10;   //错误!*ccIter是const
++ccIter;       //错误!cciter是const

3、const修饰函数

3.1、const修饰函数的参数

const修饰函数的参数。时,函数的参数在函数的内部不可以改变,具体的规则与const修饰变量的规则相同。

3.2、const修饰函数的返回值

函数的返回值分为值传递和指针传达:
1、对于值传递,函数会把返回值复制到外部临时的存储单元,这时加const修饰没有任何意义,所以

不要把函数int GetInt(void) 写成const int GetInt(void);
不要把函数A GetA(void) 写成const A GetA(void),其中A 为用户自定义的数据类型。

2、对于指针传递,如果返回值是const data、non-const pointer,那么返回值也必须赋值给const data、non-const pointer,因为指针指向的常量不能修改。例如:

const int* mallocA(){
    int* a = new int(2);
    return a;
}
int mian() {
    const int* a = mallocA();     //编译正确!
    int *b = mallocA();           //编译错误!
    return 0;
}

3、特殊情况:在操作赋重载的函数中,使用const修饰函数的返回值,可以避免因失误而造成的错误:

class Rational { /*---*/};
const Rational operator* (const Rational& lhs, const Rational& rhs);

上面这个乘积操作符重载为什么函数的返回值用const修饰?
原因:
类似与下面的操作, a*b的结果不能被修改,不能作为左值,但在程序员的编码过程中有可能会出现不小心的错误,这样做可以防止无意的赋值操作。

Rational a, b, c;
//---
(a * b) = c;
if(a * b = c);

4、const修饰成员函数

const成员函数是用来确认该成员函数可以作用于const对象身上。使用const修饰成员函数的两个理由:

1、这使得class接口比较容易理解,很明显的知道函数是否可以改动对象的内容。
2、这使得操作const对象成为可能。
#include<iostream>
#include<cstring>

using namespace std;

class TextBlock{

    private:
        string text;

    public:
        TextBlock(string str) {
            text = str;
        }

        const char& operator[](size_t position) const{
            cout<<"call const"<<endl;
            return text[position];
            //return text[position] = 'b'; 编译错误,不可以改变text的值
        }

        char& operator[] (size_t position){
            cout<<"call non-const"<<endl;
            return text[position];
            //return text[position] = 'b'; 编译正确,可以改变text的值
        }
};

int main() {
    TextBlock tb("Hello");
    cout<<tb[0]<<endl;
    tb[0] = 'x';//编译正确!如果成员函数的返回值是内置类型char而不是char&,改动函数返回值是不合法的,即使是合法,改变的也只是tb.text[0]的副本而已。

    const TextBlock ctb("World");
    cout<<ctb[0]<<endl;
    tb[0] = 'x';//编译错误!

    return 0;
}

编译执行的结果:
这里写图片描述

4.1、成员函数如果是const意味着什么??

两个流行的概念:bitwise constness(physical constness) 和 logical constness。

bitwise constness(physical constness):

const对C++的常量性进行定义,const成员函数不可以改变对象内任何的non-static成员函数;
但是,当一个对象只有指针是其数据成员,const成员函数无法修改指针的值,但可以修改指针所指物的值。
#include<iostream>
#include<cstring>
using namespace std;

class TextBlock{
    private:
        char* pText;
    public:
        TextBlock(string str){
            int len = str.length();
            char* temp = (char*) new char[len];
            str.copy(temp, len, 0);
            pText = temp;
        }
        char& operator[] (size_t position) const{
            return pText[position];
        }
};

int main() {
    const TextBlock cctb("Hello");
    char* pc = &cctb[0];
    *pc = 'J';
    cout<<cctb[0]<<endl;   //打印的结果为"J",说明指针pText所指的字符已经被修改
    return 0;
}

logical constness:

一个const成员函数可以修改它所处理的对象内的某些bits,但只有在客户端侦测不出的情况下才得如此。
利用C++中一个与const相关的摆动场:mutable,它可以释放掉non-static成员变量的bitwise constness约束。
#include<iostream>
#include<cstring>
using namespace std;

class TextBlock{
    private:
        char* pText;
        mutable size_t textLength;
        mutable bool lengthIsValid;

    public:
        TextBlock(string str){
            int len = str.length();
            char* temp = (char*) new char[len];
            str.copy(temp, len, 0);
            pText = temp;
        }
        char& operator[] (size_t position) const{
            return pText[position];
        }
        size_t length() const;
};

size_t TextBlock::length() const {

    if(!lengthIsValid) {
        textLength = strlen(pText);
        lengthIsValid = true;
    }
    return textLength;
}

int main() {
    const TextBlock cctb("Hello");
    char* pc = &cctb[0];

    *pc = 'J';

    cout<<cctb[0]<<endl;
    cout<<"length  "<<cctb.length()<<endl;//返回长度值为 5
    return 0;
}

4.2、const和non-const成员函数中避免重复

当出现下面这种情况时,为避免代码的重复,需要一次实现两次使用:

class TextBlock{
    public:
        const char& operator[](size_t position) const{
            ...    //边界检查
            ---    //日志数据访问
            ---    //检查数据的完整性
            return text[position];
        }

        char& operator[](size_t position) {
            ---    //边界检查
            ---    //日志数据访问
            ---    //检查数据的完整性
            return text[position];
        }
};

可以做一下的函数调用:

class TextBlock{
    public:
        const char& operator[](size_t position) const{
            ...    //边界检查
            ---    //日志数据访问
            ---    //检查数据的完整性
            return text[position];
        }

        char& operator[](size_t position) {
            return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]);
        }
};

上面的代码涉及到两个类型转换:
1、TextBlock& 转换成const TextBlock& 。将自身*this从原始类型TextBlock&转型为const TextBlock&,使用 static_cast为*this加上const;
2. 从const operator[]的返回值中移除const,用const_cast。

⚠️注意:const 成员函数调用 non-const 成员函数是一种错误行为,因为对象有可能因此被改动;而反向调用是安全的,non-const成员函数本来就可以对其对象做任何动作,所以在其中调用一个const成员函数并不会带来风险。

5、总结

  1. 将某些东西声明为const可帮助编译器侦测出错误用法。const 可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体。
  2. 编译器强制实施bitwise constness,但编写程序时应该使用“概念上的常量性”。
  3. 当 const 和 non-const 成员函数有着实质等价的实现时,令non-const 版本调用 const 版本可避免代码重复。

猜你喜欢

转载自blog.csdn.net/u013108511/article/details/80458286
今日推荐