虚继承与虚函数

虚继承

问题

IMG_8754D35A1C1B1.jpeg
这里举一个网上看来的例子:
以前呢有一个大户人家,起初呢就两口子,男主人掌权,保管着大门钥匙,后来他们有两个儿子,若干年后长大成人,男主人也老了,就把钥匙又配了两把,分别交给他的两个儿子,交由他们主持家业,又过去好多年,两个儿子也都成了家,但只有一对可以生育,育有一个男孩,他爹和他叔伯都把他当亲儿子养,等于说他有两个爸爸。这个男孩长大成人,又到了交接钥匙的时候,这个男孩的爹和他的叔伯(也就是最初主人的俩儿子)都配了钥匙给这个男孩,他手上就有了两把。他看着手里两把钥匙,说:“一把就够!两把…这不浪费嘛!”

#include <iostream>
using namespace std;
class Grandfather
{
public:
    int key;
};

class Father1:public Grandfather
{
};
class Father2:public Grandfather
{
};
class Grandson:public Father1,public Father2
{
};
  
int main()
{
    Grandson A;
    //A.key=9;
    return 0;
}

请问这个时候,这个男孩使用A.key,用的是谁的钥匙?这是未确定的,编译器会报错。

解决

那么如何规避这个问题呢?即使用虚基类!

所谓虚基类就是在继承的时候在继承类型public之前用virtual修饰一下 。比如还是这个例子,只需要父亲类在继承爷爷类的时候多加一个virtual,那么这个时候,派生类和基类就只维护一份一个基类对象。避免多次拷贝,出现歧义。

定义方法即在两个父亲类的派生时增加virtual的声明:

class Father1:virtual public Grandfather
class Father2:virtual public Grandfather

虚函数

image.png

wind继承instrument类,被形式参数为instrument类的tune函数调用,结果函数把wind当成instrument调用,而我们希望他被当作wind调用。

问题:

c编译会使用早捆绑,在致用Instrument地址时并不知道要调用的正确函数,而我们又不能抛弃高大上的多态,每种乐器都写一个tune(请假装我们不能)。

解决

我们需要主动引起晚捆绑机制,在基类中声明play为virtual。这样编译器就会动态捆绑该函数。
image.png
动态捆绑是动态多态的体现。
对于基类声明为virtual的函数,下次用基类指针指向派生类,再调用该函数时,调用的就是派生类重写的函数了。

虚函数原理

先拷贝基类的虚函数表,如果派生类重写了基类的某个虚函数,就用派生类的虚函数替换虚表同位置的基类虚函数,跟上派生类自己的虚函数。
image.png

lass Base
{
    virtual void fun1();
    virtual void fun2();
    virtual void fun3();
}
class Derived : public Base
{
    virtual void fun2();
    virtual void fun3();
    virtual void fun4();
}

image.png

虚析构函数

虚析构函数

在C++中,不能把构造函数定义为虚构造函数,因为在实例化一个对象时才会调用构造函数,且虚函数的实现,其实本质是通过一个虚函数表指针来调用的,还没有对象更没有内存空间当然无法调用了,故没有实例化一个对象之前的虚构造函数没有意义也不能实现。

但析构函数却是可以为虚函数的,且大多时候都声明为虚析构函数。这样就可以在用基类的指针指向派生类的对象在释放时,可以根据实际所指向的对象类型动态联编调用子类的析构函数,实现正确的对象内存释放。

问题

如果我们不能正确的调用析构函数会发生什么问题?
请看

#include <iostream>
using namespace std;
class Point
{
private:
    int x,y;
    int *str;
public:
    Point(int x=0,int y=0)
    {
        this->x = x;
        this->y = y;
        str = new int[100];
    }
    ~Point()
    {
        delete []str;
        cout<<"Called Point's Destructor and Deleted str!"<<endl;
    }
};
class Circle:public Point
{
private:
    int r;
    int *str;
public:
    Circle(int x,int y,int R):Point(x,y)
    {
        r = R;
        str = new int[100];
    }
    ~Circle()
    {
        delete []str;
        cout<<"Called Circle's Destructor and Deleted str!"<<endl;
    }
  
};
int main()
{
    Point *p;
    p = new Circle(10,10,20);
    delete p;
    return 0;
}

用基类指针指向派生类变量,根据前面的经验,如果不把基类析构函数定义为虚函数,将调用基类的析构函数,派生类是基类的超集,这样从法律上允许了内存泄漏。

解决方法

把基类中析构函数声明为virtual。这个时候会先调用释放派生类的空间,然后再释放基类的内存空间。

接口、抽象类、纯虚函数

C++接口是用抽象类类实现的。包含至少一个纯虚函数的类是一个抽象类。纯虚函数就是没有函数体的虚函数。抽象类的存在是为了提供一个高度抽象、对外统一的接口,然后通过多态的特性使用各自的不同方法。

//虚函数
virtual 返回值  函数名(形参 {};
//纯虚函数
virtual 返回值  函数名(形参)=0;
virtual int area() = 0;

抽象类的特点总结如下:

  1. 抽象类无法实例出一个对象来,只能作为基类让派生类完善其中的纯虚函数,然后再实例化使用。

  2. 抽象类的派生类可以不重写基类中的纯虚函数,继续作为抽象类被派生。直到给出所有纯虚函数的定义,则成为一个具体类,才可以实例化对象。

  3. 抽象类因为抽象、无法具化,所以不能作为参数类型、返回值、强转类型。但抽象类可以定义一个指针、引用类型,指向其派生类,来实现多态特性。

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×