一、this指针
在C++的类或者更严格意义上来说在类对象中存在一个指向其自身的指针,这个指针就是this指针。它是一个隐含的指针,即正常情况下不需要开发者主动定义和赋值即可使用。通过this指针可以访问其内部的成员。它的类型就是指向当前类类型的指针,需要提醒的是,一般情况下它都是在类对象内的函数中使用,它类似于Python类中函数的第一个显示默认指定的参数。这个在前面的C++23中的“Deducing This”机制正好是一名一暗。
先看一个经典的例子:
#include <iostream>
class A {
public:
void Display() {
std::cout << "this is test!" << std::endl;
//this->Print();//可以试试打开注释会是什么情况
}
void Print() { std::cout << "this is print!" << std::endl; }
};
int main() {
A *a = nullptr;
a->Display();
return 0;
}
这个程序是可以正常运行的。为什么?因为函数中没有访问涉及到this的内容,而代码段的函数是公用的。如果函数内有访问到类的非静态成员变量,则就会崩溃,因为对象并不存在,类内部的非静态成变量员在这种情况下是在堆上的,而堆不存在,所以崩溃。那么如果再有一个静态成员呢?静态成员在数据段,也提前在编译期就准备好了,同样是安全的。
二、编译期
要想从根本上明白上面空指针可以调用函数,需要从编译过程来看。在编译器编译的过程中,会把类内部的函数(这里指的普通函数)编译在代码段去(可以理解为全局函数编译行为),在每个函数的开始默认增加了一个this指针。既然是在参数中,那么就不会占用对象的空间大小,就意味着this指针在栈空间中。那么,同样,如果是同一个对象,它们的this指针是一个地址即它们共享一个this指针。也就是说在前面的例子中,编译器会把a->Display()编译成A::Display(&a),那个a就是this指向的对象,或者说&a=this。
这样,是不是就对上面的代码可以调用函数有了一个明确的理解,原来只是调用函数没有指向性的调用对象的内容,就没有问题。请大家注意,类不同的对象,它们有着不同的this指针,也就是说,this指针是和类的对象或实例绑定在一起的,而不是和类本身。
这也意味着静态函数和全局函数是没有这个this指针的,那么inline函数中有没有呢?大家可以想一想,特别是想一想,类内的内联,非类的内联,编译器的优化结果等等。
三、运行时
this指针也是有生命周期的,它在整个对象中函数调用的过程中始终有效。所以这就决定它在函数中可以一直使用它,不会有问题。在应用this时,对类的成员函数和变量都可以进行控制,而不同的对象有不同的this可以有效的防止在运行期的数据安全受到威胁。
this指针在运行时不允许修改即不能被重新赋值。这就意味着正常情况下,this指针也无法做++,–等改变地址的操作。
四、特殊应用
比较特殊的应用一般有两种:
1、返回this
返回this很多情况下是为了进行链式调用,所以,一定需要警惕会不会进入无限递归。
2、delete this
delete this的意思很明显就是删除自己。但是使用它有着很多需要注意的地方。首先,应该确保对象是在堆上分配的;其次,不能在析构函数中调用它,否则容易形成递归;最后使用它之后,对象的内存会被释放,但此时内存未必被系统真正回收,也就是说其处在一个不稳定态,此时访问有可能出现各种问题。所以一定要使用其的话要确保在对象生命周期的最后的时刻调用。
另外在使用this是有些情况下要引起注意,比如在构造函数未完成的情况下使用它(甚至有可能是空),有可能导致不可预料的情况发生。
五、总结
this是一个隐式的,默认的,自动的生成的一个指针,它通过编译器在编译行为期间进行控制而不需要开发者自己控制。可以将其理解为向开发者提供了一个可以更好的访问对象的接口。但在实际的开发中,很多开发者其实并没有很好的利用this指针。这就需要开发者们能充分认识到它的作用,发挥它的长处,形成一个好的开发习惯。