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常量是如何使用的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值