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 关系图 对象 (Object) this 指针 指向当前对象 const 成员函数 承诺不修改对象 mutable 成员 const 中也可修改 核心逻辑:const 函数通过 this 访问对象,但不可修改非 mutable 成员 mutable 是 const 中的「例外通道」,用于缓存、计数等场景

这张图你看懂了吗?简单总结就是: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?」


公众号:蓝海资料掘金营,微信deep3321