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

 

posted @ 2017-06-29 00:41  sz_leez  阅读(221)  评论(0编辑  收藏  举报