C++ 继承与多态详解2
Question00:
静态绑定/静多态:
编译时期
动态绑定/动多态:
调用函数最终被 编译成 call eax。寄存器的值在运行时才知道,eax从虚函数表vftable里拿的虚函数地址。
Question01:
1 很多类定义的对象,用一个变量去统一的接收:基类的指针或者引用。
2 想知道基类指针p 指向的哪个Derive对象?要借助一个识别RTTI的类型强转,dynamic_cast 当基类指针真的指向一个派生类对象时,强转才会成功。若失败,返回的指针q是NULL
Base *p;
Derive *q=dynamic_cast<Derive*>(p);
##########################分割线####################################
C++ 多重继承 虚继承 虚基类
Question1: 多重继承易出现:菱形继承的结构
注:打开VS的命令行提示 :查看 类的内存布局
命令如下:cl 文件名.cpp /d1reportSingleClassLayout类名
class Base // 虚基类
{
public:
Base(int data = 10) :ma(data) { cout << "Base()" << endl; }
~Base() { cout << "~Base()" << endl; }
private:
int ma;
};
/*
vbptr(virtual base ptr) ------> vbtable 0 8
mb
ma
*/
class Derive : public Base // 普通继承
{
public:
Derive(int data = 20) :Base(data), mb(data) { cout << "Derive()" << endl; }
~Derive() { cout << "~Derive()" << endl; }
private:
int mb;
};
int main()
{
cout << sizeof(Derive) << endl; // 8
return 0;
}
普通继承,派生类内存布局如下:
虚继承,派生类内存布局如下: 大小变成12 而不是8
分析: Base是虚基类,把虚基类部分的数据移动到 派生类数据部分的后面。并在原来放虚基类部分的数据的地方 加上一个vbptr :虚基类指针。指向虚基类表 vbtable。这个vbtable有两行:
第一行:vbptr离 最近的作用域 起始位置的偏移量。但它就在最起始位置 故偏移量为0字节
第二行:虚基类指针vbptr到虚基类数据ma的偏移量 8字节
其代码如下:
class Base // 虚基类
{
public:
Base(int data = 10) :ma(data) { cout << "Base()" << endl; }
~Base() { cout << "~Base()" << endl; }
private:
int ma;
};
/*
vbptr(virtual base ptr) ------> vbtable 0 8
mb
ma
*/
class Derive : virtual public Base // 虚继承
{
public:
Derive(int data = 20) :Base(data), mb(data) { cout << "Derive()" << endl; }
~Derive() { cout << "~Derive()" << endl; }
private:
int mb;
};
int main()
{
cout << sizeof(Derive) << endl;
return 0;
}
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
下面给出一个菱形继承:
class A
{
public:
A(int data) :ma(data) { cout << "A()" << endl; }
~A(){cout << "~A()" << endl; }
private:
int ma;
};
class B : public A
{
public:
B(int data) :A(data), mb(data) { cout << "B()" << endl; }
~B(){cout << "~B()" << endl; }
private:
int mb;
};
class C : public A
{
public:
C(int data) :A(data), mc(data) { cout << "C()" << endl; }
~C(){cout << "~C()" << endl; }
private:
int mc;
};
class D : public B, public C
{
public:
D(int data):B(data), C(data), md(data) { cout << "D()" << endl; }
~D(){cout << "~D()" << endl; }
private:
int md;
};
int main()
{
D d(10);
return 0;
}
其构造 析构顺序如图:
此时D的内存布局如下:
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
解决方式:谁直接继承A,就把继承方式改为:虚继承
即:
class B : virtual public A
class C: virtual public A
此时的运行结果:
此时的内存布局:
分析:
1 现在B虚继承A,需要把(A的作用域:ma)移动到派生类D的最后面去,即 紧挨着md;而且在原来虚基类部分放vbptr。
2 现在C虚继承A,需要把(A的作用域:ma)移动到派生类D的最后面去,但是发现后面已经有一个A了,所以就不放了;而且在原来虚基类部分放vbptr,偏移量。
3 内存布局如下:
B::
vbptr========》 vbtable 0 14h 20字节
mb
C::
vbptr========》 vbtable 0 0Ch 12字节
mc
md
A::
ma
4: 通过虚继承解决了两个ma的问题;但是随之而来的是:1 内存变大 2 原来在D的作用域里面需要D构造的是 B和C,原来的两个A构造隶属于B C,但现在把虚基类数据搬到D作用域的最下面了。
出现问题如下:
这里D为什么要操心 A的构造?因为虚继承以后,派生类内存布局更改,A和B C一样也是成为D应该负责构造的基类。所以这里必须显式地指定出来虚基类A的构造
class D : public B, public C
{
public:
D(int data):B(data), C(data), A(data),md(data) { cout << "D()" << endl; }
~D(){cout << "~D()" << endl; }
private:
int md;
};
一个派生类有两种基类:普通基类和虚基类。虚基类数据的构造优先级高,在这里生成一个派生类D的对象,先调用A的构造 接下来B C D。且A只构造一份。
如何解决派生类里面拥有间接基类多份数据(尤其是这种菱形继承中):应该在所有从基类直接继承的地方都采用 虚继承。
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
这里 A B C D E
class A
{
public:
A(int data) :ma(data) { cout << "A()" << endl; }
~A(){cout << "~A()" << endl; }
private:
int ma;
};
class B : virtual public A
{
public:
B(int data) :A(data), mb(data) { cout << "B()" << endl; }
~B(){cout << "~B()" << endl; }
private:
int mb;
};
class C : virtual public A
{
public:
C(int data) :A(data), mc(data) { cout << "C()" << endl; }
~C(){cout << "~C()" << endl; }
private:
int mc;
};
class E
{
public:
E(int data):me(data) { cout << "E()" << endl; }
~E(){ cout << "~E()" << endl; }
private:
int me;
};
class D : public B, public C,virtual public E
{
public:
D(int data):B(data), C(data),E(data), A(data),md(data) { cout << "D()" << endl; }
~D(){cout << "~D()" << endl; }
private:
int md;
};
int main()
{
D d(10);
return 0;
}
运行结果:
派生类D的内存布局:
B::
vbptr========》 vbtable 0 14h 20字节
mb
C::
vbptr========》 vbtable 0 0Ch 12字节
mc
md
A::
ma
E::
me
第一个vbtable 变成3行:因为派生类虚基类 E里面的东西,并没有给E产生vbptr,而是合并到B的虚表里面了。按照原来的做法:应该在md上面,mc下面产生一个vbptr,指向me的偏移量 12字节,但是基类里面有vbptr,所以派生类就不用产生了,把内容和第一个表合并,故而在第一个表里面放me的偏移量24字节。
如果是下面的情况:
class D : virtual public E,public B, public C { public: D(int data):B(data), C(data),E(data), A(data),md(data) { cout << "D()" << endl; } ~D(){cout << "~D()" << endl; } private: int md; };
运行结果:
内存布局:
这个指针合并:派生类自己产生vbptr,将它合并到基类继承来的第一个vbptr里,以节省内存。
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
Question02:
class Base// Base是基类
{
public:
Base(int a) :ma(a){ cout << "Base()" << endl; }
virtual void show() { cout << "Base::show()" << endl; }//虚函数
protected:
int ma;
};
class Derive :virtual public Base
{
public:
Derive(int data) :Base(data), mb(data){ cout << "Derive()" << endl; }
void show() { cout << "Derive::show()" << endl; }
private:
int mb;
};
int main()
{
Base*p = new Derive(10);
p->show();
delete p;
return 0;
}
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
Question03:
常量迭代器 普通迭代器
正向迭代器 反向迭代器
相关文章
- c++中 this指针详解[通俗易懂]
- c++语言截取字符串,详解C++ string常用截取字符串方法
- C++继承
- C++编程语言中重载运算符(operator)介绍「建议收藏」
- c++获取子类窗口句柄位置_C++中各种获取窗口句柄的方法「建议收藏」
- C++继承的基本语法与三种继承方式
- 动态库libstdc++.so.6及libc.so.6版本过低导致MySQL无法安装
- C++ 菱形继承
- C++基础——C++面向对象之类、对象与继承基础总结(类和对象概念、构造函数与析构函数、this指针、继承)
- 继承用法大全——c++面向对象编程(必看)
- c 线程安全的单例模式-C++单例模式(线程安全、内存释放)
- 【C++】"undefined reference to" 问题常见的解决方法
- 【C++ 语言】面向对象 ( 继承 | 重写 | 子类调用父类方法 | 静态多态 | 动态多态 | 虚函数 | 纯虚函数 )
- 【C++修炼之路】20.手撕红黑树
- C++STL——map与set介绍及使用
- C++继承(详解版)
- C++二进制文件的读取和写入(精华版)
- 首个 C++ 编译器诞生 30 周年了,来听听 C++ 之父畅谈 C++
- 编写一个简单的游戏来练习用 C++ 编程
- C++多继承同名隐藏实例详细介绍
- C++中virtual继承的深入理解
- c++基础语法:普通继承
- C语言/C++中如何产生随机数