浅谈C++的this指针

  之所以写这篇文章,主要是为了回答网友 zhancaihua123同学的下面几个问题:

father* p=new son;
p->disp(...);
father是父类,son是子类。disp是一个子类重定义过的虚函数。
问题一:p->disp(...);是不是可以写成p->disp(p,...);
问题二:p的类型是不是father*
问题三:子类disp函数的this指针是不是son*类型的
问题四:如果第三的问题回答“是”请回答第四题,回答“不是”的,请回避!
那么如果disp函数的this指针是 son*类型的,用p调用disp的时候,我们将p的值传递给disp的this指针,那么既然p是father*类型的,而this是son*类型的,则上述操作就相当于把父类的指针转换为子类指针,这不是与父类指针不能自动(隐式)转换为子类指针矛盾么?

  看回答,发现有好多人this指针的本质有错误的认识,估计不少人一说起this指针,脑袋立即反应出:那个类里使用的this指针,那么这个指针就是那个类类型咯。其实事实根本不是这样子的,这里修正对this指针的错误理解:

  首先,zhancaihua123同学,我在这里重申一点:“this”不是变量,是关键字,意味着this指针并不是哪个真实存在的符号/储存空间。所以,this指针没有C++语言范畴里的变量类型。所以我曾说的“子类disp函数的this指针是son*类型”这个说法是错的。this指针即不能说是son*类型也不能说是"father"类型。

  当然,可能不少同学因此反驳,我在IDE里敲入“this->”后会自动弹出类的成员变量啊,怎么就说this没有类型呢?确实,this指针多多少少跟所在类的类型有关。因此,我也对this指针使用“类型”一词,但是这里“类型”包含下面的两层含义:

  1. 起始地址(严格来说应该是0偏移地址)。
  2. 涵盖范围/寻址范围。

  因为在机器层面,变量(其实压根就没有什么变量,该叫操作数)除了整型、浮点型之分外,是没有类型的,更没有类成员一说,只有寻址。据此,再进一步说明之前先介绍两个基本常识:(一下全部在X86-64机器VC++编译器范围类讨论)

======================================基本常识分界线=====================================

基本常识1:

类成员是如何访问的?(注:没有特别说明,都不包括静态成员)

  当C++被编译成二进制代码后,不考虑什么导出符号、未定义符号之类的,什么变量名就统统消失,那么要访问这些变量,就得通过机器层面的方式:如以某个地址作为标准/基准(起始地址),通过距离这个基准一定量来定位要访问的变量。对于类成员来说,一般是对象首地址+偏移。比如以下这个类:

class A
{
    int a;
    int b;
    virtual void F();
}

他的一个对象aa在内存中的分布如下:(为什么起始地址为F0H?没有为什么,随便定的)

那么假设对象aa的首地址(F0H)保存在寄存器rax中,那么成员a的访问方式为:

rax+8h

同理,成员b的访问方式:

rax+Ch

 

基本常识2:

什么是继承?

比如如下定义:

struct IA
{
    int a;
    virtual void F();
    void FF();
};
struct B
{
    int b;
    virtual void G()=0;
};
class Derived:public B,public IA
{
    public:
    int d;
    virtual void F();
    void FF();
    virtual void G();
};

那么假设有Derived d,d对象的内存分布图如下:

好了,简介完两个基本常识,开始讲this指针的“类型”。

========================================this指针的类型=================================

this指针跟不少人想象的不一样,它的类型由被调用函数决定。它的类型遵循着这两点规则:(途中打勾为首地址。)

1.对于非虚函数,this指针的基准地址为函数定义所在层级对象的首地址,范围为该层级对象始末。

如:IA::FF();

this指针类型是以D0H为首地址,范围是从首地址开始到DFH为止。(其实里面有内存空洞,我们不去纠结这个)

Derived::FF();

this指针类型是C0H为首地址,范围是从首地址到E8H为止。

2.对于虚函数,this指针的基准地址为函数首先声明者的首地址,范围为实现者的始末。

如:IA::F();

其this指针类型是以D0H为首地址,范围是从首地址开始到DFH为止。

Derived::F();

其this指针类型是以D0H为首地址,范围是从C0H到E8H为止。

那么当有IA* a=new Derived();后,

a->F();便是这么访问b成员的了。假设首地址D0H保存在寄存器rax里,

rax-8

========================================结案陈词======================================

好了,这里可以最后总结回答原问题三及其新问的“如果用对象指针调用函数,那么到底是指针传递给this还是&object传递给this?”

之所以有 zhancaihua123同学所疑惑的“this指针到底是father*还是son*”,秘密就在这里。之所以觉得它像是father*,是因为形如IA* a=new Derived();a所存放的地址是D0H,通过a所能直接访问的范围限于上面的橙色区域,并且当调用F()时,所传的地址、this指针的值就是a的值D0H。而又觉得它是son*,是因为在F()内部,通过this指针可以访问的范围值整个子类对象。于是乎就让人觉得有父类指针隐式转换为子类指针之嫌。

顺便解答了 zhancaihua123同学第二个问题,就是传给this指针的肯定是a的值即对象指针的值。

=========================================鸣谢========================================

有人说,提出好的问题,等于解决了问题的一半。有些问题,我们没留意到,没认真想过,所以通常都是想当然。正是 zhancaihua123同学所发问,引起我的思考,才去写代码检验自己的想法。所以,最该感谢的,就是提出问题的zhancaihua123同学。

posted on 2013-08-20 21:16  CCQLegend  阅读(6050)  评论(1编辑  收藏  举报

导航