zoukankan      html  css  js  c++  java
  • C++ 由虚基类 虚继承 虚函数 到 虚函数表


    //虚基类:一个类可以在一个类族中既被用作虚基类,也被用作非虚基类。 class Base1{ public: Base1(){cout<<"Construct Base1!"<<endl;}; void foo();//普通函数 virtual void foo1(){cout<<"foo1 in Base1"<<endl;};//虚函数:可以在基类中实现(+{})或者直接定义成虚基类, 会出现错误:undefined reference to vtable for lass virtual void foo2() = 0;//纯虚函数:必须要在继承类中实现,类似java中的接口好处是 可以在不清楚具体构建的情况下,给派生类规定好规范 }; class Base2{ public: void foo();//普通函数 virtual void foo1();//虚函数 //virtual void foo2() = 0;//纯虚函数 }; //虚继承:继承类继承了基类多次,从而产生了多个拷贝,虚基类的基本原则是在内存中只有基类成员的一份拷贝。
    //这样,通过把基类继承声明为虚拟的,就只能继承基类的一份拷贝,从而消除歧义。 class Derived1: public virtual Base1{ public: Derived1(){cout<<"Construct Derived1~ !"<<endl;}; void foo2(){ cout<<"foo2 in Derive1"<<endl; } }; //多继承 class Derived2: public virtual Base1, public virtual Base2{ void foo2(){ cout<<"foo2 in Derive1"<<endl; } };
    错误解决:
    
    在使用虚函数的程序中,编译时会出现
    
          undefined reference to `vtable for Class 
    
    或    undefined reference to typeinfo for Class   的情况
    
    其解决方案就是将类似于
    
    virtual void foo();
    Should be defined (inline or in a linked source file): virtual void foo() {}
    Or declared pure virtual: virtual void foo() = 0;
     

    基类指针指向派生类  

    基类指针可以指向派生类,但是无法使用不存在于基类只存在于派生类的元素。(所以我们需要虚函数和纯虚函数)

      在内存中,一个基类类型的指针是覆盖N个单位长度的内存空间。
      当其指向派生类的时候,由于派生类元素在内存中堆放是:前N个是基类的元素,N之后的是派生类的元素。
      于是基类的指针就可以访问到基类也有的元素了,但是此时无法访问到派生类(就是N之后)的元素。

    派生类指针不能指向基类

    虚函数表

      本文部分内容参考了 陈皓专栏的内容.

      对c++了解的人都应该知道虚函数是通过一张虚函数表实现的。简称V-Table。 在这个表中,主要是一个类的虚函数的地址表,这张表解决了 继承、覆盖的问题,保证内容真实反映实际的函数。这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子 类的时候,这张虚函数表就显得尤为重要了,它就像一个地图一样,指明了实际所应该调用的函数。C++的编译器保证虚函数表的指针存在与对象实例中的最前面 的位置(这是为了保证取道虚表函数的高性能)。 

      在每一个包含虚函数的类中,都存在一张虚函数表,用来记录虚函数真正的地址。当派生类继承于基类时,将基类的虚函数表也一同继承,然后根据自己是否覆盖基类的函数,来修改虚函数表。具体看这个:讲的很详细。实际上是每一个类都会创建一张虚表,每一个类的实例也就从类中得到了一张虚表.

      

      一般: 无覆盖

    http://p.blog.csdn.net/images/p_blog_csdn_net/haoel/15190/o_vtable2.JPG

      一般继承: 虚函数覆盖

      多重继承:无覆盖

      

      可以看到子类函数的虚表就接在第一个基类虚表的后面

      多重继承: 有虚函数覆盖

    需要注意的问题:

      不能通过父类型的指针访问子类自己的函数。即使这种目的可以使用指针的方式访问来达到

      包含任一纯虚函数的类 为 抽象类。抽象类是不能够实例化的。

      

    #include <iostream>
    #include <cstdio>
    using namespace std;
    
    class Base{
    public:
         virtual void f(){ cout<<" Base::f "<<endl; }
         virtual void g(){ cout<<" Base::g "<<endl; }
         virtual void h(){ cout<<" Base::h "<<endl; }
    };
    
    class Base1{
    public:
         virtual void f(){ cout<<" Base1::f "<<endl; }
         virtual void g(){ cout<<" Base1::g "<<endl; }
         virtual void h(){ cout<<" Base1::h "<<endl; }
    };
    
    class Base2{
    public:
         virtual void f(){ cout<<" Base2::f "<<endl; }
         virtual void g(){ cout<<" Base2::g "<<endl; }
         virtual void h(){ cout<<" Base2::h "<<endl; }
    };
    
    class Base3{
    public:
         virtual void f(){ cout<<" Base3::f "<<endl; }
         virtual void g(){ cout<<" Base3::g "<<endl; }
         virtual void h(){ cout<<" Base3::h "<<endl; }
    };
    
    class Derive : public Base{
    public:
         virtual void f1(){ cout<<" Derive::f1 "<<endl; }
         virtual void g1(){ cout<<" Derive::g1 "<<endl; }
         virtual void h1(){ cout<<" Derive::h1"<<endl; }
    };
    
    class Derive1 : public Base{
    public:
         virtual void f(){ cout<<" Derive1::f"<<endl; }
         virtual void g1(){ cout<<" Derive1::g1 "<<endl; }
         virtual void h1(){ cout<<" Derive1::h1"<<endl; }
    };
    
    class Derive2 : public Base1, public Base2, public Base3{
    public:
         virtual void f1(){ cout<<" Derive2::f1"<<endl; }
         virtual void g1(){ cout<<" Derive2::g1 "<<endl; }
    };
    
    class Derive3: public Base1, public Base2, public Base3{
    public:
         virtual void f(){ cout<<" Derive3::f"<<endl; }
         virtual void g1(){ cout<<" Derive3::g1 "<<endl; }
    };
    
    typedef void(*Fun)(void);
    //作为函数指针 你得规定好指向的函数的参数类型,返回值类型.
    
    int main(void)
    {
         Base b;
         Fun pFun = NULL;
         cout<<"虚函数表地址: "<<&b<<endl;
         cout<<"虚函数表地址: "<<(void *)&b<<endl;
         cout<<"虚函数表地址: "<<(int*)&b<<endl;
         cout<<"虚函数表地址: "<<(long*)&b<<endl;
         /*上面四种输出结果一致 , b的地址转换成何种类型的指针看似是无所谓*/
         cout<<"虚函数表---第一个函数地址: "<<(int *)*(int *)(&b)<<endl;
         cout<<"虚函数表---第一个函数地址: "<<(void *)*(int*)(&b)<<endl;
         /*
           * long:在32位系统是32位整型,取值范围为-2^31 ~ (2^31 - 1);在64位系统是64位整型,取值范围为-2^63 ~ (2^63 - 1)
           * 为了能在64位机器上运行,这里要用 long 而不是int
           */
         pFun = (Fun)*((long *)*(long*)(&b)); //Base::f
         pFun = (Fun)*((long *)*(long*)(&b) + 1); //Base::g
         pFun = (Fun)*((long *)*(long*)(&b) + 2); //Base::h
         /* 我们就可以通过上面的方法调用函数 */
    
    
         /*
           *虚函数表明明显是在子类继承父类的时候能够发挥作用, 这里继承可以分为两类:
           * 一般继承(无虚函数覆盖 & 有虚函数覆盖) 和 多重继承(有 & 无 虚函数覆盖)
           */
    
    
         /* 一般: 无覆盖*/
         Derive d;
         pFun = (Fun)*((long *)*(long*)(&d) + 3); //Derive::f1
    
         /*
         *    1)虚函数按照其声明顺序放于表中。
         *    2)父类的虚函数在子类的虚函数前面。
         */
    
    
         /* 一般继承: 有虚函数覆盖 */
         Derive1 d1;
         pFun = (Fun)*((long *)*(long*)(&d1)); //Derive::1
         pFun = (Fun)*((long *)*(long*)(&d1) + 1); //Base::g
         /*
         *    1)覆盖的f()函数被放到了虚表中原来父类虚函数的位置。
         *    2)没有被覆盖的函数依旧。
         */
    
    
         /*   多重继承: 无覆盖*/
         Derive2 d2;
         pFun = (Fun)*((long *)*(long *)(&d2)); //Base::f
         pFun = (Fun)*((long *)*(long *)(&d2) + 3); //Derive2::f
         pFun = (Fun)*((long *)*(  (long *)(&d2)  + 1)); // Base2: f  这里*是取地址得到了虚表上第一个虚函数表Base1中第一个函数的地址 ; + 1 得到第二个虚函数表Base2中第一个函数的地址;
         /*
         *1)  每个父类都有自己的虚表。
         *2)  子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)
         */
    
    
         /* 多重继承: 有虚函数覆盖*/
         Derive3 d3;
         pFun = (Fun)*((long *)*(  (long *)(&d3) ) ); //Derive3: f
         pFun = (Fun)*((long *)*(  (long *)(&d3)  + 1)); // Derive3: f
         pFun = (Fun)*((long *)*(  (long *)(&d3)  + 2)); // Derive3: f  这里*是取地址得到了虚表上第一个虚函数表Base1中第一个函数的地址 ; + 1 得到第二个虚函数表Base2中第一个函数的地址;
         /*这里设计到了虚表的概念,也就是每一个基类在子类的虚表中都占有一席 , 我们当然可以通过上面这行code来访问其他虚表,
           *但是还可以用二维数组来访问,更加便捷
           */
         long **pVtab = (long **)&d3;
         //Base1 's vtable
         pFun = (Fun)pVtab[0][0];//Derive3::f
         pFun = (Fun)pVtab[0][1];//Base::g
         //Derive's vtable
          pFun = (Fun)pVtab[0][3]; //Derive3::g1
          //The tail of the vtable
         pFun = (Fun)pVtab[0][4];
    
         /*
         *三个父类虚函数表中的f()的位置被替换成了子类的函数指针。
         *我们可以看见,三个父类虚函数表中的f()的位置被替换成了子类的函数指针。
         *这样,我们就可以任一静态类型的父类来指向子类,并调用子类的f()了。如:
         */
         Base1 *b1 = &d3;
         Base2 *b2 = &d3;
         Base3 *b3 = &d3;
         b1->f(); //Derive3::f()
         b2->f(); //Derive3::f()
         b3->f(); //Derive3::f()
         b1->g(); //Base1::g()
         b2->g(); //Base2::g()
         b3->g(); //Base3::g()
         /* 当然,父类指针只能指向子类中覆盖的父类中的虚函数, 如下,尝试访问子类中自有的函数 会报错*/
         Base1 *b4 = new Derive3();
         //b4 -> g1();///error: ‘class Base1’ has no member named ‘g1’|
    
         /* 但是并不安全:
         * 1) 通过父类型的指针访问子类自己的虚函数: 在运行时,我们可以通过指针的方式访问虚函数表来达到违反C++语义的行为。
         * 2) 访问non-public的虚函数:
         */
    
         return 0;
    }

    #########################################################################################################

    #再说C++多态 #

    #########################################################################################################

    .  “C++的多态性用一句话概括就是:在基类的函数前+vritual关键字,在派生类中重写该函数,运行时会根据对象的实际类型来调用相应的函数(晚绑定或者叫动态绑定)。如果对象类型是基类,那么调用基类的函数。如果是派生类,那么调用派生类的函数。”

        1:用virtual关键字申明的函数叫做虚函数,虚函数肯定是类的成员函数。  

        2:存在虚函数的类都有一个一维的虚函数表叫做虚表,类的对象有一个指向虚表开始的虚指针。虚表是和类对应的,虚表指针是和对象对应的。  

        3:多态性是一个接口多种实现,是面向对象的核心,分为类的多态性和函数的多态性。  

        4:多态用虚函数来实现,结合动态绑定.  

        5:纯虚函数是虚函数再加上 = 0;  

        6:抽象类是指包括至少一个纯虚函数的类。

        纯虚函数:virtual void fun()=0;即抽象类!必须在子类实现这个函数,即先有名称,没有内容,在派生类实现内容。

       编译器在编译的时候,发现Base类中有虚函数,这时候编译器会为每个包含虚函数的类创建一个虚表(vtable),该表是一个一维数组,在这个数组中存放每一个函数的地址。

      如何定位虚表?编译器提供另外一个结构虚指针(vptr)。虚指针指向虚表,虚表中存放的是函数的地址。在程序运行时,根据对象的类型去初始化vptr,从而让vptr指向正确的所属类的虚表。由于pFather实际指向的对象类型是Son,所以vptr指向Son的vtable。

  • 相关阅读:
    POJ 2528 Mayor's posters(线段树)
    Codeforces Beta Round #22 (Div. 2 Only) C. System Administrator(构造割点)
    HDU 4417 Super Mario(划分树)
    Codeforces Beta Round #22 (Div. 2 Only) D. Segments(贪心)
    HDU 1247 Hat’s Words(字典树)
    HDU 3639 HawkandChicken(强连通分量)
    HDU 3394 Railway(点双连通分量)
    HDU 1394 Minimum Inversion Number(树状数组)
    HDU 3874 Necklace(树状数组+离线处理)
    树状数组
  • 原文地址:https://www.cnblogs.com/luntai/p/5878999.html
Copyright © 2011-2022 走看看