c++多态深入分析--单继承

编译器在编译的时候,发现基类中有虚函数,此时编译器会为每个包含虚函数的类创建一个虚表(vtable),该表是一个一维数组,

在这个数组中存放每个虚函数地址,这张表解决了继承、覆盖的问题,保证其真实放映实际的函数。编译器会为基类与继承类都建

立一个虚表(即便继承类中没有virtual函数,但是基类中有,那么继承类也就是有)。一般编译器将指向虚表指针存放在实例对象

中第一个地址。那么其实我们只要获得这个类的实例对象的地址,就获得了指向该虚表的指针的地址。如图:


举例分析:
首先验证是否实例化的第一个地址是虚表,输出虚表第一个函数。
class base
{
    public:
        vritual void first( );
        virtual void second( );
};
void base::first( )
{
    std::cout<<"In base the first virtual function "<<std::endl;
}
void base::second( )
{
    std::cout<<"In base the second virtual function "<<std::endl;
}

int main(int argc,char ** argv)
{
    typedef void (*fun)(void);
    fun f=NULL;
    base b;
    std::cout<<"vtptr address is "<<&b<<endl;

   //这里b地址直接取的话,就是object了,需要通过类型转换获得其
   //第一个地址,然后取该地址所指向的内容,在将其类容转换成地址
    std::cout<<"vt address is "<<(int *)*(int *)&b<<std::endl;
    //取虚表中的第一个地址
    f=(fun)*(int *)*(int *)&b;
    f( );
    f=(fun)*( (int *)*(int *)&b + 1 );
    f( );
    std::cout<<"the class instance size is "<<sizeof(b)<<std::endl;
    return 0;
}

运行结果

通过这段代码可以肯定结构如果所示,因为该对象的大小就是4,所以只有一个指针。


情况一:一般继承,无覆盖

class base
{
    public:
        vritual void first( );
        virtual void second( );
};
void base::first( )
{
    std::cout<<"In base the first virtual function "<<std::endl;
}
void base::second( )
{
    std::cout<<"In base the second virtual function "<<std::endl;
}

class derived:public base
{
    public:
        void thrid( );
        virtual void fourth( );
};
void derived::thrid( )
{
    std::cout<<"In derived the thrid function "<<std::endl;
}
void derived::fourth( )
{
    std::cout<<"In derived the fourth function"<<std::endl;
}
int main( )
{
    derived d;
    typedef void(*fun)(void);
    std::cout<<"vtptr address is"<<&d<<std::endl;
    std::cout<<"vt address is"<<(int *)*(int *)&d<<std::endl;
    f=(fun)*(int *)*(int *)&b;
    f( );
    f=(fun)*( (int *)*(int *)&b + 1 );
    f( );
    f=(fun)*( (int *)*(int *)&b + 2 );
    f( );
    f=(fun)*( (int *)*(int *)&b + 3 );
    f( );
    return 0;
}
运行结果为:


由上图我们可以证明,不是虚函数的函数是绝对不会进入虚函数表的。
去掉下面两行代码即可:
f=(fun)*( (int *)*(int *)&b + 3 );
f( );

运行结果为:

如果只是一般继承,没有发生覆盖,那么其结果如下图



情况二:一般继承有覆盖

class base
{
    public:
        vritual void first( );
        virtual void second( );
};
void base::first( )
{
    std::cout<<"In base the first virtual function "<<std::endl;
}
void base::second( )
{
    std::cout<<"In base the second virtual function "<<std::endl;
}

class derived:public base
{
    public:
        void first( );
        virtual void fourth( );
};
void derived::first( )
{
    std::cout<<"In derived the first function "<<std::endl;
}
void derived::fourth( )
{
    std::cout<<"In derived the fourth function"<<std::endl;
}
int main( )
{
    derived d;
    typedef void(*fun)(void);
    std::cout<<"vtptr address is"<<&d<<std::endl;
    std::cout<<"vt address is"<<(int *)*(int *)&d<<std::endl;
    f=(fun)*(int *)*(int *)&b;
    f( );
    f=(fun)*( (int *)*(int *)&b + 1 );
    f( );
    f=(fun)*( (int *)*(int *)&b + 2 );
    f( );
    f=(fun)*( (int *)*(int *)&b + 3 );
    f( );
    return 0;
}

结果如下


这个结果说明超出了虚表的地址范围,虚表里只有三个虚函数,所以同上的操作,去掉下面代码

    f=(fun)*( (int *)*(int *)&b + 3 );
    f( );
在运行得


那么我们就可以得到如下结构图


虚表指针在什么时候和什么地方初始化呢?
在构造函数中进行虚表的创建和虚表指针的初始化。根据构造函数的调用顺序,在构造继承类构造函数时,先调用基类的构造函数,此时编译器只知道基类,并不知道后面继承类。它初始化基类对象的虚表指针,该虚表指针指向基类的虚表。当执行子类的构造函数时,子类对象的虚表指针被初始化,指向自身的虚表。

所以在在将各种继承类对象地址赋值给基类指针的时候,指向虚表的指针是不一样的,当调用其有覆盖的函数的时候,就能实现其多态效果。

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值