一文带你掌握 C++ 的 this 指针

本文详细解释了C++中this指针的作用,包括作为自引用指针的特性、在类成员函数中的使用、链式调用、内存占用以及在常量和非常量成员函数中的区别。还介绍了mutable关键字在特殊情况下的应用和注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

this 指针是 C++ 中用于实现对象自引用的一种机制,它在 C++ 中是一个特殊的指针,指向当前对象的地址。当我们在类的成员函数中访问类的成员时,实际上是通过 this 指针来访问的,它使得成员函数能够知道哪个对象正在调用它,并允许在成员函数中访问调用它的对象的成员,这种机制在编译时由编译器自动处理。

1. 常规用法

this 指针的一些主要特点和使用场景如下所示:

  • 自引用指针this 指针是一个隐式参数,指向调用方法的对象的地址。
    • 在 C++ 的类成员函数中,this 指针是一个隐式的参数,不需要程序员显式地传递。当调用一个对象的成员函数时,编译器自动将对象的地址作为一个隐式参数传递给该函数,这个隐式参数就是 this 指针。
  • 指针类型
    • this 指针的类型是指向类本身的指针,例如,在 MyClass 类的成员函数中,this 指针的类型是 MyClass*
    • 在类的常量成员函数中,this 指针的类型是指向常量对象的指针,例如 const MyClass*
  • 链式调用this 指针可以用于实现链式调用。通过返回 *this,可以连续调用同一个对象的多个成员函数。
  • 返回对象自身的引用:在成员函数中,可以使用 this 指针返回对象自身的引用,这在编写赋值运算符时特别有用。
  • 成员访问
    • 通过 this 指针,在类的非静态成员函数中,可以使用 this 指针来访问对象的成员变量和成员函数。
    • 在成员函数中,如果函数参数名称和成员变量名称相同,可以使用 this 指针来区分它们。例如,this->value = value
    • 由于静态成员函数不是通过对象调用的,因此它们没有 this 指针。
  • 编译器处理
    • 在编译阶段,编译器处理成员函数调用时,会把对象的地址作为一个隐式实参添加到每个非静态成员函数调用中。
    • 这就是为什么在成员函数内部,this 指针总是指向调用该成员函数的对象实例。

下面是一个简单的 C++ 示例,展示了 this 指针的使用:

class MyClass {
    int x;

public:
    MyClass(int x) {
        this->x = x; // 使用 this 指针区分成员变量和参数
    }

    MyClass& setX(int x) {
        this->x = x;  // 使用 this 指针区分成员变量和参数
        return *this; // 返回对象自身的引用
    }

    int getX() {
        return x;
    }
};

int main() {
    MyClass obj(10);
    obj.setX(20).getX(); // 链式调用
    return 0;
}

2. this指针的隐式转换

关于编译器如何处理 this 指针以使其指向当前对象实例,这涉及到 C++ 编译器在编译时对类成员函数调用的处理。

成员函数的内部表示

  • 当编译器遇到一个类成员函数时,它内部会将这个函数视为一个接收一个额外参数的普通函数,这个额外的参数就是 this 指针。
  • 例如,对于类 MyClass 的成员函数 void myMethod(int param),编译器内部可能会将其视为 void myMethod(MyClass* this, int param)

成员函数的调用

  • 当对一个对象调用成员函数时,例如 obj.myMethod(123),编译器会将这个调用转换为将对象地址作为 this 指针传递给函数的调用。例如,这可能被转换为 myMethod(&obj, 123)
  • 这意味着,成员函数内部对 this 的任何引用都指向调用该函数的对象实例。

考虑以下类定义和成员函数:

class MyClass {
public:
    int value;

    void setValue(int val) {
        this->value = val;
    }
};
int main() {
    MyClass obj;
    obj.setValue(10); // 对象调用成员函数
}

在这个例子中,当 obj.setValue(10) 被调用时:

  • 编译器在内部处理这个调用,将 obj 的地址作为 this 指针传递给 setValue 函数。
  • setValue 函数内部,this->value = val 实际上是 (&obj)->value = val 的简写。

3. this指针的内存占用问题

类的大小是由其成员变量决定的,不包括任何成员函数和 this 指针,无论类中定义了多少方法,包括虚函数,都不会改变其实例的大小。

