• C++ 类的多态三(多态的原理--虚函数指针--子类虚函数指针初始化)


    //多态的原理--虚函数指针--子类虚函数指针初始化
    #include<iostream>
    using namespace std;
    
    /*
    多态的实现原理(有自己猜想部分)
    
    基础知识:
      类中的成员函数本质上是C语言中的全局函数,只是在全局函数的参数列表中多加了一个结构体指针参数
    
    详解:
      对于类中没有用virtual关键字修饰的成员函数,c++编译器在静态编译的时候,c++就会确定对象调用的全局函数 
      当类中声明虚函数时,c++编译器会在静态编译的时候为这个类生成一个虚函数表,
      虚函数表是一个存储类成员函数指针的数据结构,
      一个虚函数表只属于一个类
      虚函数表是由编译器自动生成与维护的
      virtual成员函数的地址会被c++编译器放入虚函数表中
    
      在定义一个对象的时候即运行时或者说动态编译的时候(未调用构造函数之前)---Point p1; ,
      那么c++编译器会为这个对象 隐式的  分配4个字节大小的内存,  这个内存里是一个 指针变量
      此时这个指针变量还是为NULL,当执行函数的构造函数的时候  c++编译器会默认的为这个指针变量赋值
      这个指针变量会指向该类的虚函数表
    
      对于子类而言,子类的初始化比较特殊,必须先调用父类的构造函数,这时候这个隐藏的指针变量会被初始化为父类中虚函数表的地址
      随后子类对象又会再次调用自身的构造函数  这个隐藏的指针变量又会再一次被赋值为  子类 对应的类的虚函数表的地址
    
    
      说明1:
      通过虚函数表指针VPTR调用重写函数是在程序运行时进行的,因此需要通过寻址操作才能确定真正应该调用的函数。
      而普通成员函数是在编译时就确定了调用的函数。在效率上,虚函数的效率要低很多。
      说明2:
      出于效率考虑,没有必要将所有成员函数都声明为虚函数
    
    
    */
    
    class Point{
    public:
        Point(){
            PrintA();
        }
        virtual void PrintA(){
            cout << "1 我是第一个父类虚函数 我必将产生占据4个字节大小的函数指针a  " << endl;
        }
        virtual void PrintB(){
            cout << "2 我是第二个父类虚函数 我必将产生占据4个字节大小的函数指针b  " << endl;
        }
        virtual void PrintC(){
            cout << "3 我是第三个父类虚函数 我必将产生占据4个字节大小的函数指针c  " << endl;
        }
    private:
        int b;
    };
    
    class PointA :public Point{
    public:
        void PrintA(){
            cout << "我是子类PointA 我重写了父类的虚函数 " << endl;
        }
    };
    
    class PointB :public PointA{
        void PrintA(){
            cout << "我是孙子类 PointB 我重写了字类的重写函数  用来验证子类中的重写函数是不是一个虚函数 " << endl;
        }
    };
    
    void ProtectA(PointA &pin){
        pin.PrintA();
    }
    
    void ProtectB(){
        Point p1;
        cout << "Point类型的大小" << sizeof(p1) << endl;  //打印 8
        //说明:c++编译器的确为虚函数分配了4个字节大小的内存  并且无论有多少个虚函数,只会分配4个字节大小的内存空间
        //侧面证明了 虚函数指针指向的是一个虚函数表    而不是一个虚函数指针指向一个虚函数
    
        PointA pa;
        cout << "PointA类型的大小" << sizeof(pa) << endl;  //打印 8
        //根据结果说明:子类重写父类的虚函数,虽然没有加virtual关键字,但是本质上还是一个虚函数 
        //不然 为什么c++编译器为什么会为子类对象多分配4个字节大小的内存空间呢?
        //做一下验证  写一个子类PointB  继承  PointA看是否能实现多态
    
        PointB pb;
        ProtectA(pb);// 打印出PointB 孙子类中的重写函数   
        //证明: 子类重写父类的虚函数,虽然没有加virtual关键字,但是本质上还是一个虚函数 
    
    }
    
    void ProtectC(){
        //验证子类的分步初始化对虚函数指针的影响----我在父类的构造函数中调用一个虚函数,并且在子类中重写该虚函数
        PointA pa;//调用了父类的PrintA()函数
        //验证我文章开头结论
        pa.PrintB();
    
    }
    
    void main(){
        ProtectC();
        system("pause");
    }

     

  • 相关阅读:
    PHP 求多个数组的笛卡尔积,适用于求商品规格组合 【递归思想, 类似广度优先搜索】【原创】
    CCF推荐期刊会议
    SCI分区
    值和指针接收者的区别
    程序员练级攻略
    保险
    golang 有缓冲channel和无缓冲channel
    后台学习路线
    golang之反射
    atomic和mutex
  • 原文地址:https://www.cnblogs.com/zhanggaofeng/p/5635585.html
Copyright © 2020-2023  润新知