菜鸟笔记
提升您的技术认知

如何理解const成员函数

在深入理解const成员函数之前,先来复习一下const关键字的基础用法。

1. const修饰某个非指针类型变量

const 修饰某个非指针类型变量,表示该变量只读。

const int a = 10;
a = 20;         // 错误,不可以赋值

2. const修饰指针

const 在*号前面表示指针指向的内容不可更改,指针本身可以改变

char buf[] = "hollo, world";
const char* p = buf;
p++;        //指针自增,正确
*p = 'e';   //错误,指针指向的内容不可更改

const 在*号后面表示指针本身不可改变(不能被赋值),指针指向的内容可以改变,这个指针必须在初始化的时候赋值。

char buf[] = "hollo, world";
char* const p = buf;
*p = 'H';   //正确
p++;        //错误,p不可更改

const 在*号前后都加表示指针本身和指针指向的内容均不能改变。

char buf[] = "hollo, world";
const char* const p = buf;
*p = 'H';   //错误,不能改变p指向的内容
p++;        //错误,p不能更改

3. const修饰成员函数

好了,到这里我们可以来探讨一下const成员函数了。首先要明确const修饰成员函数的目的是什么。const修饰成员函数是为了使该成员函数可以作用于const对象身上,用const修饰成员函数的作用实际上是修改隐式this指针的类型。

默认情况下,this指针的类型是指向类类型非常量版本的常量指针。这句话有点拗口,举个例子,假如有一个对象A,那么在A对象的成员函数中this的类型是A* const this,根据前面的内容我们知道,this指针不可以更改,但是this指针指向的对象的成员变量是可以更改的。

class A {
public:
    int x;
    int y;
    A():x(0),y(0) {}
    void set(int x, int y) {
        // this的类型是 A* const
        this->x = x;    
        this->y = y;    
    }
    int getX() {
        return this->x;
    }
    int getY() {
        return  this->y;
    }
}
int main()
{
    A m;
    m.set(1, 2);    // 正确
    std::cout << m.getX();  //正确
    std::cout << m.getY();  //正确 
    return 0;
}

尽管A* const this是隐式的,但是他仍然要遵循初始化的规则,也就是说我们不能把this绑定到一个常量对象上。那么我们也不能在一个常量对象上调用普通的成员函数。

const A m;
m.set(1, 2)     //错误,m是常量对象,不能绑定到this,因此不能调用非const函数
m.getX();       //错误,m是常量对象,不能绑定到this,因此不能调用非const函数

前面说过this指针的类型是A* const,因此不能绑定到一个常量对象上。如果把this指针的类型改为const A* const,就可以把this绑定到一个常量对象上了。然而,this是隐式的并不会出现在成员函数的参数列表中,所以在哪里将this声明为指向常量的指针(const A* const)就成了一个问题。C++的做法是允许把const关键字放在成员函数的参数列表之后,此时,紧跟在参数列表后面的const表示this是一个指向常量的指针(const A* const)。像这样使用const的成员函数被称为常量成员函数。为什么要把const关键字放在参数列表后面,而不是放在前面呢?因为放在参数列表前面就成了修饰成员函数的返回值,显然不符合我们需要的功能,同时this又不能出现在参数列表中,因此只能放在参数列表后面。修改A对象的定义如下:

class A {
public:
    int x;
    int y;
    A():x(0),y(0) {}
    void set(int x, int y) {
        // this的类型是 A* const
        this->x = x;    
        this->y = y;
    }
    int getX() {
        return this->x;
    }
    int getY() {
        return  this->y;
    }
    int getX() const {
        return this->x;
    }
    int getY() const {
        return this->y;
    }
}

这样我们就可以愉快的调用常量的成员函数。在这里int getX()函数和int getx() const函数构成重载,getY两个函数同理。

const A m;
A n;
m.set(1, 2);    //错误,常量对象不能调用非常量成员函数
m.getX();       //正确,调用的是常量成员函数
n.set(1, 2);    //正确
n.getX();       //正确,调用的是普通成员函数

4. 总结

const的使用方法可以总结为4句话:

  1. const 修饰某个非指针类型变量,表示该变量只读。
  2. const 修饰指针,在*号前面表示指针指向的内容不可更改,指针本身可以改变。
  3. const 修饰指针,在*号后面表示指针本身不可改变(不能被赋值),指针指向的内容可以改变,这个指针必须在初始化的时候赋值。
  4. const 修饰成员函数,表示一个常量对象可以调用该成员函数。