c++中的虚函数除了写到的内容之外,还有一些用法,这次就把它们全部补齐吧。
虚析构函数
上篇文章中说到,析构函数也是可以声明为虚函数的,如果你打算允许通过基类指针调用对象的析构函数(通过delete函数,这样是合法的),就需要让基类的虚构函数变成虚函数,否则执行delete的结果将是不确定的。
来看一个例子:
1 | class Base |
在这个例子中,我们想要的效果是通过del函数,释放掉指针指向的内存空间,但是输出结果确实只删除了基类对象。这是因为析构函数不是虚函数,所以在编译阶段编译器就要决定删除这个指针所指向的空间,由于它只知道现在的状态是基类的对象,所以它只能调用基类的析构函数,因为我们派生类的空间没有被释放,这就会导致内存泄漏。
而为了解决这个问题,我们只需要将基类和派生类析构函数定义为虚函数,这样在编译过程中,编译器就不会直接静态绑定基类的析构函数,而是在运行过程中,动态的执行。
1 | class Base |
虚表与动态绑定
那么在运行的时候,虚函数是怎么实现动态绑定的呢?每个多态类都有一个虚表,这个虚表中有当前类各个虚函数的入口,而在每个对象中也都有指向当前类虚表的指针。具体实现过程如下:
- 构造函数中为对象的虚指针赋值
- 通过多态类型的指针或引用调用成员函数时,通过虚指针找到虚表,进而找到虚函数的入口
- 最后通过该入口地址调用虚函数
来一个图示(图源清华在线):纯虚函数和抽象类
虚函数中还有一种特殊的存在,纯虚函数,这种函数没有自己本身的函数体,具体用法是这样的:也正是因为这种函数没有函数体,所以拥有纯虚函数的类无法实例化为具体的对象,一般也把这样的类称之为抽象类1
2//virtual + 函数 + =0
virtual void fun() = 0;
那么既然这种类无法实例化为具体的对象,那它还有什么作用呢?抽象类更重要的是最为一种规范,它作为基类,为它的派生类规定了函数的写法,让代码更加规范,这样也充分的利用了虚函数动态绑定的特性。(虽然不能将抽象类实例化,但是可以使用抽象类的指针让它指向它的派生类等等)
override和final
在c++11中,在使用类的时候新增了两个关键字,override和final,它们在我们对类进行继承操作时可以起到很大的帮助
override
override是重载函数的关键字,当我们在一个派生类中想要对基类的函数进行重载时,可以加入override关键字,这样在编译的过程中,编译器会将你想要重载的函数看作是函数的重载,并且在基类中寻找同名的被重载函数,如果找不到则直接报错,虽然c++默认就带有继承功能,但是使用override之后可以避免很多因为函数名书写错误或由于函数是const类型而导致的重载失败。
final
当我们想保证一个类或其中函数的稳定性,让它不能被继承,我们就可以使用final关键字,当类加上final关键字,这个类将不能被继承,而当函数中的类加上final关键字,虽然这个类可以被继承,但是在继承类中不能对这个函数进行重载操作,否则会报错。