【c++面向对象编程】第8篇:const成员与mutable:常对象与常函数

目录

一、一个编译错误引发的思考

二、const成员函数:只读的承诺

语法

为什么要区分?

三、mutable:const函数里的"例外"

基本用法

什么时候用mutable?

mutable的误解

四、重载:const版本和非const版本

五、完整例子:学生成绩管理系统

六、三个常见误区

[1. 以为const成员函数不能调用非const函数(正确)](#1. 以为const成员函数不能调用非const函数(正确))

[2. 混淆const位置](#2. 混淆const位置)

[3. 滥用mutable](#3. 滥用mutable)

七、这一篇的收获


一、一个编译错误引发的思考

先看这段代码,猜猜哪里会报错:

cpp

复制代码
class Student {
private:
    string name;
    int score;
public:
    Student(string n, int s) : name(n), score(s) {}
    
    string getName() { return name; }      // 不是const函数
    int getScore() const { return score; } // const函数
    
    void setScore(int s) { score = s; }    // 修改成员
};

int main() {
    const Student s("张三", 85);   // 常对象
    
    cout << s.getName();    // 第1处:能编译吗?
    cout << s.getScore();   // 第2处:能编译吗?
    s.setScore(90);         // 第3处:能编译吗?
}

答案:

  • 第1处:❌ 错误。sconst对象,getName()不是const函数,可能修改对象

  • 第2处:✅ 正确。sconstgetScore()const,保证不修改

  • 第3处:❌ 错误。setScore明显要修改成员

规则很简单:常对象只能调用常函数。


二、const成员函数:只读的承诺

语法

cpp

复制代码
class Demo {
private:
    int x;
public:
    int getValue() const {   // const放在参数列表后面
        return x;            // ✅ 可以读
        // x = 100;          // ❌ 不能写
    }
};

这个const修饰的是谁?

答案是:它修饰的是this指针。回忆第6篇:

函数类型 this的类型
普通成员函数 Demo* const
const成员函数 const Demo* const

const成员函数里,this指向的是一个const对象,所以:

  • 不能修改任何成员变量

  • 只能调用其他const成员函数

为什么要区分?

cpp

复制代码
class Printer {
private:
    int printCount;
public:
    void print(const string& text) const {  // 不应该修改状态
        cout << text << endl;
        // printCount++;  // 想统计打印次数,但const阻止了
    }
};

有时候你想统计打印次数,但逻辑上print不应该改变对象(它只是输出)。这时候有两种选择:

  1. 去掉const------但这样常对象就不能调用print

  2. mutable------下节讲


三、mutable:const函数里的"例外"

mutable的意思是"可变的"。它告诉编译器:即使是在const函数里,这个成员变量也可以被修改

基本用法

cpp

复制代码
class Printer {
private:
    mutable int printCount;   // 加mutable
public:
    Printer() : printCount(0) {}
    
    void print(const string& text) const {
        cout << text << endl;
        printCount++;   // ✅ 可以!mutable成员在const函数里也能改
    }
    
    int getCount() const { return printCount; }
};

int main() {
    const Printer p;    // 常对象
    p.print("Hello");   // ✅ 可以调用const函数
    p.print("World");
    cout << p.getCount();  // 输出2
}

没有mutable的话,上面的代码无法编译。有了mutable,你可以在保持"逻辑上对象没变"的同时,修改一些"实现细节"。

什么时候用mutable?

典型场景:

1. 缓存计算结果

cpp

复制代码
class Matrix {
private:
    double data[100][100];
    mutable double cachedDeterminant;
    mutable bool determinantValid;
public:
    double determinant() const {
        if (!determinantValid) {
            // 计算行列式(很耗时)
            cachedDeterminant = compute();
            determinantValid = true;
        }
        return cachedDeterminant;
    }
};

determinant()逻辑上不应该修改矩阵,但为了提高性能,需要缓存计算结果。mutable完美解决。

2. 引用计数

cpp

复制代码
class SharedObject {
private:
    mutable int refCount;
public:
    SharedObject() : refCount(0) {}
    void addRef() const { refCount++; }   // const函数但需要修改计数
    void release() const { refCount--; }
};

3. 互斥锁(线程安全)

cpp

复制代码
class ThreadSafeCache {
private:
    mutable mutex mtx;   // 锁需要被修改
    string data;
public:
    string get() const {
        lock_guard<mutex> lock(mtx);  // 锁住mtx会修改它
        return data;
    }
};

mutable的误解

mutable不是让你随意破坏const语义。它应该只用于那些不影响对象"外部可见状态"的内部细节

如果修改mutable成员后,对象的逻辑行为发生了变化,那用mutable就是错的。


四、重载:const版本和非const版本

一个类可以同时提供const和非const版本的同一个函数,编译器会根据对象是否是const来选择。

cpp

复制代码
class Array {
private:
    int data[10];
public:
    // 非const版本:返回引用,允许修改
    int& operator[](int index) {
        cout << "non-const version" << endl;
        return data[index];
    }
    
    // const版本:返回const引用,只读
    const int& operator[](int index) const {
        cout << "const version" << endl;
        return data[index];
    }
};

int main() {
    Array arr;
    arr[0] = 100;           // 调用non-const版本
    
    const Array& ref = arr;
    cout << ref[0];         // 调用const版本
}

这是标准库中的常见设计模式(比如vector::operator[])。


五、完整例子:学生成绩管理系统

把前面的知识点串起来:

cpp

复制代码
#include <iostream>
#include <string>
#include <vector>
using namespace std;

class Student {
private:
    string name;
    int score;
    mutable int accessCount;      // 记录getScore被调用了多少次(const里也能改)
    mutable int modifyCount;      // 记录setScore被调用了多少次
    
public:
    Student(string n, int s) : name(n), score(s), accessCount(0), modifyCount(0) {}
    
    // const函数:读取成绩,同时记录访问次数
    int getScore() const {
        accessCount++;    // mutable成员,允许修改
        return score;
    }
    
    // 非const函数:修改成绩,记录修改次数
    void setScore(int s) {
        score = s;
        modifyCount++;
    }
    
    string getName() const {
        return name;
    }
    
    // const函数里不能修改name/score,但可以读
    void print() const {
        cout << name << ": " << score << "分 (被查询" << accessCount 
             << "次,被修改" << modifyCount << "次)" << endl;
    }
    
    // 获取统计信息(const函数,只读访问)
    void printStats() const {
        cout << "统计: " << name << " 的成绩被查询" << accessCount 
             << "次,被修改" << modifyCount << "次" << endl;
    }
};

class ClassRoom {
private:
    vector<Student> students;
    mutable int totalQueryCount;   // 记录全班查询次数
    
public:
    ClassRoom() : totalQueryCount(0) {}
    
    void addStudent(const Student& s) {
        students.push_back(s);
    }
    
    // const函数:查询全班平均分,同时记录查询次数
    double getAverage() const {
        if (students.empty()) return 0;
        
        totalQueryCount++;   // mutable,可以修改
        
        int sum = 0;
        for (const auto& s : students) {
            sum += s.getScore();   // 调用const版本的getScore
        }
        return static_cast<double>(sum) / students.size();
    }
    
    void printClassInfo() const {
        cout << "全班平均分查询次数:" << totalQueryCount << endl;
    }
    
    // 非const函数:修改学生成绩
    void boostScore(int delta) {
        for (auto& s : students) {
            s.setScore(s.getScore() + delta);
        }
    }
};

int main() {
    cout << "=== 演示const对象和mutable ===" << endl;
    
    const Student s1("张三", 85);   // 常对象
    cout << s1.getName() << "的成绩: " << s1.getScore() << endl;  // ✅ getScore是const
    // s1.setScore(90);  // ❌ 编译错误,setScore不是const
    s1.print();         // ✅ print是const
    s1.printStats();    // ✅ const函数,显示mutable计数
    
    cout << "\n=== 演示const重载 ===" << endl;
    ClassRoom classroom;
    classroom.addStudent(Student("李四", 90));
    classroom.addStudent(Student("王五", 75));
    
    const ClassRoom& constClassroom = classroom;
    cout << "平均分: " << constClassroom.getAverage() << endl;  // const版本
    constClassroom.printClassInfo();
    
    // classroom.boostScore(5);  // 这行会修改对象,但const引用不能调
    classroom.boostScore(5);      // 通过原对象可以调非const函数
    
    cout << "加分后平均分: " << constClassroom.getAverage() << endl;
    constClassroom.printClassInfo();
    
    return 0;
}

输出:

text

复制代码
=== 演示const对象和mutable ===
张三的成绩: 85
张三: 85分 (被查询1次,被修改0次)
统计: 张三 的成绩被查询1次,被修改0次

=== 演示const重载 ===
平均分: 82.5
全班平均分查询次数:1
加分后平均分: 87.5
全班平均分查询次数:2

六、三个常见误区

1. 以为const成员函数不能调用非const函数(正确)

cpp

复制代码
class Demo {
    void normal() {}
    void constFunc() const {
        normal();   // ❌ 错误,normal不是const
    }
};

2. 混淆const位置

cpp

复制代码
int getValue() const;   // ✅ const成员函数
const int getValue();   // ❌ 返回const int,不是const成员函数

两个const位置不同,含义完全不同。

3. 滥用mutable

cpp

复制代码
class Bad {
private:
    mutable int importantData;   // 不应该随便设mutable
public:
    void changeImportantData() const {
        importantData = 100;     // 逻辑上这改变了对象的重要状态
    }
};

如果修改mutable成员后,obj1 == obj2的判断会改变,那这个成员就不应该是mutable


七、这一篇的收获

你现在应该明白:

  • 常对象const T obj)只能调用常函数const成员函数)

  • 常函数承诺不修改对象,thisconst T* const

  • **mutable**成员可以在常函数中被修改,用于缓存、计数、锁等内部状态

  • 可以重载const和非const版本的函数,编译器根据对象类型选择

💡 小作业:写一个Timer类,记录代码执行时间。要求:

  • start()stop()是非const函数

  • getElapsed() const是const函数,但内部需要修改缓存的时间值(用mutable

  • 测试常对象能否正常工作


下一篇预告 :第9篇《友元(friend):破坏封装的"特权"------真的有害吗?》------friend给了外部函数或类访问private成员的权限,就像给了你家钥匙。什么时候该用,什么时候是设计臭味?下一篇分析。

相关推荐
L_09072 分钟前
【C++】C++11 新特性
开发语言·c++
方也_arkling5 分钟前
【Java-Day15】API篇-ArrayList集合
java·开发语言
我是一颗柠檬8 分钟前
【Java后端技术亮点】动态路由权限(按钮级权限),细粒度控制到按钮级别
java·开发语言·后端·状态模式
Fanfanaas10 分钟前
C++ 继承
java·开发语言·jvm·c++·学习·算法
zzqssliu15 分钟前
taocarts 跨境独立站 SEO 优化实践(多语言 + 反向海淘场景)
java·javascript·php
前端Hardy15 分钟前
CSS 动画真的比 JS 快?Josh Comeau 做了组实验,结果跟直觉不一样
前端·javascript·后端
在繁华处23 分钟前
Java从零到熟练(十一):Spring框架入门
java·开发语言·spring
前端Hardy23 分钟前
前端日历组件,要变天了?Schedule-X v4.6 彻底杀疯了
前端·javascript·后端
十五年专注C++开发25 分钟前
cereal 库:C++ 序列化的轻量之选
开发语言·c++·序列化·反序列化·cereal
如此风景26 分钟前
UniCloud学习真经
javascript