《Effective C++》一书中第七条:Declare destructors virtual in polymorphic base classes
用于多态的基类中的析构函数应该声明为虚函数。看到这个地方,感觉自己好像对虚函数的理解有点模糊了,就趁这个机会把C++多态和虚函数的知识系统的理一理。
所谓C++的多态性,说来话长。OOP,面向对象编程的核心思想就是多态性,字面意思就是“多种形式”,《C++ primer》第五版中这么描述,“我们把具有继承关系的多个类型称为多态类型,因为我们可以利用这些类型的‘多种形式’而无须在意它们的差异。引用或指针的静态类型与动态类型不同这一事实正是C++语言支持多态性的根本所在”。
这就引出了一个实际的问题,被定义为基类的指针或者引用是可以指向派生类的,同时,基类和派生类或者多个派生类之间同一个函数有可能进行的是完全不同的操作。举个例子:
定义obj是一个基类,circle和rectangle是他的两个派生类。它们各有一个计算面积的方法:int compute()返回面积,定义obj *e。
若令e = new circle;当我们e->compute(),调用面积计算函数时,会出现什么情况呢?这个时候同样的compute函数基类和派生类都有,实际会如何选择?
我们可以用代码进行类似的测试,如下所示:
#include <iostream>
using namespace std;
class person{
public:
person(int a,int w,string n):age(a),weight(w),name(n){ cout << "construct a person"<<endl;}
virtual void output();
virtual void print(){ cout << "age:" << age << " weight:" << weight << " name:" << name << endl;}
virtual ~person(){ cout << "delete a person" << endl;}
protected:
int age;
string name;
int weight;
};
void person::output()
{
cout << "I am a person" << endl;
}
class student:public person{
public:
student(int a, int w, const string &n,int g) : person(a, w, n),grade(g)
{ cout << "construct a student" << endl;}
~student() { cout << "delete a student" << endl;}
void output() override ;
void print() override {cout << "age:" << age << " weight:" << weight << " name:" << name << " grade:" << grade << endl;}
private:
int grade;
};
void student::output()
{
cout << "I am a student" << endl;
}
int main(){
person *p = new person(18,120,"小明");
p->output();p->print();
delete(p);
cout << endl;
p = new student(19,120,"老王",98);
p->output();p->print();
delete(p);
p = nullptr;
return 0;}
上面的代码中定义了两个类,基类person以及它的派生类student,基类的方法定义为虚函数,采用C++11新标准里的override关键字对student里的方法进行标记,在做虚函数测试的同时,我还在构造函数和析构函数中加入了打印语句,顺便观察一下派生类与基类的构造函数与析构函数的调用关系。
编译,运行,结果为:
construct a person
I am a person
age:18 weight:120 name:小明
delete a person
construct a person
construct a student
I am a student
age:19 weight:120 name:老王 grade:98
delete a student
delete a person
这个结果还是很有说服力的,这里引入一个概念叫动态绑定,虽然指针p的静态类型是指向基类person的,但是在运行时,可以动态的和派生类进行绑定,引用同理,并且也适用于作为函数的参数的情况。进一步的,理解多态,就是一个固定类型的目标在不同的场合下变为不同的类型,为了实现这样的功能,就需要用到虚函数,虚函数又包含纯虚函数,纯虚函数需要在后面加一个=0,并且主函数体内不包含任何操作。
多态性使得同一个类族中的不同对象可以拥有不同的特性,更具有扩展性。
另外,为什么基类的析构函数必须要声明为虚函数呢,其实不仅仅是析构函数,派生类进行类似操作但是又和基类不完全相同的操作,都需要把基类里的函数定义为虚函数或者纯虚函数。
从结果还可以看到,派生类的构造是先调用基类的构造函数再调用自己的内部语句,而析构函数恰恰相反,先执行自己的函数体内的语句,再执行基类的析构函数。