C++ const 成员函数

在 C++ 的类中,成员函数通常可以分为两类。一类负责 “修改” 对象的数据,比如 setWidth()。另一类只负责 “读取” 或 “展示” 对象的数据,比如 getWidth()、getArea()、showInfo()。

对于只负责读取数据的成员函数,如果我们希望从语法上做出限制,防止在函数内部不小心改掉了对象的数据,就可以在成员函数的参数列表后面加上 const 关键字。这种函数称为 “const 成员函数”,平时也常叫做 “常成员函数”。

const 成员函数的基本用法

在 C++ 中,定义 const 成员函数非常简单,我们只需要在成员函数的参数列表后面加上 const 关键字即可。

语法:

class 类名 {
public:
    返回值类型 函数名(参数列表) const {
        // 函数体
    }
};

说明:

书写时小伙伴们要注意 const 的位置:它必须放在参数列表的右小括号 ) 后面、左花括号 { 前面,比如 int getWidth() const。如果我们把 const 写在最前面写成 const int getWidth(),修饰的就是返回值类型,而不是常成员函数了。

结合上一节学过的 this 指针就很好理解了。在函数末尾加上 const,相当于把隐含的 this 变成了指向只读对象的 const 指针(即 const 类名*),因此在函数内部只能读取当前对象的成员,不能修改。

示例 1:定义 const 成员函数

#include <iostream>

class Rectangle {
private:
    int width;
    int height;

public:
    Rectangle(int w, int h) : width(w), height(h) {}

    // 只读取宽度
    int getWidth() const {
        return width;
    }

    // 只计算面积
    int getArea() const {
        return width * height;
    }

    // 只打印信息
    void showInfo() const {
        std::cout << "宽:" << width << '\n';
        std::cout << "高:" << height << '\n';
    }
};

int main() {
    Rectangle rect(10, 5);
    rect.showInfo();
    std::cout << "矩形面积:" << rect.getArea() << '\n';
    return 0;
}

运行结果如下。

宽:10
高:5
矩形面积:50

分析:

在这个例子中,我们为 Rectangle 类定义了 getWidth()、getArea() 和 showInfo() 这 3 个成员函数。它们都只负责读取 width 和 height 的值,不需要修改当前对象的任何数据,因此我们在参数列表后面都加上了 const。

注意 如果把 const 成员函数拆分为 “类内声明、类外定义”,那么声明和定义的末尾都必须写上 const。比如类外定义也要写成 int Rectangle::getWidth() const,两边保持一致才行,否则会编译报错。

const 成员函数的只读限制

给成员函数加上 const 之后,相当于向编译器作出了 “不修改当前对象” 的承诺。编译器在编译阶段会严格检查函数体,一旦违反以下 2 条规则,就会直接报错:

  • 不能修改当前对象的普通成员变量:在 const 成员函数内部,我们可以正常读取任何成员变量,但不能修改普通成员变量的值。
  • 不能调用当前对象的非 const 成员函数:在 const 成员函数内部,我们只能调用其他的 const 成员函数,不能调用未加 const 的普通成员函数。因为非 const 成员函数有可能会修改对象的数据,如果允许调用它,就相当于绕开了只读限制。

下面我们通过一个例子,看看在 const 成员函数内部修改成员变量时会发生什么。

示例 2:违反只读限制报错

#include <iostream>

class Rectangle {
private:
    int width = 10;

public:
    void reset() const {
        width = 0;    // 错误:不能修改成员变量
    }
};

int main() {
    Rectangle rect;
    rect.reset();
    return 0;
}

运行结果如下。

(报错)error: assignment of member 'Rectangle::width' in read-only object

分析:

width = 0; 这句代码尝试在 reset() const 内部修改成员变量。由于函数末尾带有 const,当前对象在函数内部处于只读状态(read-only object),因此编译时直接报错,不允许对 width 重新赋值。

同理,假设类中还定义了一个只读的 getWidth() const 和一个未加 const 的 setWidth(),那么在 const 成员函数内部调用时也有明确区分。

void test() const {
    // 正确方式
    int w = getWidth();

    // 错误方式
    setWidth(20);
}

初学的小伙伴有时会把 const 成员函数误解成 “里面什么成员都不能用”。其实并非如此,大家只要分清 “读” 和 “写” 即可:读取成员变量和调用其他 const 成员函数都是完全正常的,只有可能修改当前对象的操作才会被禁止。

const 对象与常引用的调用规则

除了防止我们在函数内部误改数据之外,const 成员函数还有一个非常关键的实际用途:只有 const 成员函数,才能被 “const 对象(常对象)” 或 “const 引用(常引用)” 调用。

就像普通变量加上 const 会变成常量一样,我们在创建对象时也可以在前面加上 const,将其定义为常对象。通过引用传递接收对象参数时,我们也经常把形参写成 const 引用。这样既能省去复制开销,又能防止原对象被修改。

无论是 const 对象还是 const 引用,都代表对象处于只读状态。它们与普通对象调用成员函数的权限对比如下表所示。

成员函数调用权限
对象或引用类型 调用 const 成员函数 调用非 const 成员函数
普通对象 可以调用 可以调用
const 对象(常对象) 可以调用 不能调用(编译报错)
const 引用(常引用) 可以调用 不能调用(编译报错)

这里有一个很容易踩坑的细节:哪怕我们在普通成员函数里一行修改数据的代码都没写,只要函数声明末尾没有加上 const,编译器就会认为它 “有可能会修改对象”。一旦被判定为可能修改对象,const 对象和 const 引用就都不能调用它了。下面我们结合具体示例来看。

示例 3:不同对象的调用权限

#include <iostream>

class Counter {
private:
    int value;

public:
    Counter(int v) : value(v) {}

    // 普通成员函数
    void increase() {
        value++;
    }

    // const 成员函数
    int getValue() const {
        return value;
    }
};

// 形参为 const 引用
void printCounter(const Counter& c) {
    std::cout << "常引用读取:" << c.getValue() << '\n';
}

int main() {
    // 1. 普通对象
    Counter c1(10);
    c1.increase();
    std::cout << "c1 的值:" << c1.getValue() << '\n';

    // 2. const 对象
    const Counter c2(100);
    std::cout << "c2 的值:" << c2.getValue() << '\n';

    // 3. 传给 const 引用形参
    printCounter(c1);
    return 0;
}

运行结果如下。

c1 的值:11
c2 的值:100
常引用读取:11

分析:

在 Counter 类中,increase() 需要让 value 加 1,因此定义为普通的非 const 成员函数。而 getValue() const 只负责返回当前数值,因此声明为 const 成员函数。

进入 main() 函数后,c1 是普通对象,调用 increase() 和 getValue() 都没有问题。而 c2 是 const 对象,printCounter() 的形参 c 是 const 引用,它们都只能调用带有 const 修饰的 getValue()。

如果我们尝试通过常对象 c2 去调用未加 const 的 increase(),下面这种写法就会直接编译报错。

const Counter c2(100);
c2.increase();

反过来想,要是我们漏掉了 getValue() 末尾的 const,示例中的 c2.getValue() 和 c.getValue() 也会编译报错。因此在实际开发中,只要成员函数不需要修改成员变量(如 getXXX()、showXXX()),我们都建议在末尾加上 const。

mutable 关键字

在少数场景下,某个成员函数整体上是只读的(如 showInfo() const),但我们又希望在里面统计一下 “这个函数一共被调用了多少次”。此时如果直接修改普通的计数成员变量,编译器就会报错。

为了处理这种特殊情况,C++ 提供了 mutable 关键字,意为 “可变的”。只要在定义该成员时加上 mutable(如 mutable int count = 0;),即使在 const 成员函数内部也能正常修改它。这个关键字平时用得不多,小伙伴们简单了解一下即可。

上一篇:

下一篇:

给站长反馈

绿叶网正在不断完善中,小伙伴们如果发现任何问题,还望多多给站长反馈,谢谢!

邮箱:lvyenet@vip.qq.com

「绿叶网」服务号
绿叶网服务号放大
关注服务号,微信也能看教程。
绿叶网服务号