虚函数和它的虚表

一、多态与虚函数(virtual)
先理清楚下边三句话:
1.函数重载是处理同一层次上的同名函数问题,而虚函数处理的是不同派生层次上的同名函数问题。(覆盖)
2.同名隐藏则是基类与派生类同名函数的问题。
3.在基类与派生类中,只要不构成重写就是重定义!!!

虚函数功能:实现多态。在类的继承层次结构中,不同层次可以出现名字相同、参数个数相同、类型相同但是功能不同的函数。
 通过指针或引用来访问基类和派生类中的同名函数。 

使用方法:
(1)在基类中用virtual声明成员函数为虚函数。在类外定义时不必再加此关键字
(2)派生类中重新定义此函数,函数名、函数类型、函数参数个数和类型必须与基类虚函数相同(当一个成员函数被声明为虚函数时,派生类中同名函数都自动成为虚函数,virtual可加可不加)
(3)如果在派生类中没有对基类的虚函数重新定义,则派生类简单的继承其直接基类的虚函数。

注意:一个成员函数被声明为虚函数后,在同一类族中的类就不能再定义一个非virtual的但与该虚函数具有相同的参数和函数返回值类型的同名函数

什么情况下应当声明虚函数??
基类成员函数在类的继承后有无可能被更改功能,如果希望他更改,则声明为虚函数。
对成员函数的调用是通过对象名还是通过基类指针或引用去访问的,如果是基类指针或引用,则声明为虚函数
某些情况下,定义虚函数时,不定义函数体,它的作用只是定义了一个虚函数名,具体功能留给派生类去添加。
使用虚函数,系统要有一定的空间开销。当一个类有虚函数时,编译系统会为该类构造一个虚函数表,它是一个指针数组,存放每个虚函数的入口地址。

二.虚表剖析:
对于有虚函数的类,编译器都会维护一张虚表,对象的前四个字节就是指向虚表的指针

class CText
{
public:
       /*virtual*/ void DH()
       {}
private:
       int iText;
};

加关键字 virtual 大小为8字节,不加为4字节
回避虚函数的机制:
    在某些情况下,我们可能对虚函数的调用不要进行动态绑定,而是强迫其执行虚函数的某个特定版本,使用作用域运算符可以实现这一目的。那么,我们什么时候要使用此版本呢?
当一个派生类的虚函数调用他的覆盖基类虚函数时,基类版本通常完成继承层次中所有类型都要做的共同任务,派生类中的要执行与派生类有关的操作!

三、含虚函数的类在继承关系下,对象内存剖析及虚表的内部结构全解

先来搞清楚带虚函数类的对象在内存中的分布
class B
{
public:
       B()
       {
              dataB = 1;
       }
       virtual   void fun1()
       {
              cout << "B=funtest1"  << endl;
       }
private:
       int dataB;
};

这里写图片描述
根据其所占八个字节,头四个字节存储为虚表指针,指向虚表,紧接着是自己的数据成员

单继承:如果有虚函数表,那么只有一个虚函数表,并且按照虚函数声明的顺序顺序排列,派生类的虚函数紧接着基类的虚函数排列

class B
{
public:
       B()
       {
              dataB = 1;
       }
       virtual   void fun1()
       {
              cout << "B=fun1"  << endl;
       }
       virtual void fun2()
       {
              cout << "B=fun2" << endl;
       }
private:
       int dataB;
};
class C1:public B
{
public:
       C1()
       {
              dataC1 = 2;
       }
       virtual   void fun1()//重写基类虚函数
       {
              cout << "C1=fun1" << endl;
       }
       virtual void fun3()//派生类自己的虚函数
       {
              cout << "C1=fun3" << endl;
       }
       void fun4()
       {
              cout << "C1=fun4()" << endl;
       }
private:
       int dataC1;
};

这里写图片描述
从上图得知派生类对象继承了基类对象虚表,并且如果基类对象虚函数在派生类中有重写,那么派生类会改写成自己的虚函数
但是我们并没有在c1的虚表内看到它自己的虚函数fun(3),这是因为编译器不显示,其实派生类自己的虚函数紧跟在基类虚函数的后边。
证明它我们可以通过打印出它的虚函数直观的看见。

多继承:

