news 2026/5/13 8:16:06

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【c++面向对象编程】第8篇:const成员与mutable:常对象与常函数

目录

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

二、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处:❌ 错误。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成员的权限,就像给了你家钥匙。什么时候该用,什么时候是设计臭味?下一篇分析。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/13 8:15:17

3DMigoto深度解析:GIMI框架下的原神模型导入技术实现方案

3DMigoto深度解析&#xff1a;GIMI框架下的原神模型导入技术实现方案 【免费下载链接】GI-Model-Importer Tools and instructions for importing custom models into a certain anime game 项目地址: https://gitcode.com/gh_mirrors/gi/GI-Model-Importer Genshin Imp…

作者头像 李华
网站建设 2026/5/13 8:13:25

告别鼠标手!用Zutilo为Zotero打造全键盘流操作环境(Windows/Mac通用)

告别鼠标手&#xff01;用Zutilo为Zotero打造全键盘流操作环境&#xff08;Windows/Mac通用&#xff09; 如果你每天在Zotero中花费数小时整理文献&#xff0c;频繁在鼠标和键盘之间切换不仅效率低下&#xff0c;长期还可能导致手腕疲劳甚至重复性劳损。Zutilo这款轻量级插件能…

作者头像 李华
网站建设 2026/5/13 8:13:24

classmcp:为AI前端开发降本增效的CSS语义化工具

1. 项目概述&#xff1a;为AI助手装上CSS语义化的“快捷键”如果你和我一样&#xff0c;日常重度依赖Claude、Cursor这类AI助手来加速前端开发&#xff0c;那你一定对下面这种场景深恶痛绝&#xff1a;当你让AI生成一个简单的按钮时&#xff0c;它给你吐出一长串令人眼花缭乱的…

作者头像 李华
网站建设 2026/5/13 8:12:25

硬件仿真:从芯片验证奢侈品到工程必需品的实战演进

1. 硬件仿真&#xff1a;从“奢侈品”到“必需品”的演进又到了年底复盘的时候。翻看过去一年在行业里的记录和项目笔记&#xff0c;我发现一个贯穿始终的主题&#xff0c;它不再是某个尖端技术的炫技&#xff0c;而是一种实实在在的“工程保险”——硬件仿真。十几年前&#x…

作者头像 李华
网站建设 2026/5/13 8:10:31

OBS多平台同步推流终极指南:3步实现一键多平台直播

OBS多平台同步推流终极指南&#xff1a;3步实现一键多平台直播 【免费下载链接】obs-multi-rtmp OBS複数サイト同時配信プラグイン 项目地址: https://gitcode.com/gh_mirrors/ob/obs-multi-rtmp 你是不是经常需要在YouTube、Twitch、Bilibili等多个平台同时直播&#x…

作者头像 李华