this 指针虽然不会增加对象实例的内存大小,但它在成员函数被调用时需要占用栈内存来存储当前对象的地址,这通常是在函数调用的栈帧中进行的,占用一定的内存空间(通常是几个字节,取决于平台和编译器),而不是在对象的内存布局中。因此,this 指针本身并不增加类实例的大小。

4. 常量和非常量成员函数中的this指针

  1. 在非量常成员函数中this 是一个指向非常量的指针,它的类型是指向类类型的指针,比如 MyClass*。这意味着你可以通过 this 指针修改对象的成员。
  2. 在常量成员函数中this 是一个指向常量的指针,它的类型是指向常量类类型的指针,比如 const MyClass*,这表明在常成员函数中,你不能通过 this 指针修改对象的任何成员(除非它们被声明为 mutable)。

以下通过两个例子展示了在常成员函数和非常成员函数中 this 指针的使用差异。在常成员函数中,this 指针不能用于修改对象的成员变量,这保证了函数不会改变对象的状态。

非常量成员函数中的 this 指针

class MyClass {
public:
    int value;

    // 非常成员函数
    void setValue(int val) {
        this->value = val; // 可以通过 this 指针修改成员变量
    }
};

int main() {
    MyClass obj;
    obj.setValue(10); // 正常工作
    return 0;
}

在这个例子中,setValue 是一个非常成员函数。因此,this 指向 MyClass 类型的非常量对象。我们可以通过 this 指针修改对象的成员变量。

常量成员函数中的 this 指针

class MyClass {
public:
    int value;

    // 常成员函数
    int getValue() const {
        return this->value; // 可以通过 this 指针读取成员变量,但不能修改
    }

    // 尝试在常成员函数中修改成员变量将导致编译错误
    // void tryToSetValue(int val) const {
    //     this->value = val; // 编译错误:不能在常成员函数中修改成员变量
    // }
};

int main() {
    const MyClass obj{10};
    int val = obj.getValue(); // 正常工作
    // obj.tryToSetValue(20); // 如果取消注释,这将导致编译错误
    return 0;
}
  • 在这个例子中,getValue 是一个常量成员函数,this 指针是一个指向常量的 MyClass 类型的指针。
  • 我们可以通过 this 指针读取成员变量,但不能修改它们,如果尝试在常成员函数中修改成员变量,比如 tryToSetValue 函数那样,将导致编译错误。

mutable关键字

在常量成员函数中,只有 mutable 成员变量可以通过 this 指针被修改,这允许开发者在保持对象整体常量性的同时,修改某些特定的成员变量。

mutable 关键字在 C++ 中用于允许一个特定的类成员在常成员函数中被修改,这意味着即使在对象被声明为常量时,带有 mutable 修饰的成员变量仍然可以被修改。

在类成员变量声明中使用:将 mutable 关键字放在成员变量的声明之前,以标识这个成员可以在常成员函数中被修改。

假设类有一个需要在常成员函数中更新的计数器(例如,用于跟踪函数调用次数),可以使用 mutable 关键字来实现:

class MyClass {
public:
    mutable int counter; // 即使在常对象中也可以被修改的成员

    MyClass() : counter(0) {}

    void someFunction() const {
        // 这是一个常成员函数,即使在常成员函数中,也可以修改 counter
        counter++; 
    }
};

int main() {
    const MyClass obj;
     // 这将修改 counter,尽管 obj 是常量
    obj.someFunction();
    return 0;
}

在这个例子中,尽管 someFunction 是一个常成员函数,并且 obj 被声明为常量,counter 成员仍然可以被修改,这是因为 counter 被声明为 mutable

适用场景

  • 计数器和缓存:在对象状态逻辑上不变的情况下,用于跟踪额外信息(如调用次数)或作为缓存(例如,存储昂贵计算的结果)。
  • 并发控制:在多线程程序中,用于同步和锁定,即使在常成员函数中也可能需要修改这些成员。

注意事项

  • 谨慎使用mutable 应该谨慎使用,因为它破坏了常对象的不可变性原则。过度使用 mutable 可能会导致代码难以理解和维护。
  • 保持对象的逻辑常量性:即使成员被声明为 mutable,也应确保它不会改变对象的逻辑状态。通常,mutable 成员用于不影响对象外部可观察行为的情况。

欢迎来公众号交流,共同学习,共同进步,在这里有 C++、Golang、数据结构等面试考点,持续更新!
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Coder567

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值