介绍有关C++中继承与多态的基础虚函数类

互联网 17-9-11
这篇文章主要给大家介绍了关于C++中继承与多态的基础虚函数类的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧。

前言

本文主要给大家介绍了关于C++中继承与多态的基础虚函数类的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧。

虚函数类

纯虚函数

看一个例子:

class Person   {     virtual void Display () = 0; // 纯虚函数   protected :     string _name ;   // 姓名   };      class Student : public Person   {};

上面概念大家可能都会问一句为什么要这样? 这些内容在接下来的知识里都能找到答案~ 好了那么我们今天的主角虚函数登场!!!!

何为虚函数表,我们写一个程序,调一个监视窗口就知道了。

下面是一个有虚函数的类:

#include<iostream>   #include<windows.h>   using namespacestd;      class Base   {   public:      virtual void func1()      {}         virtual void func2()      {}      private:      inta;   };      void Test1()   {      Base b1;   }      int main()      {      Test1();      system("pause");      return0;   }

我们现在点开b1的监视窗口

这里面有一个_vfptr,而这个_vfptr指向的东西就是我们的主角,虚函数表。一会大家就知道了,无论是单继承还是多继承甚至于我们的菱形继承虚函数表都会有不同的形态,虚函数表是一个很有趣的东西。

我们来研究一下单继承的内存格局

仔细看下面代码:

#include<iostream>   #include<windows.h>   using namespace std;         class Base   {   public:      virtual void func1()      {        cout<< "Base::func1"<< endl;      }         virtual void func2()      {        cout<< "Base::func2"<< endl;      }      private:      inta;   };      class Derive:public Base   {   public:      virtual void func1()      {        cout<< "Derive::func1"<< endl;      }         virtual void func3()      {        cout<< "Derive::func3"<< endl;      }         virtual void func4()      {        cout<< "Derive::func4"<< endl;      }      private:      int b;   };

首先子类的fun1()重写了父类的fun1() ,虚表里存的是子类的fun1() ,接下来父类的fun2() ,子类的fun3() , fun4()都是虚函数,所以虚表里会有4个元素,分别为子类的fun1() ,父类fun2() ,子类fun3() ,子类fun4() 。然后我们调出监视窗口看我们想的到底对不对呢?

我预计应该是看到fun1() ,fun2() ,fun3() ,fun4()的虚函数表,但是呢这里监视窗口只有两个fun1() , fun2() ,难道我们错了????

这里并不是这样的,只有自己靠得住,我觉得这里的编译器有问题,那我们就得自己探索一下了。 但是在探索之前我们必须来实现一个可以打印虚函数表的函数。

typedef void(*FUNC)(void);   void PrintVTable(int* VTable)   {      cout<< " 虚表地址"<<VTable<< endl;         for(inti = 0;VTable[i] != 0; ++i)      {        printf(" 第%d个虚函数地址 :0X%x,->", i,VTable[i]);        FUNC f = (FUNC)VTable[i];        f();      }         cout<< endl;   }         int main()   {      Derive d1;      PrintVTable((int*)(*(int*)(&d1)));      system("pause");      return0;   }

下图来说一下他的缘由:

我们来使用这个函数,该函数代码如下:

//单继承   class Base   {   public:    virtual void func1()    {     cout << "Base::func1" << endl;    }       virtual void func2()    {     cout << "Base::func2" << endl;    }      private:    int a;   };      class Derive :public Base   {   public:    virtual void func1()    {     cout << "Derive::func1" << endl;    }       virtual void func3()    {     cout << "Derive::func3" << endl;    }       virtual void func4()    {     cout << "Derive::func4" << endl;    }      private:    int b;   };   typedef void(*FUNC)(void);   void PrintVTable(int* VTable)   {      cout<< " 虚表地址"<<VTable<< endl;          for(inti = 0;VTable[i] != 0; ++i)      {        printf(" 第%d个虚函数地址 :0X%x,->", i,VTable[i]);        FUNC f = (FUNC)VTable[i];        f();      }          cout<< endl;   }           int main()   {      Derive d1;      PrintVTable((int*)(*(int*)(&d1))); //重点      system("pause");      return0;   }
PrintVTable((int*)(*(int*)(&d1)));

首先我们肯定要拿到d1的首地址,把它强转成int*,让他读取到前4个字节的内容(也就是指向虚表的地址),再然后对那个地址解引用,我们已经拿到虚表的首地址的内容(虚表里面存储的第一个函数的地址)了,但是此时这个变量的类型解引用后是int,不能够传入函数,所以我们再对他进行一个int*的强制类型转换,这样我们就传入参数了,开始函数执行了,我们一切都是在可控的情况下使用强转,使用强转你必须要特别清楚的知道内存的分布结构。

到底打印的对不对呢? 我们验证一下:

这里我们通过&d1的首地址找到虚表的地址,然后访问地址查看虚表的内容,验证我们自己写的这个函数是正确的。(这里VS还有一个bug,当你第一次打印虚表时程序可能会崩溃,不要担心你重新生成解决方案,再运行一次就可以了。因为当你第一次打印是你虚表最后一个地方可能没有放0,所以你就有可能停不下来然后崩溃。)我们可以看到d1的虚表并不是监视器里面打印的那个样子的,所以有时候VS也会有bug,不要太相信别人,还是自己靠得住。哈哈哈,臭美一下~

我们来研究一下多继承的内存格局

看如下代码:

class Base1   {   public:    virtual void func1()    {     cout << "Base1::func1" << endl;    }       virtual void func2()    {     cout << "Base1::func2" << endl;    }      private:    int b1;   };      class Base2   {   public:    virtual void func1()    {     cout << "Base2::func1" << endl;    }       virtual void func2()    {     cout << "Base2::func2" << endl;    }      private:    int b2;   };         class Derive : public Base1, public Base2   {   public:    virtual void func1()    {     cout << "Derive::func1" << endl;    }       virtual void func3()    {     cout << "Derive::func3" << endl;    }      private:    int d1;   };      typedef void(*FUNC) ();   void PrintVTable(int* VTable)   {    cout << " 虚表地址>" << VTable << endl;       for (int i = 0; VTable[i] != 0; ++i)    {     printf(" 第%d个虚函数地址 :0X%x,->", i, VTable[i]);     FUNC f = (FUNC)VTable[i];     f();    }    cout << endl;   }         void Test1()   {    Derive d1;    //Base2虚函数表在对象Base1后面    int* VTable = (int*)(*(int*)&d1);    PrintVTable(VTable);    int* VTable2 = (int *)(*((int*)&d1 + sizeof (Base1) / 4));    PrintVTable(VTable2);   }   int main()   {    Test1();    system("pause");    return 0;   }

现在我们现在知道会有两个虚函数表,分别是Base1和Base2的虚函数表,但是呢!我们的子类里的fun3()函数怎么办?它是放在Base1里还是Base2里还是自己开辟一个虚函数表呢?我们先调一下监视窗口:

现在很清楚了,fun3()在Base1的虚函数表中,而Base1是先继承的类,好了现在我们记住这个结论,当涉及多继承时,子类的虚函数会存在先继承的那个类的虚函数表里。记住了!

现在我们来结束一下上面我列的那么多概念现在我来逐一的解释为什么要这样.

1.为什么静态成员函数不能定义为虚函数?

因为静态成员函数它是一个大家共享的一个资源,但是这个静态成员函数没有this指针,而且虚函数变只有对象才能能调到,但是静态成员函数不需要对象就可以调用,所以这里是有冲突的.

构造函数当中不适合用虚函数的原因是:在构造对象的过程中,还没有为“虚函数表”分配内存。所以,这个调用也是违背先实例化后调用的准则析构函数当中不适用虚函数的原因是:一般析构函数先析构子类的,当你在父类中调用一个重写的fun()函数,虚函数表里面就是子类的fun()函数,这时候已经子类已经析构了,当你调用的时候就会调用不到.

我们都知道父类的指针可以指向子类,现在呢我们我们用一个父类的指针new一个子类的对象。

//多态 析构函数   class Base   {   public:    virtual void func1()    {     cout << "Base::func1" << endl;    }       virtual void func2()    {     cout << "Base::func2" << endl;    }       virtual ~Base()    {     cout << "~Base" << endl;    }      private:    int a;   };      class Derive :public Base   {   public:    virtual void func1()    {     cout << "Derive::func1" << endl;    }    virtual ~Derive()    {     cout << "~Derive"<< endl;    }   private:    int b;   };      void Test1()   {    Base* q = new Derive;    delete q;   }   int main()   {    Test1();    system("pause");    return 0;   }

当然虚函数的知识点远远没有这么一点,这里可能只是冰山一角,比如说菱形继承的虚函数表是什么样?然后菱形虚拟继承又是什么样子呢? 这些等我总结一下会专门写一个博客来讨论菱形继承。虚函数表我们应该已经知道是什么东西了,也知道单继承和多继承中它的应用,这些应该就足够了,这些其实都是都是为你让你更好的理解继承和多态,当然你一定到分清楚重写,重定义,重载的他们分别的含义是什么. 这一块可能有点绕,但是我们必须要掌握.

以上就是介绍有关C++中继承与多态的基础虚函数类的详细内容,更多内容请关注技术你好其它相关文章!

来源链接:
免责声明:
1.资讯内容不构成投资建议,投资者应独立决策并自行承担风险
2.本文版权归属原作所有,仅代表作者本人观点,不代表本站的观点或立场
标签: 继承
上一篇:php获取远程图片并下载保存到本地的方法分析 下一篇:C#中关于async与await的使用详解

相关资讯