C 虚函数详解
扫描二维码
随时随地手机看文章
01—C 虚函数探索
C 是一门面向对象语言,在C 里运行时多态是由虚函数和纯虚函数实现的,现在我们看下在C 中如何用虚函数实现多态。先来看一段代码。
// virtual_function.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include
class Base {
public:
Base() {
std::cout << "Base::constructor run" << std::endl;
}
virtual void fun1() {
std::cout << "Base::fun1 run" << std::endl;
}
virtual void fun2() {
std::cout << "Base::fun2 run" << std::endl;
}
virtual ~Base() {
std::cout << "Base::desconstructor run" << std::endl;
}
};
class Derive : public Base {
public:
Derive() {
std::cout << "Derive::constructor run" << std::endl;
}
void fun1() {
std::cout << "Derive::fun1 run" << std::endl;
}
void fun3() {
std::cout << "Derive::fun3 run" << std::endl;
}
~Derive() {
std::cout << "Derive::desconstructor run" << std::endl;
}
};
int main()
{
Derive* d = new Derive();
d->fun1();
d->fun2();
d->fun3();
delete d;
}
这段代码编译运行后输出了:Base::constructor run
Derive::constructor run
Derive::fun1 run
Base::fun2 run
Derive::fun3 run
Derive::desconstructor run
Base::desconstructor run
这段代码里基类Base定义了虚函数fun1和fun2,派生类Derive有成员函数fun1和fun3,其中派生类覆盖了继承而来的基类虚函数fun1。在主函数里创建Derive类型对象指针d指向Derive类型对象。由于派生类Derive成员函数fun1覆盖了基类Base成员函数fun1,因此通过d调用fun1实际调用的是派生类Derive类的成员函数fun1,而继承而来的成员函数fun2没有被覆盖,则通过指针d调用fun2实际调用的是基类成员函数fun2。这里好像让看不出虚函数有什么作用,那么我们将主函数修改如下:int main()
{
Base* b = new Derive();
b->fun1();
b->fun2();
delete b;
}
在程序里我们将创建一个基类指针b并指向的是派生类,并且调用delete释放内存时使用的是基类指针b。编译运行输出结果如下:Base::constructor run
Derive::constructor run
Derive::fun1 run
Base::fun2 run
Derive::desconstructor run
Base::desconstructor run
通过基类指针b调用fun1函数,实际调用的是派生类Derive的成员函数fun1,由于在派生类Derive中没有定义成员函数fun2,因此通过基类指针b调用fun2函数,实际调用的依旧是基类Base的成员函数fun2。代码里虽然我们没有对派生类的成员函数fun1加virtual,实际上派生类的成员函数fun1是虚函数。但是对于大多数C 初学者会有2个疑问的地方。1、通过基类指针b调用fun1函数,实际调用的是派生类的成员函数fun1。2、通过delete释放内存使用的是基类指针b,会调用派生类析构函数和基类析构函数,成功释放内存,不会存在内存泄露问题。带有虚函数的类称为虚基类,子类继承虚基类。在C 中虚基类有一个虚函数表指针保存虚函数表地址,而虚函数表保存函数地址,虚函数表并不在虚基类里,但是虚函数表指针在虚基类里,子类继承虚基类,子类也就有了虚函数表指针。那么C 是如何通过虚函数表和虚函数表指针实现多态呢?打开VS2019,并用管理员身份运行"2019开发人员命令提示符"工具,如下图所示:输入:cl /d1 reportSingleClassLayoutXXX [filename],XXX表示类名,[filename]表示类所在的.cpp文件路径。这里我输入源文件的派生类名和源文件路径,回车输出如下:从输出可以看出派生类从基类继承了虚函数表指针vfptr,且占用字节数大小是4字节,刚好就是一个指针占用字节数。虚函数表vftable里保存了派生类成员函数fun1,基类成员函数fun2的地址,由于派生类成员函数fun3不是虚函数,因此虚函数表里没有fun3的地址。看到这里我们就明白了,通过基类指针b调用fun1的过程:通过虚函数表指针vfptr找到虚函数表vftable,再通过虚函数表找到派生类成员函数fun1的地址,调用派生类成员函数fun1。而通过基类指针b调用fun2的过程则是:通过虚函数表指针vfptr找到虚函数表vftable,再通过虚函数表找到基类成员函数fun2的地址,调用基类成员函数fun2。看到这里,第一个疑问已经解开了,关键在于虚函数表指针和虚函数表。在C 中有虚函数的类,其析构函数默认是虚析构函数,只要是虚函数就会在虚函数表里有相应的函数地址,因此派生类里的虚函数表指针vfptr指向的虚函数表vftable必然保存着派生类析构函数的地址,类的析构过程:从继承链的最底端到最顶端依次调用析构函数,因此delete b调用过程:通过虚函数表指针vfptr找到虚函数表vftable,再通过虚函数表找到派生类析构函数地址,调用析构函数。版权申明:内容来源网络,版权归原创者所有。除非无法确认,我们都会标明作者及出处,如有侵权烦请告知,我们会立即删除并表示歉意。谢谢!