C++ const的一些最佳实践

1,468 阅读5分钟

前言

有了前面的学习基础之后,本文想带大家吃透const关键字在实际开发中的应用。const是不可改变的意思。它可以应用于成员变量、成员函数变量、函数参数、函数返回值等。本文覆盖的场景比较多,能帮大家重新回顾下之前学习的知识点。

const修饰变量

const可以修饰一个变量,表示这个变量是不可修改的,也就是只读的意思。一旦被初始化,该变量的值将不能再被修改。所以说也能表示常量。

const int MAX_VALUE = 100;
// MAX_VALUE的是只读的,它的值不能再被修改

C++表示常量还有另外一种方式。宏定义,格式: #define 常量名 常量值

#define MAX 999

const既可以修饰普通的变量,也可以修饰类的成员变量。初始化const修饰的成员变量有两种方式

1.通过初始化成员列表方式来完成

class MyClass {
private:
    const int _count;
public:
    MyClass(int value) : _count(value) {}
};

2.在声明常量的同时进行初始化

这种方式被称为直接初始化。这是C++11的新特性。

class MyClass {
private:
    //c++11新特性,申明变量时直接初始化
    const int COUNT = 100;
public:
    MyClass(int value) {}
};

const修饰成员函数

const修饰成员函数,表示该函数不会修改类的成员变量。const关键字加在函数声明的末尾。

class MyClass {
private:
    int _data;
public:
    MyClass(int value) {}
    
    int getData() const{
//        _data++;  //这种操作是不被允许的,
        return _data;
    }
};

加了const之后的函数,该函数中是不能修改该类的任何成员变量。

函数返回值为const

表示返回的是一个常量,不希望返回值被修改。

  1. 先来看个简单的例子
const int getValue() {
    int data = 10;
    return data;
}

int main() {
    int value = getValue();
    value = 20; 
}

在这个例子中getValue的返回值是const,它的目的是为了函数的调用者不能修改函数的返回值。但是在函数调用的地方用了一个int类型的变量value来接收函数的返回值,并且又把这个value修改为20。这种情况,对value修改并没有修改函数的返回值。所以说对于函数返回值为基本数据类型,返回值是const没有任何实际意义。

  1. 再来看一个例子
class A {
public:
    int data;

    A(int value) : data(value) {}
};


const A getA() {
    A a(10);
    return a;
}

int main() {
    A a2 = getA();
}

上面的getA函数返回值是const修饰的类对象。这个函数用const修饰返回值也是没有意义的。由于返回的对象是一个临时对象,它的生命周期仅限于getA函数的作用域内。因此,即使在调用者尝试修改这个临时对象,也不会对原始对象产生任何影响。

  1. 再来看一个例子。下面例子返回一个指针数组
char *getValue() {
    char *p = new char[10];
    strcpy(p, "hello");
    return p;
}

int main() {
    char *p1 = getValue();
    cout << p1 << endl; //输出“hello”
    *p1 = 'H'; //修改p1指针指向的内容的第一个字符
    cout << p1 << endl; //输出“Hello”
    
    return 0;
}

在这个例子中getValue函数返回值类型是指针数组,在调用该函数的地方用了一个p1指针变量来接收返回值,然后是可以修改修改p1指针的内容的。在这个例子中指针pp1指向的都是堆内存上的内容,所以说p1修改了内容之后,等于修改了函数的返回值。

要想函数返回值不被修改,可以修改为如下方式

const char *getValue() {
    char *p = new char[10];
    strcpy(p, "hello");
    return p;
}

int main() {
    //这样会编译报错,
    //error: cannot initialize a variable of type 'char *' with an rvalue of type 'const char *'
//    char *p1 = getValue();
    const char *p1 = getValue();
    cout << p1 << endl; //输出“hello”
//    *p1 = 'H'; //不支持修改,编译报错

     return 0;   
}

小结
如果返回的对象是通过值传递的方式返回的,所以即使将返回值声明为const也不会影响调用者对返回值的修改。**只有通过引用或指针返回的对象,使用const修饰才有意义,因为这种情况才会对返回值修改。

函数的参数为const修饰

const关键字可以用于函数参数,表示该参数在函数内部不可被修改。这样可以确保函数不会修改传入的参数值。

class A {
public:
    int data;

    A(int value) : data(value) {}
};


void func(const A &a){
//    a.data = 100; //不支持修改,编译报错
    cout << a.data << endl;
}

这一点在之前讲拷贝构造函数的时候已经用到过这个语法了。我们再来回顾下。

class A {
public:
    int data;

    A(int value) : data(value) {}

//拷贝构造函数
    A(const A &a) {
        this->data = a.data;
    }
};

const修饰对象

这个例子其实和第一个例子是一种类型,只不过它修饰的是类对象。这是一个使用相对不那么广泛的例子,但也需要大家掌握它。

class A {
public:
    int data;

    A(int value) : data(value) {}
};


int main() {
    const A a(10);
//    a.data = 100; //编译报错,不能修改任何成员

    return 0;
}

这个例子中用const修饰对象A,就不能修改成员变量。

const修饰的对象具有如下特点

  1. const修饰的对象在声明后其值不能被修改,即为常量对象,而且不能修改任何成员变量。
  2. const修饰的对象只能访问const修饰的成员函数,因为访问非const修饰的成员函数极有可能修改成员变量的值。
  3. const修饰的对象可以访问public修饰的成员变量,但是不能修改它的值。