多态,继承,引用指针对象一顿扒.

来源:互联网 发布:上海恺英网络怎么样 编辑:程序博客网 时间:2024/05/01 16:03

先把实验代码写到前面

#include <iostream> 

using namespace std;

 

class A

{

private:

    int a;

 

public:

    A(int x = 0):a(x)

    {

       cout << "A constructor" << endl;

    }

 

    A(A & ya)

    {

       a = ya.a;

       cout << "A copy constractor" << endl;

    }

 

    ~A()

    {

       cout << "A 析构" << endl;

    }

 

    void setval(int x)

    {

       a = x;

    }

 

    virtual void showinf()

    {

       cout << a <<",A 调用" << endl;

    }

 

    void print()

    {

       cout << "so complicated!" << endl;

    }

 

};

 

class B:public A

{

private:

    int b;

 

public:

    B(int x = 0,int y = 0):A(x),b(y)

    {

       cout << "B constractor" << endl;

    }

 

    B(B & yb):A(yb)      //必须用初始化列表来完成对继承自父类部分的数据,因为有私有成员

    {

       this->b = yb.b;

       cout << "B copy constractor" << endl;

    }

 

    ~B()

    {

       cout << "B 析构" << endl;

    }

 

    virtual void showinf()

    {

       cout << b << ",B 调用" << endl;

    }

};

 

void f(A a)

{

    a.showinf();

}

 

void g(A * pa)

{

    pa->showinf();

}

 

void h(A & a)

{

    a.showinf();

}

 

void main()

{

    A a(1);       //A constructor

    A* pa = &a;

 

    a.showinf();//1,A 调用          //对象方式:直接去代码区找函数A::showinf入口

    f(a);      //1,A 调用        //对象方式:通过拷贝构造函数打造一个新临时对象传入作为实参,直接去代码区找函数A::showinf入口,函数调用完成调用析构释放栈中的临时对象

    g(pa);     //1,A 调用        //指针方式:通过a对象内部的vfptr[0]找函数A::showinf()入口地址

    h(a);      //1,A 调用        //引用方式:类似上一行指针方式(引用底层其实也是传一个this的指针)

 

 

    B b(2,5);     //A constructor,B constractor   //里面文章不少

    pa = &b;

 

    b.showinf();  //5,B 调用   原理:同名覆盖,和虚函数没半毛钱关系,直接代码区找函数showinf入口,此时B::showinf覆盖了或者说叫隐藏了A::showinf,A::showinf()是肯定存在的,下行代码我们将进行验证

    b.A::showinf();   //2,A 调用    原理:调用被子类同名覆盖的父类中的函数(虽为虚函数)

    f(b);         //2,A 调用    原理:真切割通过A类的拷贝构造函数打造一个临时对象,并将B类型的对象隐式转换为A类型,问题:此时构造的临时对象其vfptr[0]为什么指向了A::showinf(),bvfptr[0]还是指向的B::showinf()的呢? 此调用不是通过vfptr[0]来调用的而是直接找code区入口是么?

    g(pa);        //5,B 调用    原理:多态(void) pa->(*vf[0](pa))?  类型又怎办?

    h(b);         //5,B 调用    原理:多态

 

    pa->print();

}

 

开扒心得:

,我来就这段代码来讲讲其中的八卦,撇开两旁美丽的姑娘,请跟我来,我带你去梦游

在分析中我将按照代码的执行顺序,逐步的扒每步其中的玄机

1. A a(1);     //A constructor

在实例化a对象是传入1整型实参,调用A::A(int x = 0)带默认值的构造函数打造a对象.如图可见此时this中的地址为0x0013ff50,问题1:此地址为什么不是a的地址?

然后输出A constractor信息,没多少可说的

多态,继承,引用指针对象一顿扒. - smzh0302 - 塑料的玫瑰花

 

F10单步继续,可见&a0x0013ff50,此时a的地址才最终确定,和构造函数中的this地址是一致的,这是对问题1的释疑,但这是为什么呢?由图可见a对象中有一个__vfptr值为0x00417810,它指向一个函数指针数组共一个元素,第一个元素即[0]的值为0x0041114f,也即虚函数A::showing(void)的入口地址.

