0. 基本语法

多态是面向对象的三大特性之一。

多态:多态即调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。

多态分为两类

  • 静态多态:函数重载 和 运算符重载 属于静态多态,是对函数名的复用。
  • 动态多态:派生类和虚函数实现运行时多态。

  • 静态多态的函数地址绑定, 编译阶段确定函数地址。
  • 动态多态的函数地址绑定, 运行阶段确定函数地址。

引入虚函数,就是为了通过函数重写实现多态的效果。


动态多态需要满足的条件:

  • 出现在继承当中
  • 子类重写父类的虚函数,注意,必须是虚函数,重写非虚函数的话实质是函数覆盖了。

动态多态的使用条件:

  • 父类的指针或者引用,指向了子类对象。

#include <iostream>
#include <cstdio> 

using namespace std;

class base{
public:
    virtual void speak(){
        cout << "base在说话" << endl;
    }
};

class son : public base {
public:
    void speak(){  // 重写了父类的虚函数 实现了多态
        cout << "son在说话" << endl;
    }

};

// C++中 有自动的父子类 类型转换
void speak(base &person){   // 使用引用触发多态
    person.speak();
}

int main(){
    base b;
    son s;

    speak(s);
    return 0;
}

1. 多态的原理

类中定义了虚函数的话,实例化一个对象 a 以后,就会在 a 中产生一个虚函数指针vfptr,指向虚函数表(vftable)。

简而言之,子类在虚函数表中替换了重写函数的那条记录。

2. 纯虚函数和抽象类

纯虚函数语法:virtual 返回值类型 函数名 (参数列表) = 0,无需大括号。

  • 当类中有了一旦有了纯虚函数,这个类就成为了抽象类

抽象类特点:

  • 抽象类无法实例化对象,但可以指向子类对象。
  • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类,就无法实例化对象。
class base{
public:
    virtual void speak() = 0;  // 纯虚函数
    int a;
};

class son : public base {
public:
    void speak(){  // 重写了父类的虚函数 实现了多态
        cout << "son在说话" << endl;
    }

};

int main(){
    base *b = new son;  // 父类指针指向了子类  这就实现了多态
    son ss;  // 子类直接实例化出一个对象

    return 0;
}

3. 虚析构和纯虚析构

作用:

  • 解决通过父类指针无法释放子类对象的问题。
  • 如果子类中没有堆区数据,可以不写虚析构或纯虚析构
  • 拥有纯虚析构也属于抽象类,无法直接实例化对象。

问题:多态使用时(父类指针或引用指向了子类),如果子类中有属性开辟到了堆区,那么父类指针在释放时无法调用子类的析构代码。

解决办法:将父类中的析构函数改为虚的或者纯虚的。

测试代码:


#include <iostream>
#include <cstdio> 

using namespace std;

class base{
public:
    virtual void f() = 0;  // 纯虚函数

    base(){
        cout << "base 构造函数" << endl;
    }

    ~base(){
        cout << "base析构" << endl;
    }

};

class son : public base {
public:

    void f(){
        cout << "void f fuction in son" << endl;
    }
    son(){
        cout << "son构造函数" << endl;
    }

    ~son(){
        cout << "son析构" << endl;
    }

};

int main(){
    base *b = new son;
    delete b;

    return 0;
}

输出:

base构造
son构造
base析构

在base析构前边,没有son析构,如果子类中在堆区开了空间,那就会造成内存泄漏。

此时,如果把base类中的析构函数加上virtual修饰,变成一个虚函数,那么在释放父类指针的时候,就会触发子类的析构函数了,把子类释放掉。

  • 一个纯虚的析构函数,除了有声明,还必须要有具体实现,类内声明,类外定义