• C++虚函数


    C++中虚函数的作用主要是实现了多态的机制,核心理念就是通过基类访问派生类定义的函数。多态也就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。所谓泛型技术,也就是试图使用不变的代码来实现可变的算法。比如:模板技术、RTTI技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行是决议。

    静态多态与动态多态:

    静态多态,也叫早绑定,也就是说程序在编译阶段根据参数个数确定调用那个函数,例如

    class Rect       //矩形类
    {
    public:
        int calcArea(int width);
        int calcArea(int width,int height);
    };
    
    int main()
    {
        Rect.rect;
        rect.calcArea(10);
        rect.calcArea(10,20);
        return 0;
    }

    动态多态,也叫晚绑定,是以封装和继承为基础,在运行时才决定调用那个函数,例如:

    class Shape
    {
    public:
        virtual double calcArea();//虚函数
    };
    
    class Circle:public Shape
    {
    public:
        Circle(double r);
        double calcArea();
    };
    
    class Rect:public Shape
    {
        Rect(double width,double height);
        double calcArea();
    };
    
    int main()
    {
        Shape *shape1 = new Circle(4.0);
        Shape *shape2 = new Rect(3.0,5.0);
        shape1->calcArea();
        shape2->calcArea();
        return 0;
    }

    注意:

    1.基类声明的虚函数,在派生类中也是虚函数,即使不再使用virtual关键字,虚函数也实现了动态多态的方式,如上述main函数中,shape1和shape2分别调用各自成员calcArea函数;

    2.如果基类没有用virtual修饰,即为普通成员函数时,这时在main函数中,shape1和shape2调用的则为基类的calcArea函数;

    重载与重写:

    重载(overload),是指编写一个与已有函数同名但参数表不同的函数;

    重写(override),也称为“覆盖”,是指派生类重写基类的虚函数,重写的函数必须有一致的参数表和返回值;

    父类和子类出现同名函数称为隐藏。

        父类对象.函数函数名(...);                                   //调用父类的函数
        子类对象.函数名(...);                                         //调用子类的函数
        子类对象.父类名::函数名(...);                              //子类调用从父类继承来的函数。

    父类和子类出现同名虚函数称为覆盖
        父类指针 = new 子类名(...); 父类指针->函数名(...);//调用子类的虚函数。

    virtual关键字:

    纯虚函数,标志本身为一个抽象类,不能直接实例化,是用于规范派生类的行为,实际上就是告诉派生类必须实现相同的函数接口

    class Shape
    {
    public:
        virtual double calcArea()=0;   // =0标志一个虚函数为纯虚函数
    };

    虚析构函数,析构函数可以是虚的,甚至是纯虚的,一个如果用作于其他类的基类时,它的析构函数必须是虚的,否则会导致内存泄漏,例如

    class Shape
    {
    public:
        //virtual ~Shape();
        ~Shape();
    };
    
    class Circle:public Shape
    {
    public:
        Circle(int x, int y, double r);
        ~Circle();
        virtual double calcArea();
        
    private:
        double mRadius;
        Coordinate *mpCenter;      //坐标类指针
    };
    
    Circle::Circle(int x, int y, double r)
    {
        mpCenter = new Coordinate(x,y);
        mRadius = r ;
    }
    
    Circle::~Circle()
    {
        delete m_pCenter;
        mpCenter = nullptr;
    }
    
    int main()
    {
        Shape *shape = new Circle(3, 5, 4.0);
        shape->calcArea();
        delete shape;
        shape = nullptr;
        return 0;
    }

    上面的程序中,如果在圆形的类中定义一个圆心的坐标,并且坐标是在堆中申请的内存,则在mian函数中通过父类指针操作子类对象的成员函数的时候是没有问题的,可是在销毁对象内存的时候则只是执行了父类的析构函数,子类的析构函数却没有执行,这会导致内存泄漏。

    如果delete后边跟父类的指针则只会执行父类的析构函数,如果delete后面跟的是子类的指针,那么它即会执行子类的析构函数,也会执行父类的析构函数。

    virtual在函数中的使用限制:

    普通函数不能是虚函数,也就是说这个函数必须是某一个类的成员函数,不可以是一个全局函数,否则会导致编译错误。
    静态成员函数不能是虚函数 static成员函数是和类同生共处的,他不属于任何对象,使用virtual也将导致错误。
    内联函数不能是虚函数 如果修饰内联函数 如果内联函数被virtual修饰,计算机会忽略inline使它变成存粹的虚函数。
    构造函数不能是虚函数,否则会出现编译错误。

    虚函数表:
    虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其容真实反应实际的函数。这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数。

    1.一般情况下,在虚函数表中,虚函数按照其声明顺序存放的,父类的虚函数在子类的虚函数前面;

    2.如果子类中有虚函数重载了父类的虚函数,覆盖的函数被放到了虚表中原来父类虚函数的位置,没有被覆盖的函数依旧;

    3.如果是多重继承(无虚函数覆盖),每个父类都有自己的虚表,子类的成员函数被放到了第一个父类(所谓的第一个父类是按照声明顺序来判断的)的表中;

    4.如果是多重继承(有虚函数覆盖),每个父类虚函数表中的位置被替换成了子类的函数指针。这样,我们就可以任一静态类型的父类来指向子类,并调用子类的函数;

  • 相关阅读:
    LVM快照备份与恢复
    Docker Images for MySQL Group Replication 5.7.14
    sysbench write and read only
    Leetcode: Closest Leaf in a Binary Tree
    Amazon | OA 2019 | Optimal Utilization
    物联网架构成长之路(27)-Docker练习之Zookeeper安装
    物联网架构成长之路(26)-Docker构建项目用到的镜像2
    物联网架构成长之路(25)-Docker构建项目用到的镜像1
    物联网架构成长之路(23)-Docker练习之Elasticsearch服务搭建
    物联网架构成长之路(22)-Docker练习之Etcd服务搭建
  • 原文地址:https://www.cnblogs.com/sz-leez/p/7092355.html
Copyright © 2020-2023  润新知