关于类,初学者最常提出的疑问之一便是:“当调用某个成员函数时,C++ 如何知道该函数作用于哪一个对象?”
首先,我们定义一个简单的类作为示例。该类封装一个整数值,并提供若干访问函数用于读取和修改该值:
#include <iostream>
class Simple
{
private:
    int m_id{};
public:
    Simple(int id)
        : m_id{ id }
    {
    }
    int getID() const { return m_id; }
    void setID(int id) { m_id = id; }
    void print() const { std::cout << m_id; }
};
int main()
{
    Simple simple{1};
    simple.setID(2);
    simple.print();
    return 0;
}
程序输出符合预期:
2
当执行 simple.setID(2); 时,C++ 必须让 setID() 作用于对象 simple,并且使 m_id 实际上指的是 simple.m_id。
答案在于 C++ 使用了一个隐藏的指针——this!本课将深入探讨 this 的细节。
隐藏的 this 指针
在任何成员函数内部,关键字 this 都是一个 const 指针,保存当前隐式对象的地址。
通常我们不必显式提及 this,但下面示例证明我们可以这么做:
#include <iostream>
class Simple
{
private:
    int m_id{};
public:
    Simple(int id)
        : m_id{ id }
    {
    }
    int getID() const { return m_id; }
    void setID(int id) { m_id = id; }
    void print() const { std::cout << this->m_id; } // 使用 this 指针访问隐式对象,并通过 operator-> 选择成员 m_id
};
int main()
{
    Simple simple{ 1 };
    simple.setID(2);
    simple.print();
    return 0;
}
输出仍为:
2
实际上,下面两种 print() 实现完全等价:
void print() const { std::cout << m_id; }       // 隐式使用 this
void print() const { std::cout << this->m_id; } // 显式使用 this
编译器在编译时会自动在所有访问隐式对象的成员前加上 this->,既简化代码,又避免反复书写 this-> 的冗余。
提醒
使用 -> 可通过指向对象的指针选择成员。this->m_id 与 (*this).m_id 等价。
相关内容见《使用指针与引用进行成员选择》。
this 如何被设置?
再审视以下函数调用:
simple.setID(2);
尽管调用 setID(2) 看似只有一个实参,实际上有两个!编译器将该表达式改写为:
Simple::setID(&simple, 2); // 注意:simple 由对象前缀变为函数实参!
至此,这只是一次普通的函数调用,对象 simple 通过地址传递给函数。
但这只是故事的一半。由于调用时增加了一个形参,成员函数定义也必须相应调整以接收并使用该实参。原始定义如下:
void setID(int id) { m_id = id; }
编译器如何改写函数属于实现细节,但最终效果类似:
static void setID(Simple* const this, int id) { this->m_id = id; }
注意:
- 新增最左侧形参 this,类型为Simple* const(指针自身不可改,但可修改所指内容)。
- 成员 m_id被改写为this->m_id,利用this指针访问。
进阶提示
此处static表示该函数不与类对象关联,而视为位于该类作用域内的普通函数。详见《静态成员函数》。
总结
- 当我们写 simple.setID(2)时,编译器实际调用Simple::setID(&simple, 2),将simple的地址传入。
- 函数含隐藏形参 this,接收simple的地址。
- 函数内部所有成员变量前均隐式加 this->,因此this->m_id即simple.m_id。
好消息:上述过程完全自动,无需刻意记忆。只需记住:所有非静态成员函数均拥有一个指向被调用对象的 this 指针。
关键洞见
所有非静态成员函数均含有一个指向隐式对象的 const 指针 this。
this 始终指向被操作对象
初学者常疑惑存在多少个 this 指针。答案是:每个成员函数调用只有一个 this 形参,指向当前隐式对象。例如:
int main()
{
    Simple a{1}; // 在 Simple 构造函数中 this = &a
    Simple b{2}; // 在 Simple 构造函数中 this = &b
    a.setID(3);  // 在 setID() 中 this = &a
    b.setID(4);  // 在 setID() 中 this = &b
    return 0;
}
this 指针在调用 a 的成员函数时指向 a,在调用 b 的成员函数时指向 b。
由于 this 仅是函数形参(非数据成员),并不会增加类实例的内存占用。
显式引用 this
多数情况下无需显式使用 this,但在以下场景有用:
1. 解决命名冲突
若成员函数的形参与数据成员同名,可用 this 区分:
struct Something
{
    int data{}; // 未使用 m_ 前缀,因为是 struct
    void setData(int data)
    {
        this->data = data; // this->data 为成员,data 为局部参数
    }
};
某些开发者喜欢在所有成员前加 this-> 以显式表明其为成员。我们建议避免这种做法,因其降低可读性而收益甚微;使用“m_”前缀能更简洁地区分私有成员与局部变量。
2. 返回 *this 以实现函数链式调用
有时需要让成员函数返回隐式对象本身,以便在同一表达式中连续调用多个成员函数,称为函数链式调用(method chaining)。
以 std::cout 为例:
std::cout << "Hello, " << userName;
编译器按如下顺序求值:
(std::cout << "Hello, ") << userName;
若 operator<< 返回 void,则后续表达式 void{} << userName 非法。因此 operator<< 返回所传入的流对象(此处即 std::cout),从而实现链式输出。
我们可在自定义类中模拟此行为。以下类 Calc 初始版本:
class Calc
{
private:
    int m_value{};
public:
    void add(int value) { m_value += value; }
    void sub(int value) { m_value -= value; }
    void mult(int value) { m_value *= value; }
    int getValue() const { return m_value; }
};
调用:
calc.add(5);
calc.sub(3);
calc.mult(4);
若让各函数返回 *this 的引用,则可链式调用:
class Calc
{
private:
    int m_value{};
public:
    Calc& add(int value) { m_value += value; return *this; }
    Calc& sub(int value) { m_value -= value; return *this; }
    Calc& mult(int value) { m_value *= value; return *this; }
    int getValue() const { return m_value; }
};
于是:
calc.add(5).sub(3).mult(4);
逐步求值:
- calc.add(5)→- m_value = 5,返回- calc引用;
- calc.sub(3)→- m_value = 2,返回- calc引用;
- calc.mult(4)→- m_value = 8,返回- calc(未再使用)。
最终 m_value 为 8。
这是 this 最常见的显式用法之一,每当需要链式调用时应考虑采用。
将类重置为默认状态
若类拥有默认构造函数,可提供 reset() 以将现有对象恢复默认状态。
注意:构造函数仅用于初始化新对象,不得直接调用,否则行为未定义。
最佳做法是在 reset() 内创建临时默认对象,再赋值给当前隐式对象:
void reset()
{
    *this = {}; // 值初始化新对象并覆盖隐式对象
}
完整示例:
#include <iostream>
class Calc
{
private:
    int m_value{};
public:
    Calc& add(int value) { m_value += value; return *this; }
    Calc& sub(int value) { m_value -= value; return *this; }
    Calc& mult(int value) { m_value *= value; return *this; }
    int getValue() const { return m_value; }
    void reset() { *this = {}; }
};
int main()
{
    Calc calc{};
    calc.add(5).sub(3).mult(4);
    std::cout << calc.getValue() << '\n'; // 输出 8
    calc.reset();
    std::cout << calc.getValue() << '\n'; // 输出 0
    return 0;
}
this 与 const 对象
- 对于非 const 成员函数,this是“指向非 const 对象的 const 指针”(指针不可改,所指对象可改)。
- 对于 const 成员函数,this是“指向 const 对象的 const 指针”(指针与对象均不可改)。
若试图在 const 对象上调用非 const 成员函数,编译器报错信息可能晦涩:
error C2662: 'int Something::getValue(void)': cannot convert 'this' pointer from 'const Something' to 'Something &'
error: passing 'const Something' as 'this' argument discards qualifiers [-fpermissive]
原因在于:
非 const 成员函数的隐式 this 形参是“指向非 const 对象的 const 指针”;而实参类型却是“指向 const 对象的 const 指针”。将“指向 const 的指针”转换为“指向非 const 的指针”需要丢弃 const 限定符,而这是不允许的隐式转换。
为何 this 是指针而非引用
既然 this 始终指向隐式对象(且除非触发未定义行为,否则不会为 null),为何它被设计为指针而非引用?
答案很简单:引入 this 时,C++ 尚无引用。若今天才添加 this,它无疑会是引用。在现代类 C++ 语言(如 Java、C#)中,this 均以引用形式实现。
