什么是虚函数
在某基类中声明为 virtual并在一个或多个派生类中被重新定义的成员函数,用法格式为:virtual 函数返回类型 函数名 ( 参数表 ) { 函数体 };
实现多态性,通过指向派生类的基类指针或引用,访问派生类中同名覆盖成员函数。
根据什么考虑是否把一个成员函数声明为虚函数呢?主要考虑以下几点 。
1 )首先看成员函数所在的类是否会作为基类。 然后看成员函数在类的继承后有无可能
被更改功能,如果希望更改其功能, 一般应该将它声明为虚函数。
2 )如果成员函数在类被继承后的功能不需要被修改,或派生类用不到该函数,则不要
把它声明为虚函数。 不要仅仅考虑到要作为基类而把类中的所有成员函数都声明为虚函数。
3 )应考虑对成员函数的调用是通过对象名还是通过基类指针或引用去访问,如果是通
过基类指针或引用去访问的,则应当声明为虚函数。
4 )有时,在定义虚函数时并不定义其函数体,即函数体是空 的 。 它的作用只是定义了
一个虚函数名,具体功能留给派生类去添加 。
需要说明的是:使用虚函数,系统要有一定的空间开销 。 当一个类带有虚函数时编译
系统会为该类构造一个虚函数表,它是一个指针数组,用于存放每个虚函数的人口地址 。 系
统在进行动态关联时的时间开销是很少的,因此,多态是高效的 。
为什么析构函数必须是虚函数?
析构函数是进行类的清理工作,具体来说就是释放构造函数开辟的内存空间和资源,当然我们完全可以在析构函数中进行任何我们想要的操作。
虚函数的时候就说到,为实现多态性,可以通过基类的指针或引用访问派生类的成员。也就是说,声明一个基类指针,这个基类指针可以指向派生类对象。
#include <iostream>
using namespace std;
class Father {
public:
~Father() {
cout << "class Father destroyed" << endl;
}
};
class Son : public Father {
public:
~Son() {
cout << "class Son destroyed" << endl;
}
};
int main() {
Father* p = new Son;
delete p;
return 0;
}
/*
运行结果:
class Father destroyed
*/
在主函数中,我们声明了一个基类的指针,并用一个派生类的实例去初始化这个基类指针,随后删除这个指针。我们看到程序运行的结果,只有基类的析构函数被调用。
#include <iostream>
using namespace std;
class Father {
public:
virtual ~Father() {
cout << "class Father destroyed" << endl;
}
};
class Son : public Father {
public:
~Son() {
cout << "class Son destroyed" << endl;
}
};
int main() {
Father* p = new Son;
delete p;
return 0;
}
/*
运行结果:
class Son destroyed
class Father destroyed
*/
当基类的析构函数被定义成虚函数时,我们再来删除这个指针时,先调用派生类的析构函数,再调用基类的析构函数。
为什么默认的析构函数不是虚函数?
虚函数不同于普通成员函数,当类中有虚成员函数时,类会自动进行一些额外工作。这些额外的工作包括生成虚函数表和虚表指针,虚表指针指向虚函数表。每个类都有自己的虚函数表,虚函数表的作用就是保存本类中虚函数的地址,我们可以把虚函数表形象地看成一个数组,这个数组的每个元素存放的就是各个虚函数的地址。
这样一来,就会占用额外的内存,当们定义的类不被其他类继承时,这种内存开销无疑是浪费的。