this
指针是 C++ 中用于实现对象自引用的一种机制,它在 C++ 中是一个特殊的指针,指向当前对象的地址。当我们在类的成员函数中访问类的成员时,实际上是通过 this
指针来访问的,它使得成员函数能够知道哪个对象正在调用它,并允许在成员函数中访问调用它的对象的成员,这种机制在编译时由编译器自动处理。
1. 常规用法
this
指针的一些主要特点和使用场景如下所示:
- 自引用指针:
this
指针是一个隐式参数,指向调用方法的对象的地址。- 在 C++ 的类成员函数中,
this
指针是一个隐式的参数,不需要程序员显式地传递。当调用一个对象的成员函数时,编译器自动将对象的地址作为一个隐式参数传递给该函数,这个隐式参数就是this
指针。
- 在 C++ 的类成员函数中,
- 指针类型:
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指针
- 在非量常成员函数中:
this
是一个指向非常量的指针,它的类型是指向类类型的指针,比如MyClass*
。这意味着你可以通过this
指针修改对象的成员。 - 在常量成员函数中:
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、数据结构等面试考点,持续更新!