多态,继承,引用指针对象一顿扒. - smzh0302 - 塑料的玫瑰花

 

 

2.F10单步走把  A* pa = &a;执行完可以发现

pa的值为0x0013ff50和第1步中的a对象的地址相符.

多态,继承,引用指针对象一顿扒. - smzh0302 - 塑料的玫瑰花

 

 

3.F10单步执行a.showinf();

此时用a对象直接调用showinf(),程序将直接去代码去找A::showinf()的入口地址.显示"1,A 调用"

 

4.F11进入f(a);的内部,f函数的原型void f(A a)可知其实参为一个对象,此时首先要调用A的拷贝构造函数打造一个临时对象,我们不妨称它为a`,由图可见this此时的值为0x0013fe38,这个地址即为临时对象的地址,它是一个无名对象.

多态,继承,引用指针对象一顿扒. - smzh0302 - 塑料的玫瑰花

 

然后输出A copy constractor

5.F11进入void f(A a)函数,此时实参a即地址为0x0013fe38的对象,然后执行a.showinf(),此时调用的找寻函数入口的方式仍然是直接到code代码区进入.输出1,A 调用,然后出void f(A a)的函数时,临时对象(0x0013fe38为首的A类型的对象)要析构,调用A::~A()函数发生析构,输出A 析构

6.执行g(pa);由函数原型void g(A * pa)可知实参是一个A*指针,看图pa的值为0x0013ff50和第一步的地址符合,没问题.然后执行pa->showinf();此时showinf()调用方法是通过pa找到对象地址,然后通过__vfptr[0]找到A::showinf()的入口地址0x0041114f,从面完成调用A::showinf(),输出1,A 调用

多态,继承,引用指针对象一顿扒. - smzh0302 - 塑料的玫瑰花

 

 

6.下面说下h(a);由函数原型void h(A & a)可知实参为A类型的一个引用

看图

多态,继承,引用指针对象一顿扒. - smzh0302 - 塑料的玫瑰花

 

注意看图中的黄箭头,这是程序目前下步要执行的代码,监视中可见此是&a,即实参的地址为0x0013ff50,和第一步创建的对象a的地址相符.

    至于调用showninf和指针传参类似,也是调用__vfptr[0]来找到函数入口的地址的.这里说下引用传参本质上讲是传递了一个指向对象的指针.更深了先不说,也说不明白.

我们发现实参用引用,并未调用拷贝构造函数打造临时对象就完成了函数调用,从面提高了程序的效率,避免了不必要的内存开销.

输出”1,A 调用.至此基类对象函数调用说完了,下面着手多态.

7.准备执行B b(2,5);F11进入B(int x = 0,int y = 0):A(x),b(y),可见程序走到B类的初始化列表,在初始化列表中写了A(x)即基类名蕨类构造实参)的方式进一步初始化基类的数据成员.

注意此时编译时B::showinf(void)的地址将覆盖__vfptr[0]原来的A::showinf(void).因为基类函数是虚函数且公有继承且子类重写了该函数(重写需要返回值及参数都要与基类中的函数严格一致,这里再延伸下如果基类函数声明为void A::showinf(void) const子类原型为void B::showinf(void)则不为重写,是同名覆盖?应该是.我记得侯老师说的是.).这是多态的得以实现的最核心最重要环节.

依次输出A constractor,B constractor

这里值得说的是在初始化b对象中继承自A类的东东时,thisb的地址是一样的,只不过它指向的对象长度较短,即此时thisA*类型的,bB类型的.这也验证了全面继承父类的东西且排在子对象的前端.

 

8. pa = &b;让基类类型的指针的值保存为子类对象的地址.这是实现多态的基础,b的地址为0x0013ff30.

多态,继承,引用指针对象一顿扒. - smzh0302 - 塑料的玫瑰花

 

 

9.执行b.showinf();将调用B::showinf(),在调用该函数进操作系统将直接去code代码区找寻B::showinf(),我们不妨看下此时b对象内部的结构,看图

多态,继承,引用指针对象一顿扒. - smzh0302 - 塑料的玫瑰花

 

由图可知.继承自父类A的部分排在对象的首部.且继承自A的部分仍然有一个__vfptr只不过此时__vfptr[0]函数指针型元素保存的是B::showinf(void)的入口的地址0x411114a,不再是A::showinf(void)的地址0x0041114f.

执行b.showinf();时操作系统将直接去代码区直接找函数入口地址0x411114a,但不是通过__vfptr[0].此时起作用的是同名覆盖,即子类void showinf()覆盖了父类的virtual void showinf(),虽然父类带一个virtual仍然是同名覆盖,b.showinf()时看到的只能是子类B::showinf(),父类的A::showinf()被隐藏了.但父类的A::showinf()是一定存在于对象a中的(可能此种说法不严谨,就是那个意思吧),下面我们将验证这点.

b.showinf();输出5,B 调用

 

10. b.A::showinf();输出2,A 调用,调用被子类同名覆盖的父类中的函数(虽为虚函数)

 

11.准备执行f(b);再看函数原型void f(A a)可见实参期望一个A类型的对象,而现在给出的是一个b,即一个B类型的对象.B类型公有继承自A,所以可以调用A类的拷贝构造函数打造一个临时对象形成实参.

F11进入A::A(A& ya)

看图

多态,继承,引用指针对象一顿扒. - smzh0302 - 塑料的玫瑰花

 

注意看this(指向临时对象的指针)__vfptr[0]的值为0x0041114f A::showinf(void)却是Ashowninf()为什么呢?我想这是因为你的A::A(A& ya)的写法是a = ya.a;的缘故.如果memcpy(this,&ya,sizeof(A))此时就能使__vfptr[0]的值就能保存成0x0041114a B::showinf(void).

进入f函数

void f(A a)

{

    a.showinf();

}

调用a.showinf()注意此时a是内存栈中的临时对象,对它的描述是按A类型切割过的B类型对象,且经过A的拷贝构造函数处理过的对象.那么仍然是操作系统直接直接对代码区A::showinf(void)入口地址,__vfptr[0]没半毛钱关系,因为此时是对象直接调用(深入的原因?不知道).

f(b);依次输出A copy constractor,2,A 调用,A 析构.

这再多说两句:如果A类的拷贝构造函数写成了memcpy(this,&ya,sizeof(A)),且f函数写成了

void f(A a)

{

   a.showinf();

   A * pa = &a;

   pa->showinf();  //注意这里

}

这样一来, pa->showinf();将调用B::showinf(void),而此时pa->b数据成员已经被切割掉,B::showinf()又要去访问pa->b成员,那么将会访问到一个没有任何价值的随机数.

 

12. 执行g(pa);b的地址0x0013ff30传入g(pa);作为实参

多态,继承,引用指针对象一顿扒. - smzh0302 - 塑料的玫瑰花

 

这里之所以有两个__vfptr是因为B::showinf(void)也是一个虚函数,不小心写上的,和本讨论主题不甚相关,请忽略.

然后执行pa->showinf();此时操作系统将通过__vfptr[0]得到0x0041114aB::showinf(void)的入口地址,从而实现多态.(再深入的我先不说,比如函数指针调用时隐含第一个参数this指针,还说的不太能自圆其说,so..)

g(pa);输出5,B 调用;

 

13. h(b); 也输出5,B 调用;实现原理:多态.

 

14.最后说下pa->print(),如果print()A类的成员函数则是可以的,但如果是B类的成员函数编译将通不过.再若将其设计为virtual B::print();也不行,因为pa->print();将会自继承自父类的__vfptr[]中找该函数,virtual B::print()地址将保存在B对象自己的__vfptr[].

最后输出结果:(print函数输出信息未收集)

多态,继承,引用指针对象一顿扒. - smzh0302 - 塑料的玫瑰花

qq:94338240

smzh0302@16.com欢迎C,C++方面交流

 

 

原创粉丝点击