多态与虚函数

多态与虚函数

通过基类指针只能访问派生类的成员变量,但是不能访问派生类的成员函数。

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

有了虚函数,基类指针指向基类对象时就使用基类的成员(包括成员函数和成员变量),指向派生类对象时就使用派生类的成员。换句话说,基类指针可以按照基类的方式来做事,也可以按照派生类的方式来做事,它有多种形态,或者说有多种表现方式,我们将这种现象称为多态(Polymorphism)。

虚函数的注意事项 1. 只需要在虚函数的声明处加上 virtual 关键字,函数定义处可以加也可以不加。

  1. 可以只将基类中的函数声明为虚函数,这样所有派生类中具有遮蔽关系的同名函数都将自动成为虚函数。

  2. 当在基类中定义了虚函数时,如果派生类没有定义新的函数来遮蔽此函数,那么将使用基类的虚函数。

  3. 只有派生类的虚函数覆盖基类的虚函数(函数原型相同)才能构成多态(通过基类指针访问派生类函数)。

  4. 构造函数不能是虚函数。

  5. 析构函数可以声明为虚函数,而且有时候必须要声明为虚函数。

构成多态的条件 1. 必须存在继承关系; 2. 继承关系中必须有同名的虚函数,并且它们是覆盖关系(函数原型相同)。 3. 存在基类的指针,通过该指针调用虚函数。

虚析构函数的必要性

大部分情况下都应该将基类的析构函数声明为虚函数。

1
2
3
4
5
6
7
class Base{
public:
    Base();
    virtual ~Base();
protected:
    char *str;
};

纯虚函数和抽象类详解

1
virtual 返回值类型 函数名 (函数参数) = 0;
纯虚函数没有函数体,只有函数声明,在虚函数声明的结尾加上=0,表明此函数为纯虚函数。

包含纯虚函数的类称为抽象类(Abstract Class)。纯虚函数没有函数体,无法实例化。派生类必须实现纯虚函数才能被实例化。

抽象基类除了约束派生类的功能,还可以实现多态。

一个纯虚函数就可以使类成为抽象基类,但是抽象基类中除了包含纯虚函数外,还可以包含其它的成员函数(虚函数或普通函数)和成员变量。

只有类中的虚函数才能被声明为纯虚函数,普通成员函数和顶层函数均不能声明为纯虚函数。

虚函数表

编译器之所以能通过指针指向的对象找到虚函数,是因为在创建对象时额外地增加了虚函数表。

如果一个类包含了虚函数,那么在创建该类的对象时就会额外地增加一个数组,数组中的每一个元素都是虚函数的入口地址。不过数组和对象是分开存储的,为了将对象和数组关联起来,编译器还要在对象中安插一个指针,指向数组的起始位置。数组就是虚函数表(Virtual function table),简写为vtable。

typeid运算符与RTTI机制

typeid 运算符用来获取一个表达式的类型信息。类型信息是创建数据的模板,数据占用多大内存、能进行什么样的操作、该如何操作等,这些都由它的类型信息决定。

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>
using namespace std;
//基类
class People{
public:
    virtual void func(){ }
};
//派生类
class Student: public People{ };
int main(){
    People *p;
    int n;

    cin>>n;
    if(n <= 100){
        p = new People();
    }else{
        p = new Student();
    }
    //根据不同的类型进行不同的操作
    if(typeid(*p) == typeid(People)){
        cout<<"I am human."<<endl;
    }else{
        cout<<"I am a student."<<endl;
    }
    return 0;
}
除了 typeid 运算符,dynamic_cast 运算符和异常处理也依赖于 RTTI 机制,并且要能够通过派生类获取基类的信息,或者说要能够判断一个类是否是另一个类的基类,要在基类和派生类之间再增加一条绳索。称此为继承链(Inheritance Chain)。