C++入门系列5:this指针
前言:C++入门系列文章,是按照鲍松山老师的C++入门课程进行的学习整理与记录,方便自己同时也方便一些编程新手,更好地去理解编程中许多概念和技术产生的原因,由来和发展过程,希望通过逻辑帮助自己更快也更长久地理解和应用。
尊重原创,因此先附上鲍老师的视频链接:https://edu.51cto.com/course/4940.html,感谢鲍老师的精心备课,悉心讲解与耐心传授。
1:this指针
1.1 类的字节大小如何计算?
我们先不谈this指针,1.1,先提第一个问题,我在C++里面定义了一个类,一个类的字节大小,你知道该如何计算么?
首先我们新建一个c++的类,这个类有4个私有成员变量:
1)商品名字:char Name[21]; 2)商品数量:int Amount;
3)商品单价:float Price; 4)商品总价:float Total_value;
#include <iostream>
#include<cstring>
using namespace std;
class CGoods
{
public:
//初始化该商品
void RegisterGoods(char name[], int amount, float price)
{
strcpy(Name, name);
Amount = amount;
Price = price;
}
void GetName(char name[])
{
strcpy(name, Name);
}
int GetAmount(void)
{
return Amount;
}
float GetPrice(void)
{
return Price;
}
//总价=单价 × 数量
void CountTotal(void)
{
Total_value = Price * Amount;
}
float GetTotal_value(void)
{
return Total_value;
}
private:
char Name[21];
int Amount;
float Price;
float Total_value;
};
int main()
{
//1 CGoods类的大小如何计算?
cout << "sizeof CGoods is: " << sizeof(CGoods) << endl;
//2 对象如何给自己的数据进行赋值?
CGoods t1, t2;
t1.RegisterGoods("apple", 5, 1);
t2.RegisterGoods("orange", 6, 2);
char name[21];
t1.GetName(name);
cout << "\nt1的名字:" << name << endl;
t2.GetName(name);
cout << "t1的名字:" << name << endl;
return 0;
}
运行程序后的结果如下:
sizeof CGoods is: 36
t1的名字:apple
t1的名字:orange
Process returned 0 (0x0) execution time : 0.001 s
Press ENTER to continue.
现在开始1.1的问题,一个类的大小如何计算?
由结果显示,CGoods类的大小为36。计算方式如下:
24(Name字段大小21,由于需要补齐为4的倍数,所以为24) + 4(int类型)+ 4(float类型)+ 4(float类型)= 36
一个类里面包含数据和方法,按照上述计算方式和看到的结果,我们可以推测出,类在内存中的保存方式应该是图1,而不是图2,即类所创建的对象保存各自的数据,而类的成员方法则是所有对象共享的。因为既然方法是公有的,每个对象都可以调用的,所以实在没有必要为每个对象都保存函数的存储。
图1:对象仅保存自己的成员数据
图2:对象同时保存成员数据和成员方法
1.2 对象是如何给自己的数据进行赋值的?
现在开始1.2的问题,当我们运行:
t1.RegisterGoods("apple", 5, 1);
想问的问题是,RegisterGoods成员函数是怎么知道给t1的Name赋值为“apple”的?我们看一下RegisterGoods成员函数:
void RegisterGoods(char name[], int amount, float price)
{
strcpy(Name, name);
Amount = amount;
Price = price;
}
似乎并没有看出来,c++的这个类的函数是怎么知道给t1的Name赋值的。。
好的,到这里,就可以引出本节的核心了:this指针!
1.3 先看一下C语言是如何给自己的数据进行赋值的?
我们同样创建了一个c语言中的CGoods的结构体,程序代码如下:
#include <iostream>
#include<cstring>
using namespace std;
struct CGoods
{
char Name[21];
int Amount;
float Price;
float Total_value;
};
void RegisterGoods(CGoods *ps, char name[], int amount, float price)
{
strcpy(ps->Name, name);
ps->Amount = amount;
ps->Price = price;
}
int main()
{
CGoods t1, t2;
RegisterGoods(&t1, "apple", 5, 1);
RegisterGoods(&t2, "orange", 6, 2);
return 0;
}
简单分析一下:
我们创建了2个结构体CGoods, t1, t2, 当我们要给t1进行初始化注册商品时,我们传入的第一个变量是t1的地址,然后RegisterGoods函数中的ps指针接收了该地址的值,最后再将所有初始化的值传给该地址下的结构体里面的数据即可。
仔细看一下这段c代码和上一部分中的C++的代码,是不是可以联想到,是不是C++也帮我们做了同样的操作,只是是隐式的呢?事实上,如果C++里面把上面的ps指针变量名统一全部改为this,并且是C++帮我们隐式地做了转换。所以刚刚抛出了1.2的问题,即RegisterGoods成员函数是怎么知道给t1的Name赋值为“apple”的?现在可以很好地理解了,其实C++帮我们隐式地传递了t1的地址,然后成员函数有一个this指针接收了该地址,并给该地址里面的成员数据进行赋值。
这里说明下C++中对类的处理顺序:
1)识别CGoods类
2)识别类中的数据成员(所以我们的函数虽然在类的上面,但是却可以调用位于函数代码下方的数据成员)
3)改写函数:
eg:我们调用成员函数时,C++会增加一个参数,即对象自己的地址
// 这是我们的代码
t1.RegisterGoods("apple", 5, 1);
// 这是C++改写后的代码
RegisterGoods(&t1, "apple", 5, 1);
成员函数运行时候,C++也会增加一个参数,即该类的指针且该指针名字必须为this,这就是this指针,代表的是当前对象的地址。所以我们虽然只看到类的成员函数只有2个参数,实际在运行时应该是3个,其他的以此类推。即不管我们看没看到,实际当对象调用该类的成员函数时,隐式地存在一个this指针,当然我们自己也可以显式地写出来。
// 这是我们看到的代码
void RegisterGoods(char name[], int amount, float price)
// C++改写后的代码
void RegisterGoods(CGoods *this, char name[], int amount, float price)
好的,至此,1.2 的问题,C++的对象是如何给自己的数据赋值的,以及C++里面为啥要有this指针这个概念也已经清楚了。
2:const常量的使用
上面我们写了,this指针,我们自己也可以显式地写出来,那么有一个问题来了,如果有人在成员函数里面故意修改this指针呢?那不是就无法正确给对象自己的成员数据赋值了么?
比如,我故意在成员函数里面赋值为空:
// 初始化该商品
void RegisterGoods(char name[], int amount, float price)
{
this = NULL; //故意赋值为空!!!
strcpy(Name, name);
Amount = amount;
Price = price;
}
当我们加了一行故意赋值为空后,编译也开始报错了,那么这又是为什么呢?
这是因为之前,之前C++对成员函数的改写,我们并没有写完整,接下来写一下完整的改写后的成员函数:
// 这是我们看到的代码
void RegisterGoods(char name[], int amount, float price)
// C++改写后的代码
void RegisterGoods(CGoods *this, char name[], int amount, float price)
// C++改写后的完整的代码
void RegisterGoods(CGoods *const this, char name[], int amount, float price)
看清楚了么?完整的改写后的代码里面是:CGoods *const this,即多了一个const
那么为什么多了一个cosnt就开始报错了呢?const的作用是什么呢?
以下进行举例说明!
const int a = 0; //a)const封锁变量a为常量,所以a的值之后不能再修改了;
const int* p = &a; //b)const在*的左边,封锁的是*p,所以
//1)无法通过指针p来修改a的值;
//2)但是可以修改p指向其他int变量的地址;
int* const p = &a; //c)const在*的右边,封锁的是p,p是一个指针,所以
//1)指针p之后只能指向a的地址,不能再修改了;
//2)但是可以通过p指针修改a的值
const int* const p = &a; //d)指针p在*的两边都有,说明既封锁了p,也封锁了*p,所以
//1)指针p之后只能指向a的地址,不能再修改了;
//2)同时也不可以通过p指针修改a的值
好的,现在我们再回过去看刚刚提到的问题,C++对改写后的完整的成员函数如下:
// C++改写后的完整的代码
void RegisterGoods(CGoods *const this, char name[], int amount, float price)
const在*的右边,封锁的是指针this,因此属于上述的情形c,所以指针this之后不能再修改了,所以我们之前说,故意赋值为空,即this=NULL;运行之后报错了,原因就在于此。
2.1 查看this指针
接下来通过2个小栗子来查看一下this指针:
第1个栗子展示一下this指针的产生与释放:
STEP1:
在调用 t1.RegisterGoods(“apple”, 5, 1); 之前,this指针是不存在的,程序调试的截图如上。
可以看到此时t1的地址是da30,t2的地址是da60
STEP2:当我们进入RegisterGoods成员函数后,这时this指针存在并开始有值,其值为da30,即我们t1对象的地址。
STEP3:
当运行结束: t1.RegisterGoods(“apple”, 5, 1);之后,this指针再次释放不存在了,此时t1对象已经初始化结束。t2对象初始化的过程与此类似。
第2个栗子是我们可以显式地写出this指针
当我们的变量名名字和类的私有变量名字相同时,作为函数,它怎么知道哪个Name是该对象的私有变量,哪个Name又是函数传过来值的变量呢?这个时候我们可以显式地写出this指针,来解决这个问题:
如上图所示,当我们显示地写出this指针后,程序就知道Name是函数传过来的值变量,this->Name是对象的变量。
3:本节小结
本节的重点是理解为什么会在C++里面存在this指针这个概念,另外需要理解const常量是如何使用的。