继承与多态-多重继承
文章目录
- 继承与多态-多重继承
- 1.虚基类和虚继承
- 本节内容
- 2.菱形继承---怎么解决?
- 本节内容
- **面试问题: 怎么理解多重继承的?**---重点
- 3.c++提供的四种类型转换
- 本节内容
1.虚基类和虚继承
本节内容
-
多重继承?
代码复用, 一个派生类 有多个基类
抽象类—有纯虚函数的类 -
虚基类
virtual的两种修饰 -
修饰成员方法----叫做虚函数
-
修饰继承方式—>虚继承. 被虚继承的类, 称为虚基类
class A { private: int ma; };
class B : virtual public A
{
private:int mb;
};
-
看一下虚基类的结构
基类被虚继承后的 内存布局 如下: 多了 vbptr 和 vbtable, 注意区别 vfptr与vftable
virtual base 与 virtual funcclass A size(4):+---0 | ma+---class B size(12): ---- x86上+---0 | {vbptr} ----- 重点 : 指向vbtable4 | mb+---+--- (virtual base A) ---- 这里开始是A8 | ma+---B::$vbtable@:0 | 01 | 8 (Bd(B+0)A) ----- 重点, 相较于vbptr的偏移量
-
根据这个结构, 思考下面这个例子为什么出错?
#include <iostream> using namespace std;class A { private: int ma; public:virtual void func(){cout << "call A:func" << endl;}//添加一个 new重载, 看看 new和delete的一不一样void operator delete(void* ptr){cout << "delete p: " << ptr << endl;free(ptr);} }; class B : virtual public A { private:int mb; public:void func(){cout << "call B:func" << endl;}//添加一个 new重载, 看看 new和delete的一不一样void* operator new(size_t size){void* p = malloc(size);cout << "new p: " << p << endl;return p;} };int main() { // 基类指针指向派生类对象, 永远指向 派生类内存起始地址// 正式由于这个原因, 使得虚基类中, 派生类继承的虚基类, 在内存结构最下面, 起始是vbptr, 使得堆上释放会出错 A *a= new B();a->func(); // 可以正确调用delete a; // 但是释放有问题return 0; }/* new p: 00BB6FA0 call B:func delete p: 00BB6FA8 *///发现new和delete的不是一块地方, --- 编译器不同, 可能会没有这个问题 //vs不行, 但是linux的g++确是正确的//实测发现 g++ (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0 的g++ 貌似也不行,没搞明白
vs里不使用堆, 就没有这个问题
B b; A *a= &b; a->func();
2.菱形继承—怎么解决?
本节内容
-
菱形继承示意图
A/ \/ \B C\ /\ /DA/|\/ | \B C D\ | /\|/EA/ \B C\ / \D E\ /F
-
菱形继承面临的问题
第一个图中,在菱形继承中,D 会包含两份 A 的成员(通过 B 和 C),这可能导致二义性和资源浪费。 -
cpp开源代码,很少有 多重继承
-
实例: 重复构造, 浪费资源
#include <iostream> using namespace std;class A { private: int ma; public:A(int data) :ma(data) { cout << "A()" << endl; }~A() { cout << "~A()" << endl; }};class B : public A { private:int mb; public:B(int data) :A(data), mb(data) { cout << "B()" << endl; }~B() { cout << "~B()" << endl; }};class C : public A { private:int mc; public:C(int data) :A(data), mc(data) { cout << "C()" << endl; }~C() { cout << "~C()" << endl; }};class D : public B, public C { private:int md; public:D(int data) :B(data),C(data), md(data) { cout << "D()" << endl; }~D() { cout << "~D()" << endl; }};int main() { D d(10);return 0; }A() B() A() C() D() ~D() ~C() ~A() ~B() ~A()
使用虚继承解决, 继承的A全部换为虚继承
-
特别注意:由于B,C都是虚继承, D继承B,C时, A的vbtable是在D里面的, 因此D里面必须对A构造, 默认构造是不需要这样的, 但是A里面写了 自定义带参构造, 就需要写上了
#include <iostream> using namespace std;class A { private: int ma; public:A(int data) :ma(data) { cout << "A()" << endl; }~A() { cout << "~A()" << endl; }};class B : virtual public A { private:int mb; public:B(int data) :A(data), mb(data) { cout << "B()" << endl; }~B() { cout << "~B()" << endl; }};class C : virtual public A { private:int mc; public:C(int data) :A(data), mc(data) { cout << "C()" << endl; }~C() { cout << "~C()" << endl; }};class D : public B, public C //B和C里面的vbptr存储的偏移量 是不同的 { private:int md; public:D(int data) :A(data), B(data),C(data), md(data) { cout << "D()" << endl; }~D() { cout << "~D()" << endl; }};int main() { D d(10);return 0; }A() B() C() D() ~D() ~C() ~B() ~A()
面试问题: 怎么理解多重继承的?—重点
好处:可以更多的 代码复用
坏处: 菱形继承会出现 派生类 有多份 间接基类的数据 设计的问题
3.c++提供的四种类型转换
本节内容
-
c中类型强转:
int a = (int)b; -
cpp里的语言级别强转
const_cast:去掉常量属性的类型转换.static_cast:能提供编译器认为安全的类型转换.---做有关联的类型转换 reinterpret_cast:类似于c风格的强转.dynamic_cast:主要用于继承结构中, 可以支持RTTIL类型识别的上下转换
-
代码:
#include <iostream> using namespace std;class A { public:virtual void func() { cout << "~A()" << endl; }};class B : public A {public:void func() { cout << "~B()" << endl; }};class C : public A {public:void func() { cout << "~C()" << endl; }//添加新功能, void funcss(){cout << "sssss" << endl;}};void classShow(A* p) //指针若是C类型, 就调用funcss, 而不是func {//typeid(*p).name()=="C", 不用这个, 用dynamic_cast//dynamic_cast会检查 p指针 是否指向的 是一个 C类型的对象// p->vfptr->vftable RTTI信息, 如果是, dynamic转型成功, // 返回C对象的地址给 c2, 否则返回 nullptr//编译时期类型转换, 不能识别RTTI信息C* c2 = dynamic_cast<C*>(p); //static_cast在这里永远都能强转成功if (c2 != nullptr){c2->funcss();}else p->func(); }int main() { //const int a = 10;//**************************************************************************************************************//const_cast//int* p = (int*)&a;//int* p2 = const_cast<int*>(&a); //二者在汇编上是一样的, 但是const_cast不能跨类型转换, 只能基本类型一样的转换/*int* p = (int*)&a; 00D51FC7 lea eax,[a] 00D51FCA mov dword ptr [p],eax int* p2 = const_cast<int*>(&a); 00D51FCD lea eax,[a] 00D51FD0 mov dword ptr [p2],eax *///double* p3 = (double*)&a;//double* p4 = const_cast<double*>(&a); // 这就是不行的//*****************************************************************************************************************//static_cast----编译时期类型转换//int a = 10;//char b = static_cast<int>(a);//int* p = nullptr;//short* c = static_cast<short*>(p); // 只能做有联系的类型转换/* 1. 什么是有关系的类型?继承关系:例如基类指针和派生类指针。基本类型的隐式转换:例如 int 到 double。用户定义的类型转换:例如类中定义了转换运算符。在这些情况下,static_cast 可以安全地使用。2. 什么是无关的类型?完全不同的指针类型:例如 int* 和 double*,char* 和 float* 等。不相关的类类型:例如两个没有继承关系的类。*///*****************************************************************************************************************//reinterpret_cast 类似与c风格, 啥都不管//*****************************************************************************************************************//dynamic_castA a;B b;C c;classShow(&a);classShow(&b);classShow(&c);//结合classShow处看return 0; }
-
什么是 RTTI?
RTTI 是 C++ 的一种特性,允许程序在运行时获取对象的类型信息。它主要由以下两部分组成:
typeid
运算符:用于获取对象的类型信息。dynamic_cast
运算符:用于在继承层次中进行安全的类型转换。
RTTI 需要编译器支持,并且在运行时需要额外的内存来存储类型信息。