21. this指针与常对象:this指针、const成员函数、mutable关键字
好,咱们今天聊点实在的。this指针、const成员函数、mutable关键字,这三个东西在C++里经常一起出现。很多初学者觉得它们很绕,其实说白了,就是C++为了让你既能保护数据,又能在必要时灵活修改,设计的一套规则。
我个人习惯,讲这种底层概念时,先别急着背语法。先想想:为什么需要它们?
21.1 this指针:每个对象都知道自己是谁
先问个问题:你写了一个类,里面有成员函数。当你调用 obj.func() 时,函数内部怎么知道它操作的是哪个对象的成员变量?
答案就是 this指针。
this指针是每个非静态成员函数内部隐含的一个指针。它指向调用该函数的那个对象本身。说白了,它就是对象的「自引用」。
核心理解: this 是一个 const 指针(类型为 ClassName * const),你不能改变它指向的对象,但可以修改对象的内容(除非函数是 const 的)。
来看个最简单的例子:
class Student {
private:
int id;
public:
void setId(int id) {
// 这里如果不加 this,id = id 就是自己赋值给自己,毫无意义
this->id = id; // 左侧是成员变量,右侧是参数
}
Student& returnSelf() {
return *this; // 返回当前对象的引用
}
};
我在项目中遇到过一种情况:链式调用。比如你要连续设置多个属性,返回 *this 就特别方便。像这样:
class Config {
public:
Config& setHost(const std::string& h) { host = h; return *this; }
Config& setPort(int p) { port = p; return *this; }
private:
std::string host;
int port;
};
// 使用
Config cfg;
cfg.setHost("localhost").setPort(8080); // 链式调用,清爽!
嗯,这里要注意:静态成员函数里没有 this 指针。因为静态函数属于类本身,不属于某个具体对象。你想想看,如果连对象都没有,哪来的「自己」呢?
21.2 const成员函数:承诺不改数据
接下来是 const 成员函数。这东西我刚开始学的时候觉得有点多余——「我不改数据不就行了?加个 const 多麻烦。」
后来被坑了一次才明白:const 成员函数不是写给你看的,是写给编译器看的。
当你把一个对象声明为 const 时,比如 const Student s;,编译器会严格限制:只能调用该对象的 const 成员函数。为什么?因为非 const 成员函数有可能修改对象,而 const 对象不允许被修改。
我曾经踩过的坑: 写了一个打印函数,参数是 const 引用,结果函数内部调用了某个非 const 的成员函数。编译直接报错。当时我还纳闷:「我又没改数据,只是打印一下啊!」后来才意识到,编译器只看函数声明,不看函数实现。你声明为非 const,编译器就认为你有修改的可能。
正确的写法是这样的:
class Book {
private:
std::string title;
int pages;
public:
// const 成员函数:承诺不修改成员变量
std::string getTitle() const {
return title; // 只读,没问题
}
int getPages() const {
return pages; // 只读,没问题
}
// 非 const 成员函数:可以修改
void setPages(int p) {
pages = p; // 修改,没问题
}
};
void printBook(const Book& b) {
// b 是 const 引用,只能调用 const 成员函数
std::cout << b.getTitle() << std::endl; // OK
// b.setPages(200); // 编译错误!setPages 不是 const
}
const 成员函数内部,this 指针的类型变成了 const ClassName *。也就是说,你不能通过 this 修改任何成员变量。这是编译器帮你做的保障。
小技巧: 如果一个成员函数逻辑上不应该修改对象,就把它声明为 const。这不仅是语法要求,更是一种设计契约。别人看到你的 const 函数,就知道调用它是安全的。
21.3 mutable关键字:const中的例外
好,现在问题来了:const 成员函数里不能修改成员变量,这是铁律。但有时候,你确实需要在一个 const 函数里修改某个变量——比如缓存、计数器、调试信息等。
这时候 mutable 就派上用场了。
mutable 的中文意思是「可变的」。它告诉编译器:即使对象是 const 的,这个成员变量也可以被修改。
我举个例子,你在项目中可能遇到过:
class Database {
private:
std::string data;
mutable int accessCount = 0; // 统计访问次数,即使 const 对象也要能改
public:
std::string getData() const {
accessCount++; // 没问题,因为 accessCount 是 mutable 的
return data;
}
int getAccessCount() const {
return accessCount;
}
};
void test(const Database& db) {
std::string s = db.getData(); // 调用 const 函数,但内部修改了 accessCount
std::cout << db.getAccessCount(); // 输出 1
}
你看,db 是 const 引用,但 getData() 内部修改了 accessCount。这就是 mutable 的威力。
注意: mutable 不能滥用。它本质上是绕过了 const 的保护机制。我个人的原则是:只有当你修改的变量不影响对象的「逻辑常量性」时,才使用 mutable。比如缓存、日志、统计信息这些,改了也不影响对象的核心数据。
如果你把核心业务数据也声明为 mutable,那 const 就形同虚设了。你想想看,那还不如直接去掉 const 呢,对吧?
21.4 三者关系:一张图说清楚
为了帮你理清思路,我画了一张图。这张图展示了 this 指针、const 成员函数、mutable 关键字之间的核心关系:
这张图你看懂了吗?简单总结就是:this 是对象的身份证,const 是函数的承诺书,mutable 是承诺书里的「免责条款」。
21.5 实战中的避坑指南
最后,我结合自己的经验,给你列几个实际开发中容易踩的坑:
| 场景 | 错误做法 | 正确做法 |
|---|---|---|
| const 对象调用非 const 函数 | const Obj o; o.func(); 编译报错 |
将 func 声明为 const,或去掉对象的 const |
| 在 const 函数中修改成员 | 直接修改普通成员变量 | 将该成员声明为 mutable,或重新设计逻辑 |
| this 指针在静态函数中使用 | static void f() { this->x; } 编译报错 |
静态函数没有 this,改用参数传递对象 |
| 链式调用忘记返回引用 | 返回 Obj 而非 Obj&,导致拷贝 |
返回 *this 的引用,避免不必要的拷贝 |
我个人习惯: 写类的时候,先把所有只读的成员函数都加上 const。如果后续发现某个变量需要在 const 函数里修改,再考虑加 mutable。这样设计出来的接口更安全,也更清晰。
好了,关于 this 指针、const 成员函数和 mutable 关键字,今天就聊到这里。这些东西看似简单,但用好了能让你的代码更健壮、更优雅。下次写类的时候,不妨多想想:「这个函数该不该加 const?这个变量要不要 mutable?」