关于C++的this指针学习总结
- 需要回答的问题
- this指针的认识
- this指针的本质(一句话总结)
- this的类型
- this的存储位置
- this指针的作用
- 1.区分同名变量
- 2.链式调用/连续赋值/连续调用(return *this)
- 3.访问当前对象的地址或成员
- this指针的使用(this指针与const的关系)
- 系统运行时,会做以下的事情:
- 常方法
- const 与 static 的关系
- this可以为nullptr吗?
- this 和智能指针有什么关系?
- 为什么不能在构造函数里调用虚函数并期望多态?
- this和智能指针有什么关系?
- const 与 friend
需要回答的问题
- this指针是什么?
- this存在哪里?
- 编译器怎么传递this?
- const成员函数里this是什么类型?
- this能为空吗?
- 为什么要用this指针?
- 静态函数为什么没有this指针?
…
参考链接(整合了一下自己的理解和原文中更凝练的总结)
链接: 卡码笔记this指针原理
this指针的认识
首先,我们知道对象是属性和方法的集合,但如果每次创建对象时,都为其开辟属性+方法的内存,会消耗不必要的内存空间。
eg :这里有相同类型的三个不同对象 a,b,c,属性不同但方法相同,若给每个对象都创建专属方法,会消耗对应的内存,但因为方法相同,可以共用同一套方法,进而节省空间,如下图。
这样做虽然节省了空间,但是方法无法区分调动的是a,b,c哪个对象的属性,所以为了其区分对象属性,需要将对象的地址传入,进而需要用到this指针。
代码展示
#include <iostream> using namepsace std; class Test { int num; public: Test(int x):num(x){} void Print() { cout << this << endl; } //打印this指针 }; int main() { Test a(1), b(2), c(3); cout << "&a=" << &a << endl; //打印a的地址 cout << "&b=" << &b << endl; //打印b的地址 cout << "&c=" << &c << endl; //打印c的地址 a.Print(); b.Print(); c.Print(); return 0; }运行结果 可以看到分为这样结果的三组 表明对象地址和this打印结果相同
总结:
this指针指向调用该函数的对象
this指针存在的根本意义是为了节约空间
this指针的本质(一句话总结)
this指针是编译器隐式传给非静态成员函数的第一个参数,类型是指针常量Type* const。调用obj.func(x)时,编译器实际生成的是fun(&obj,x),把对象的地址作为第一个实参传入。this通常通过寄存器传递(x86用ecx,即thiscall约定),不占用栈空间。const成员函数的this类型就是const Type* const,所以不能修改其成员。静态成员函数无this,因为它不绑定任何对象实例。
this的类型
普通成员中:this类型是T* const,也就是指针常量,所谓指针常量就是本质是一个常量,限制的是指针,所以this的指向无法指向其他对象,但指向的对象不是常量(可以修改成员);
const成员函数中:this类型变成const T* const,指针本身不可变,指向的对象也不可变。所以无法通过this修改任何非mutable成员。
this的存储位置
this是函数参数,具体存在哪取决于调用约定。x86平台的thiscall约定中,this通过ecx寄存器传递,不占栈空间。x64和ARM平台也通常用寄存器传递前几个参数。只有在函数内部需要取this的地址时,编译器才会把它溢出到栈上。
| 平台 | 寄存器 |
|---|---|
| x86 | ecx |
| x64 | rdi / rcx |
| ARM | r0 |
this指针的作用
1.区分同名变量
示例:
class Test { int num; public: Test(int num) :num(num) { //构造函数 num =100; cout << "num= " << num <<" ,"; cout <<"this->num= " << this->num << endl; } void Print( ) { //无参打印函数 int num = 100; num = num; cout << "num= " << num << " ,"; cout << "this->num= " << this->num << endl; } void Print(int num) { //有参打印函数 num = num; cout << "num= " << num << " ,"; cout << "this->num= " << this->num << endl; } }; int main() { Test t1(10); t1.Print(); t1.Print(100); return 0; }运行结果如下:
可以看到三个函数体内有关打印的代码一致,先打印局部变量num,再打印成员变量num
第1行对应构造函数的结果 num=100 this->num =10
第2行对应有参打印函数的结果 num=100 this->num =10
第3行对应无参打印函数的结果 num=100 this->num =10
则在函数体中 num=100,num=num 其实都是对局部变量的改变,
即num=100,是对局部变量的值进行修改
num=num,是局部变量给自身赋值
由此可知:
- 在构造函数中初始化列表的num,系统可以区分同名的成员变量和局部变量,并正确赋值,也就是说num(num) 本质上等价于 this->num = num。
- 在打印函数和构造函数体中,不论是形参名还是变量名,只要与成员变量名相同,必须用this指针来和成员变量区分,否则系统会优先选择局部变量而不是成员变量。
- 若函数体中不存在和成员变量名相同的局部变量,则系统可以正常识别成员变量
2.链式调用/连续赋值/连续调用(return *this)
解引用 this 得到当前对象本身(类型是 T&),返回它就是返回对象的引用。链式调用 obj.set(1).set(2).set(3) 就是靠每个方法 return *this 实现的——每次返回的都是同一个对象的引用,所以可以继续调用它的方法。
在重载赋值函数中常见,代码如下:
class Test { int num; public: Test(int num) :num(num) { } Test& operator=(const Test& t) { //重载赋值函数 if (this != &t) { num = t.num; } return *this; } void Print() { cout << num << endl; } }; int main() { Test t1(10); Test t2 = t1; t1.Print(); t2.Print(); return 0; }运行结果如下:
这里的Test t2= t2 相当于得到 t2.operaor=(t1)的返回值
这里返回类型为Test&搭配*this 可以更高效的返回对象本身
3.访问当前对象的地址或成员
这里比较好理解,用最简单的方法呈现一下:
class Test { int num; public: Test(int num) :num(num) { cout << "this= " << this; cout << " this->num= " << this->num << endl; } }; int main() { Test t1(10); return 0; }结果如下:
this指针的使用(this指针与const的关系)
除了知道this指针上述三个作用外,还需要知道系统是如何去使用this指针,进而理解this指针与const的关系。
首先要始终牢记,this指针指向对象,再去解释后面的内容。
系统运行时,会做以下的事情:
- 识别类内的属性,包括:value(值) int(类型) private(访问类型)
- 识别类内的方法的声明(暂时不看方法体)
- 改写 即编译时添加形参 返回值 添加this指针
- 修改调用 传入this指针
示例如下:
class Test{ int value; public: void SetValue(int x) { value = x; } //改写为 //void SetValue(Test* const this,int x){this->value = x;} int GetValue() { return value; } //改写为 //int GetValue(Test* const this){return this->value;} }; int main() { Test t1; t1.SetValue(200); //修改为 t1.SetValue(&t1,200); cout << t1.GetValue() << endl; //修改为 t1.GetValue(&t1); return 0; }系统去改写就是为了传入当前对象的地址,值得注意的时这里系统还加入了const
如void SetValue(Test* const this,int x),是为了防止在函数体中对this本身进行修改,如this=nullptr等改变this指向的操作,确保了代码的正确与安全。
常方法
系统传入this指针时,会自己加入const ,如Test* const this,
有时我们还会自己定义常方法,在函数名末尾加const
示例:
class Test{ int value; public: void SetValue(int x) { value = x; } int GetValue() const { return value; } //改写为 //int GetValue(const Test* const this){return this->value;} }; int main() { Test t1; t1.SetValue(200); cout << t1.GetValue() << endl; return 0; }这里让 GetValue 变为常方法 int GetValue() const
加入该 const 后,系统在改写时还会对this指针再加一个const修饰
改写为:int SetValue(const Test* const this,int x),让this指针变成只读指针,无法修改this的指向,也无法修改this本身,从而实现常方法。
注意:
- 常对象只能调用常方法 普通函数都可以调用
- int fun(); int fun()const; const可以作为函数重载的依据,因为有const所以形参列表不同
- 同名同返回值的普通函数和常方法 普通对象优先调用普通方法 常对象调用常方法
- 对于不修改对象属性值的方法,让其定义或重载为常方法,可以提高代码灵活性
const 与 static 的关系
1.常方法与静态变量之间的关系
示例:
class Test { int value; static int num; public: void fun() const { cout << ++num << endl; } }; int Test::num = 0; int main() { Test t1; t1.fun(); return 0; }运行结果为1
静态变量不为某个对象所有,是为整个类型服务的,总共只有一份
静态变量也就不受制与 this 指针,而常方法修饰的是this指针,所以代码正常运行,num结果+1。
2.静态方法与const的关系
静态方法可以直接调用,不需要用this指针来区分
静态成员函数属于类而不属于任何对象实例,调用时不需要对象(T::staticFunc()),编译器不会传入对象地址,所以没有 this。这也是静态函数不能访问非静态成员的根本原因——没有对象地址,无法定位成员的内存位置。
class Test { int value; static int num; public: // static void fun() const { //error static void fun() { cout << num << endl; //cout << value << endl; //error } }; int Test::num = 0; int main() { Test t1; t1.fun(); return 0; }注意:
- 静态方法不需要this也可以调用,系统不会添加this指针 那么也无法在函数名末添加const
- 静态方法无this指针,无法区分对象,也无法访问对象成员
this可以为nullptr吗?
语法上可以构造出来(((T*)nullptr)->func()),但这是未定义行为。如果 func 内部不访问任何成员变量,某些编译器下可能不崩溃,但这纯属巧合,不能依赖。任何通过空 this 访问成员的操作都会段错误。
this 和智能指针有什么关系?
this 是裸指针,不管理生命周期。如果需要在对象内部获取指向自身的 shared_ptr,不能直接 shared_ptr(this)(会导致双重释放),必须继承 std::enable_shared_from_this,然后调用 shared_from_this()。
为什么不能在构造函数里调用虚函数并期望多态?
构造函数执行时,对象的动态类型还是当前正在构造的类(vptr 指向当前类的 vtable),this 的静态类型也是当前类。所以即使调用虚函数,也只会调到当前类的版本,不会调到派生类的重写版本。
this和智能指针有什么关系?
this是裸指针,不管理生命周期。如果需要在对象内部获取指向自身的shared_ptr,不能直接shared_ptr(this) 会导致双重释放,必须寂静城std::enable_shared_from_this,然后调用shared_from_this()。
const 与 friend
友元函数 属于外部函数
class Test { int value; public: Test(int x):value(x){} friend void fun(const Test& b); }; void fun(const Test& b) {cout << b.value << endl;} int main() { Test t1(10); fun(t1); return 0; }友元函数,无this指针,也不自动添加,无法用const修饰