• C++学习笔记--从虚函数说开去


    虚函数与纯虚函数

          虚函数:在某基类中声明为virtual并在一个或多个派生类中被重新定义的成员函数,virtual  函数返回类型  函数名(参数表){函数体;} ,实现多态性,通过指向派生类的基类指针或引用,访问派生类中同名覆盖成员函数。注意虚函数在基类中是有定义的,即便定义是空。

          纯虚函数:在基类中是没有定义的,必须由派生类重定义实现,否则不能由对象进行调用。

    看下面的例子:

    #include<iostream>
    using namespace std;
    class Cshape
    {
    public:
        void SetColor(int color){m_nColor=color;}
        virtual void Display(void){};  
    private:
        int m_nColor;
    };
     
    class Crectangle:public Cshape{ //公有继承
    public:
     
    };
    
    void main()
    {
        Crectangle obRectangle;
        Cshape* pShape=&obRectangle;
        pShape->Display();
    }

         上面例子中,Display是虚函数,虽然是空定义,并且在派生类中没有重写该函数,但是上面的代码在编译阶段不会出错,而是在链接时出错,而如果将Display声明为纯虚函数,即 virtual void Display( )=0; 则该代码将在编译阶段出错,即编译器需要确保纯虚函数必须在子类中进行重写。注意:如果在基类中定义了纯虚函数,那么基类是不能实例化对象的,它只是提供一个接口,就想老板造房子,我就告诉你干嘛,具体怎么干还得靠自己,但是完全不说,你也当然不知道要干嘛。

    虚函数怎么实现多态

        前面讲到虚函数的作用主要是实现多态,那么具体是怎么实现的?为什么需要用基类指针来指向派生类对象?

        多态:C++中实现多态有虚函数,抽象类,重载,覆盖,模板等方法,虚函数是最重要的一种。把不同的子类对象当做父类来看,可以屏蔽不同子类对象之间的差异,写出通用的代码,做出通用的编程,以适应需求的不断变化,实际上是通过基类来访问子对象的。那么子对象在程序中是怎么通过基类来找到与该对象对应的方法呢?这就要说到虚函数表

    虚函数表

        虚函数是通过虚函数表来实现的,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以当用父类的指针来操作一个子类的时候,这张虚函数表就像一张地图,指明实际应该调用的函数。

        这里引用一下CSDN上的一篇题为“C++虚函数表解析”博客(博主的地址是http://blog.csdn.net/haoel):

        假设我们有这样一个类,

    class Base{
    public:
        virtual void f(){cout << "Base::f" << endl;}
        virtual void g(){cout << "Base::g" << endl;}
        virtual void h(){cout << "Base::h" << endl;}
    };

        我们可以通过Base的实例来得到虚函数表:下面是实际例程:

    void main()
    {
       typedef void(*Fun)(void); //这里是定义一个参数为void,返回值为void的函数指针。
       Base b;
       Fun pFun=NULL;
       cout << "虚函数表地址:" << (int*)(&b) << endl;
       cout << "虚函数表--第一个函数地址:" << (int*)*(int*)(&b) << endl;
       // 调用第一个虚拟函数
       pFun=(Fun)*((int*)*(int*)(&b));
       pFun();
    }

         程序运行结果如下:(Windows 7 + Visual C++6.0)

        由以上程序可以知道,如果要调用Base::g 和Base::h,代码如下:

       pFun=(Fun)*((int*)*(int*)(&b)+1);
       pFun=(Fun)*((int*)*(int*)(&b)+2);

    虚拟函数表的图示如下:

    类中的虚函数所占内存为多少?

        虚函数要占用4个字节的内存空间,用来指定虚函数的虚拟函数表的入口地址。既然是入口地址,所以一个类的虚函数所占的地址是不变的,和虚拟函数的个数没有关系,如果派生类继承了多个超类的虚拟函数,那个这个派生类的虚拟函数所占用的内存空间等于4*(超类个数),即为每个超类维护一张虚函数表(关于多重继承的虚函数表内存分布,更详细请参考博客http://blog.csdn.net/haoel/article/details/1948051#)。另外要注意的是:父类和子类共享一个虚函数指针。这一点可以从下面的代码中看出:

    #include<iostream>
    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 Child:public Base{
        virtual void i(){cout << "Child::i" << endl;}
    };
    void main()
    {
        typedef void(*Fun)(void);
        Child child;
        Base* pBase=&child;
        Fun pFun=NULL;
        cout << sizeof(Base) << " " << sizeof(Child) << endl;
        cout << "虚拟函数i()的地址是:" << (int*)*(int*)pBase+3 << endl;
        pFun=(Fun)*((int*)*(int*)pBase+3);
        pFun();
    }

    代码运行结果如下:

  • 相关阅读:
    谷歌被墙,怎样给谷歌浏览器加入迅雷下载插件
    python文件和文件夹訪问File and Directory Access
    svn简单介绍
    javaproject积累——树形结构的操作
    Android多线程研究(1)——线程基础及源代码剖析
    Android4.4 Telephony流程分析——彩信(MMS)发送过程
    hadoop优质链接
    Android开发系列(二十一):Spinner的功能和使用方法以及实现列表选择框
    锤子Smartisan T1手机官方4.4.2系统内核版本号信息
    深入研究Clang(五) Clang Lexer代码阅读笔记之Lexer
  • 原文地址:https://www.cnblogs.com/hubavyn/p/3762471.html
Copyright © 2020-2023  润新知