目录
一、一个编译错误引发的思考
二、const成员函数:只读的承诺
语法
为什么要区分?
三、mutable:const函数里的“例外”
基本用法
什么时候用mutable?
mutable的误解
四、重载:const版本和非const版本
五、完整例子:学生成绩管理系统
六、三个常见误区
1. 以为const成员函数不能调用非const函数(正确)
2. 混淆const位置
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处:❌ 错误。
s是const对象,getName()不是const函数,可能修改对象第2处:✅ 正确。
s是const,getScore()是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不应该改变对象(它只是输出)。这时候有两种选择:
去掉
const——但这样常对象就不能调用print了用
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成员函数)常函数承诺不修改对象,
this是const T* constmutable成员可以在常函数中被修改,用于缓存、计数、锁等内部状态可以重载
const和非const版本的函数,编译器根据对象类型选择
💡 小作业:写一个
Timer类,记录代码执行时间。要求:
start()和stop()是非const函数
getElapsed() const是const函数,但内部需要修改缓存的时间值(用mutable)测试常对象能否正常工作
下一篇预告:第9篇《友元(friend):破坏封装的“特权”——真的有害吗?》——friend给了外部函数或类访问private成员的权限,就像给了你家钥匙。什么时候该用,什么时候是设计臭味?下一篇分析。