class  Base1//基类
{
public:
       virtual  void fun1()
       {
              cout << "Base1:fun1()" << endl;
       }
       virtual void fun2()
       {
              cout << "Base1:fun2()" << endl;
       }
private:
       int b1;
};
class  Base2//基类
{
public:
       virtual  void fun3()
       {
              cout << "Base3:fun3()" << endl;
       }
       virtual void fun2()
       {
              cout << "Base4:fun4()" << endl;
       }
private:
       int b2;
};
class  Derive:public Base1,public Base2//派生类
{
public:
       virtual  void fun1()
       {
              cout << "Derive:fun1()" << endl;
       }
       virtual void fun5()
       {
              cout << "Derive:fun5()" << endl;
       }
private:
       int c;
};

这里写图片描述
由图清晰可知,派生类维护了两张虚表,并且其自己的虚函数跟在第一张虚表的后边。因此我们可得如下结论:
多重继承会有多个虚函数表,几重继承,就会有几个虚函数表。这些表按照派生的顺序依次排列,如果子类改写了父类的虚函数
,那么就会用子类自己的虚函数覆盖虚函数表的相应的位置,如果子类有新的虚函数,那么就添加到第一个虚函数表的末尾。

菱形非虚拟继承:

class  Base//基类
{
public:
       virtual  void fun1()
       {
              cout << "Base1:fun1()" << endl;
       }
       virtual void fun2()
       {
              cout << "Base1:fun2()" << endl;
       }
private:
       int b;
};
class  Base1:public Base
{
public:
       virtual  void fun1()//重写基类Base虚函数
       {
              cout << "Base1:fun1()" << endl;
       }
       virtual void fun3()
       {
              cout << "Base1:fun3()" << endl;
       }
private:
       int b1;
};
class  Base2 :public Base
{
public:
       virtual  void fun1()//重写基类Base虚函数
       {
              cout << "Base2:fun1()" << endl;
       }
       virtual void fun4()
       {
              cout << "Base2:fun4()" << endl;
       }
private:
       int b2;
};
class  Derive:public Base1,public Base2//派生类
{
public:
       virtual  void fun1()//重写基类Base1虚函数
       {
              cout << "Derive:fun1()" << endl;
       }
       virtual void fun5()
       {
              cout << "Derive:fun5()" << endl;
       }
private:
       int c;
};

这里写图片描述
此种情况其实是单继承与多继承的组合,派生类自己的虚函数依旧在Base1虚表后边。

带虚函数的虚拟继承:

class  Base//基类
{
public:
       virtual  void fun1()
       {
              cout << "Base1:fun1()" << endl;
       }
       virtual void fun2()
       {
              cout << "Base1:fun2()" << endl;
       }
private:
       int b;
};
class  Base1: virtual public Base
{
public:
       virtual  void fun1()//重写基类Base虚函数
       {
              cout << "Base1:fun1()" << endl;
       }
       virtual void fun3()
       {
              cout << "Base1:fun3()" << endl;
       }
private:
       int b1;
};

这里写图片描述
我们可以很清楚的看到,对象的存储方式与普通继承有不同。这里,派生类Base1所占字节数为20
这里写图片描述

菱形虚拟继承:

class  Base//基类
{
public:
       virtual  void fun1()
       {
              cout << "Base1:fun1()" << endl;
       }
       virtual void fun2()
       {
              cout << "Base1:fun2()" << endl;
       }
private:
       int b;
};
class  Base1: virtual public Base
{
public:
       virtual  void fun1()//重写基类Base虚函数
       {
              cout << "Base1:fun1()" << endl;
       }
       virtual void fun3()
       {
              cout << "Base1:fun3()" << endl;
       }
private:
       int b1;
};
class  Base2 :virtual public Base
{
public:
       virtual  void fun1()//重写基类Base虚函数
       {
              cout << "Base2:fun1()" << endl;
       }
       virtual void fun4()
       {
              cout << "Base2:fun4()" << endl;
       }
private:
       int b2;
};
class  Derive:public Base1,public Base2//派生类
{
public:
       virtual  void fun1()//重写基类Base1虚函数
       {
              cout << "Derive:fun1()" << endl;
       }
       virtual void fun5()
       {
              cout << "Derive:fun5()" << endl;
       }
private:
       int c;
};

这里写图片描述

这里写图片描述
内存分布如下:
这里写图片描述
虚拟继承虽然消除了二义性,但是开销是增加虚函数指针。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值