⭐上篇文章:29.C++多态 2 (重载,重定义(隐藏),重写 三者的区别)-CSDN博客
⭐本篇代码:c++学习/17.C++三大特性-多态 · 橘子真甜/c++-learning-of-yzc - 码云 - 开源中国 (gitee.com)
⭐标⭐是比较重要的部分
目录
一. 抽象类
带有纯虚函数的类就是抽象类,抽象类无法实例化为对象。在虚函数后面加上 = 0,这个函数就是纯虚函数。
纯虚函数的实现在子类中实现,以便完成多态
代码举例:
下面A是一个纯虚函数,B继承A但是没有重写test,C重写了test
#include <iostream>
using namespace std;
class A
{
public:
virtual void test() = 0; //纯虚函数在子类中实现
};
class B : public A
{};
class C : public A
{
public:
virtual void test()
{
return;
}
};
int main()
{
A a;
B b;
C c;
}
可以看到编译器不能生产 A 和 B的对象
这说明,如果抽象类的子类没有重写纯虚函数,这个类仍是抽象类
抽象类的作用:
强制子类去重写虚函数
世界上有很多事物是抽象的,需要抽象类去表示
二. 多态的原理 ⭐⭐
2.1 计算带有虚函数的类大小
分析一下,下面的输出是多少?
#include <iostream>
using namespace std;
class A
{
public:
virtual void test1() {}
void test2() {}
private:
int _a;
};
int main()
{
A a;
cout << sizeof(a) << endl;
return 0;
}
分析类(结构体)的大小我们需要了解:
1 需要使用内存对齐规则。
2 类中的成员函数不占用空间。
可以看这篇文章:
5.C++面向对象2(类对象大小计算,结构体内存对齐,大小端存储方式,this指针)-CSDN博客
上面的类A中,可以看到只有一个成员变量int占用4字节,最大对齐数 = min(4,8) = 4。所以
sizeof(A)应该是4字节。
可事实是这样的吗?
可以看到,输出的结果是8, 这里我们调试看一下
可以看到a中除了_a变量, 还有一个指针, 这个指针指向了函数test。
所以在32位系统下,int变量占4位,指针占4位。所以输出的结果是8.
如果换成64位,指针占8位。那么结果应该是16位
2.2 虚函数表指针与虚表
在上面的测试中,可以看到对象a中有一个指针,这个指针是虚函数指针,本质是一个指针数组。
虚函数指针指向了一张虚函数表(虚表),这个表中存放的是这个类的虚函数
增加更多的函数,并且一个类继承这个类进行观察。
测试代码如下:
#include <iostream>
using namespace std;
class A
{
public:
virtual void test1() {}
virtual void test2() {}
void test3() {}
private:
int _a;
};
class B : public A
{
public:
virtual void test1() {}
void test5() {}
private:
int _b;
};
int main()
{
A a;
B b;
return 0;
}
调试查看的结果如下:
可以看到,这个虚函数指针指向的虚函数表中存放的都是虚函数的地址。
如果子类重写了父类的虚函数,那么在子类的虚函数表中的这个重写的虚函数就会覆盖父类的虚函数。
如果子类没有重写父类的虚函数,那么在子类的虚函数表中的虚函数和父类的虚函数是一样的。
派生类虚表生产的顺序为,1 拷贝一份父类的虚表 2 将重写的虚函数覆盖这个虚表 3 将增加的虚函数的地址写入虚表中(如果有的话)
2.3 多态原理分析
实现多态的两个条件:
1 完成对虚函数的重写
2 使用父类的指针或者引用去调用这个虚函数
多态的原理是一种动态绑定。当我们的基类指针去调用相应对象的函数时候(这个时候是运行期间),会根据这个对象中的虚函数指针找到对应的虚表,再根据虚表中的相应函数地址去调用这个函数。最后完成多态
满足多态以后的函数调用,不是在编译时确定的,是运行起来以后到 对象的中取找的。不满足多态的函数调用时编译时确认好的。
三. 虚函数与虚函数表的存放位置
1 虚函数是函数,存放再代码段
2 虚函数表存放的是指针,是虚函数的地址。我们通过代码分析可以看到虚函数表其实也存放在代码段。
对于一个类对象 a 虚函数指针在第一个
&a 就是a的地址
(long long*)&a 就可以获取前8个字节,即虚函数指针的地址
*((long long*)&a) 对虚函数指针进行解引用就能找到虚表地址
测试代码如下:
#include <iostream>
using namespace std;
class A
{
public:
virtual void test1() {}
private:
int _a;
};
void f() {}
int main()
{
A a;
int b;
int* c = new int;
const string d = "123";
printf("虚函数表地址:%p\n", (void*)*(long long*)&a);
printf("栈区地址:%p\n", &b);
printf("堆区地址:%p\n", &c);
printf("常量区地址:%p\n", &d);
printf("代码区地址:%p\n", &f);
return 0;
}
可以看到虚表的地址在代码段