继承与多态-多重继承
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} ----- 重点 : 指向vbtable 4 | mb +--- +--- (virtual base A) ---- 这里开始是A 8 | ma +--- B::$vbtable@: 0 | 0 1 | 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 \ / \ / D A /|\ / | \ B C D \ | / \|/ E A / \ 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_cast A a; B b; C c; classShow(&a); classShow(&b); classShow(&c); //结合classShow处看 return 0; }
-
什么是 RTTI?
RTTI 是 C++ 的一种特性,允许程序在运行时获取对象的类型信息。它主要由以下两部分组成:
typeid
运算符:用于获取对象的类型信息。dynamic_cast
运算符:用于在继承层次中进行安全的类型转换。
RTTI 需要编译器支持,并且在运行时需要额外的内存来存储类型信息。