C++虚函数知识点总结
虚函数
注意:
在函数声明的返回类型之前加virtual。
并且只在函数的声明中添加virtual,在该成员函数的实现中不用加。
虚函数的继承
- 如果某个成员函数被声明成虚函数,那么他的子类,以及子类中的子类 ,所计继承的这个成员函数,也自动是虚函数。
- 如果在子类中重写这个虚函数,可以不用再加virtual,但仍然建议加上virtual,提高代码的可读性。
虚函数原理——虚函数表
对应虚函数的类,该类的对象所占内存大小为,数据成员的大小+一个指向虚函数表指针 (4字节)。
例如:如下所示Father类所创建的对象
1 | class Father |
1 | Father father; |
结果为12,两个int的数据成员4+4一共占了8个字节,再加上一个虚函数表指针(4个字节),一共是12个字节
( 如果该类中没有虚函数,就没有虚函数表指针,也就少4个字节)
如下图所示:
思考:它尽然是个指针,那我们就能通过这个指针来访问它所指向内存所对应的内容。
(先存的是虚函数表指针,然后才是数据成员。)
所以说,对象地址就是虚函数表地址。
1 | cout<<(int*)&father<<endl; |
强转成指针。
接着,取出虚函数表的指针。
1 | int* vptr = (int*)*(int*)(&father); |
为了编译器能通过,前面加上int*。
然后,就找到了虚函数,并执行方法。
为了便于调用,这里定义个函数指针类型。
1 | typedef void(*func_t)(void); |
func_t指针,指向参数为void,返回值为void的函数。
调用虚函数。
1 | ((func_t)*(vptr))(); |
调用成功。
接着调用x,y两个数据成员。
1 | cout << *(int*)((int)&father+ 4) << endl; |
取到地址,转成int整数,加上偏移量,通过编译器加上(int*),再解引用,得到里面的值。
(+上偏移量要先转成int)
多态的使用:父类指针指向子类对象
1 | Father* father1 = &son; |
使用继承的虚函数表
在上面的基础上,为Father类添加一个派生类。并且对Father的func1进行重写,再添加一个它独有的func5,声明为虚函数。
1 | class Son :public Father |
同上面通过使用指向虚函数表的指针来访问对应的内容
1 | for (int i = 0; i < 4; i++) |
1 | // 访问两个成员 |
子类虚函数表
直接复制父类的虚函数表
如果子类重写了父类的某个虚函数,那么就在这个虚函数表中进行相应的替换
如果子类中添加的新的虚函数,就把这个虚函数添加到虚函数表中(尾部添加)
![image-20210924121136461](/images/C++虚函数知识点总结.assets/image-20210924121136461.png)
使用多重继承的虚函数表
在上面的基础上再添加一个Mother类
1 | class Mother |
此时的Son类对象
vs编译器中把子类自己的虚函数放到了第一个父类的虚函数表最后
同样通过指针访问对应的虚函数表内容
1 | Son son; |
小补充:
对象地址+偏移量
转化int类型 + 对应的字节个数
转化int*类型 + 走几步(几个步长)
虚函数的修饰
final
final——C++11更新
1.用来修饰类,让该类不能被继承。
1 | class XiaoMi |
(补充:C++默认继承方式为private)
2.用来修饰虚函数,使得该虚函数在子类中,不得被重写。但是还可以使用。
override
override仅能修饰虚函数。
只能用在函数的声明,函数的实现不要写。
作用:
- 提示程序的阅读者,这个函数是重写父类的功能。
- 防止程序员在重写父类的函数时,把函数名写错。
父类的虚析构函数
把father类的指针定义为virtual时,并且对父类的指针执行delete操作时, 就是对该指针使用”动态析构”。
如果这个指针指向的是子类对象,那么会先调用该子类的析构函数,再调用父类的析构函数。
如果指向的是父类对象,那么只调用父类的析构函数。
注意:
为了防止内存泄露,最好在基类的虚构函数上添加virtual关键字,使基类析构函数为虚函数。
纯虚函数与抽象类
什么时候使用纯虚函数?
某些类,现实项目和实现角度吗,都不需要实例化(不需要创建它的对象)。
这个类中定义的某些成员函数只是为了提供一个形式上的接口,准备让自子类来做具体的实现。
此时这个函数就可以定义为”纯虚函数“,包含纯虚函数的类,就叫做抽象类(不能创建对象)。
继承该抽象类的子类如果不重写这个纯虚函数,那么它也是不能创建对象的。
用法:
virtual +函数 = 0
代码示例:
1 |
|
纯虚函数的注意事项:
父类声明为某纯虚函数之后,它的子类:
- 实现这个纯虚函数
- 继续把这个纯虚函数声明为纯虚函数,这个子类也称为抽象类
- 不对这个纯虚函数做任何处理,等效于上一种情况(不推荐)