目录
[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处:❌ 错误。
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* const -
**
mutable**成员可以在常函数中被修改,用于缓存、计数、锁等内部状态 -
可以重载
const和非const版本的函数,编译器根据对象类型选择
💡 小作业:写一个
Timer类,记录代码执行时间。要求:
start()和stop()是非const函数
getElapsed() const是const函数,但内部需要修改缓存的时间值(用mutable)测试常对象能否正常工作
下一篇预告 :第9篇《友元(friend):破坏封装的"特权"------真的有害吗?》------friend给了外部函数或类访问private成员的权限,就像给了你家钥匙。什么时候该用,什么时候是设计臭味?下一篇分析。