⭐上篇文章:29.C++多态 2 (重载,重定义(隐藏),重写 三者的区别)-CSDN博客
⭐本篇代码:c++学习/17.C++三大特性-多态 · 橘子真甜/c++-learning-of-yzc - 码云 - 开源中国 (gitee.com)
⭐标⭐是比较重要的部分
目录
一. 抽象类
二. 多态的原理 ⭐⭐
2.1 计算带有虚函数的类大小
2.3 多态原理分析
一. 抽象类
代码举例:
下面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个字节,即虚函数指针的地址
测试代码如下:
#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;
}
可以看到虚表的地址在代码段