“多态(polymorphism)”指的是同一名字的事物可以完成不同的功能。
多态可以分为编译时的多态和运行时的多态。
前者主要是指函数的重载(包括运算符的重载)、对重载函数的调用,在编译时就能根据实参确定应该调用哪个函数,因此叫编译时的多态;
而后者则和继承、虚函数等概念有关,是本章要讲述的内容。本教程后面提及的多态都是指运行时的多态。

  1. #include <iostream>
  2. using namespace std;
  3. //基类People
  4. class People{
  5. public:
  6. People(char *name, int age);
  7. void display();
  8. protected:
  9. char *m_name;
  10. int m_age;
  11. };
  12. People::People(char *name, int age): m_name(name), m_age(age){}
  13. void People::display(){
  14. cout<<m_name<<"今年"<<m_age<<"岁了,是个无业游民。"<<endl;
  15. }
  16. //派生类Teacher
  17. class Teacher: public People{
  18. public:
  19. Teacher(char *name, int age, int salary);
  20. void display();
  21. private:
  22. int m_salary;
  23. };
  24. Teacher::Teacher(char *name, int age, int salary): People(name, age), m_salary(salary){}
  25. void Teacher::display(){
  26. cout<<m_name<<"今年"<<m_age<<"岁了,是一名教师,每月有"<<m_salary<<"元的收入。"<<endl;
  27. }
  28. int main(){
  29. People *p = new People("王志刚", 23);
  30. p -> display();
  31. p = new Teacher("赵宏佳", 45, 8200);
  32. p -> display();
  33. return 0;
  34. }

运行结果:
王志刚今年23岁了,是个无业游民。
赵宏佳今年45岁了,是个无业游民。
通过基类指针只能访问派生类的成员变量,但是不能访问派生类的成员函数。

为了消除这种尴尬,让基类指针能够访问派生类的成员函数,C++ 增加了虚函数(Virtual Function)。使用虚函数非常简单,只需要在函数声明前面增加 virtual 关键字。

更改上面的代码,将 display() 声明为虚函数:

#include <iostream>
using namespace std;

//基类People
class People{
public:
    People(char *name, int age);
    virtual void display();  //声明为虚函数
protected:
    char *m_name;
    int m_age;
};
People::People(char *name, int age): m_name(name), m_age(age){}
void People::display(){
    cout<<m_name<<"今年"<<m_age<<"岁了,是个无业游民。"<<endl;
}

//派生类Teacher
class Teacher: public People{
public:
    Teacher(char *name, int age, int salary);
    virtual void display();  //声明为虚函数
private:
    int m_salary;
};
Teacher::Teacher(char *name, int age, int salary): People(name, age), m_salary(salary){}
void Teacher::display(){
    cout<<m_name<<"今年"<<m_age<<"岁了,是一名教师,每月有"<<m_salary<<"元的收入。"<<endl;
}

int main(){
    People *p = new People("王志刚", 23);
    p -> display();

    p = new Teacher("赵宏佳", 45, 8200);
    p -> display();

    return 0;
}

有了虚函数,基类指针指向基类对象时就使用基类的成员(包括成员函数和成员变量),指向派生类对象时就使用派生类的成员。
C++中虚函数的唯一用处就是构成多态。
C++提供多态的目的是:可以通过基类指针对所有派生类(包括直接派生和间接派生)的成员变量和成员函数进行“全方位”的访问,尤其是成员函数。如果没有多态,我们只能访问成员变量。

C++虚函数注意事项

只需要在虚函数的声明处加上 virtual 关键字,函数定义处可以加也可以不加。
为了方便,你可以只将基类中的函数声明为虚函数,这样所有派生类中具有遮蔽关系的同名函数都将自动成为虚函数。

一般情况下,接口类的成员方法就可以设计成虚函数

但是派生类在继承接口类后,其他地方要调用派生类的时候,对类的声明有可能会是接口类本身,这样就存在析构不完全的问题

#include <iostream>
using namespace std;
//基类
class Base{
public:
    Base();
    ~Base();
protected:
    char *str;
};
Base::Base(){
    str = new char[100];
    cout<<"Base constructor"<<endl;
}
Base::~Base(){
    delete[] str;
    cout<<"Base destructor"<<endl;
}
//派生类
class Derived: public Base{
public:
    Derived();
    ~Derived();
private:
    char *name;
};
Derived::Derived(){
    name = new char[100];
    cout<<"Derived constructor"<<endl;
}
Derived::~Derived(){
    delete[] name;
    cout<<"Derived destructor"<<endl;
}
int main(){
   Base *pb = new Derived();
   delete pb;
   cout<<"-------------------"<<endl;
   Derived *pd = new Derived();
   delete pd;
   return 0;
}

运行结果:
Base constructor
Derived constructor
Base destructor
—————————-
Base constructor
Derived constructor
Derived destructor
Base destructor

我们在实际使用的时候,可能也会出现上述问题,例如我们用Interface类进行声明,而实际上的impl可能是其他的派生类,这种情况下,我们需要将析构函数也用virtual进行修饰。

class Base{
public:
    Base();
    virtual ~Base();
protected:
    char *str;
};

C++纯虚函数

纯虚函数语法

virtual 返回值类型 函数名(函数参数) = 0;

纯虚函数没有函数体,只有函数声明,在虚函数声明的结尾加上=0,表明此函数为纯虚函数。

最后的=0并不表示函数返回值为0,它只起形式上的作用,告诉编译系统“这是纯虚函数”。

RTTI

这种在程序运行后确定对象的类型信息的机制称为运行时类型识别(Run-Time Type Identification,RTTI)。在 C++ 中,只有类中包含了虚函数时才会启用 RTTI 机制,其他所有情况都可以在编译阶段确定类型信息。