前提知识:
对象的内存中只包含成员变量,存储在栈区或堆区(使用 new 创建对象);
成员函数与对象内存分离,存储在代码区。
对象的大小,可以自己分析,int 四个字节,指针也是四个字节。(在x86中)可用sizeof运算符查看对象的大小。
1、普通继承?
代码示例
class A {
public:
int ma;
void func(){
cout << "A :: func" << endl;
}
};
class B : public A {
public:
int mb;
void func() {
cout << "B :: func" << endl;
}
};
内存分布
class A size(4):
+---
0 | ma
+---
class B size(8):
+---
0 | +--- (base class A)
0 | | ma
| +---
4 | mb
+---
?2、普通多继承
代码示例
????????派生类都只有一个基类,称为单继承。C++ 也支持多继承,即一个派生类可以有两个或多个基类。
class A {
public:
int ma;
void func(){
cout << "A :: func" << endl;
}
};
class B : public A {
public:
int mb;
void func() {
cout << "B :: func" << endl;
}
};
class C : public A {
public:
int mc;
void func() {
cout << "C :: func" << endl;
}
};
class D : public B, public C {
public:
int md;
void func() {
cout << "D :: func" << endl;
}
};
?内存分布
????????从多继承的内存分布中,我们可以看到存在一些缺点:在一个派生类中保留间接基类的多份同名成员;菱形继承中,会有命名冲突的出现。
????????类 A 派生出类 B 和类 C,类 D 继承自类 B 和类 C,这个时候类 A 中的成员变量继承到类 D 中变成了两份,一份来自 A-->B-->D 这条路径,另一份来自 A-->C-->D 这条路径。此时,在D中对A中的变量赋值,就会有命名冲突。并且在使用基类A的公开成员函数时,也会指向不明确报错。
class A size(4):
+---
0 | ma
+---
class B size(8):
+---
0 | +--- (base class A)
0 | | ma
| +---
4 | mb
+---
class C size(8):
+---
0 | +--- (base class A)
0 | | ma
| +---
4 | mc
+---
class D size(20):
+---
0 | +--- (base class B)
0 | | +--- (base class A)
0 | | | ma
| | +---
4 | | mb
| +---
8 | +--- (base class C)
8 | | +--- (base class A)
8 | | | ma
| | +---
12 | | mc
| +---
16 | md
+---
?3、虚继承
????????为了解决多继承时的命名冲突和冗余数据问题,C++?提出了虚继承,使得在派生类中只保留一份间接基类的成员。
????????在继承方式前面加上?virtual?关键字就是虚继承;
????????虚继承的目的是让某个类做出声明,承诺愿意共享它的基类。其中,这个被共享的基类就称为虚基类,本例中的 A 就是一个虚基类。在这种机制下,不论虚基类在继承体系中出现了多少次,在派生类中都只包含一份虚基类的成员。
?代码示例
class A {
public:
int ma;
void func(){
cout << "A :: func" << endl;
}
};
class B :virtual public A {
public:
int mb;
void func() {
cout << "B :: func" << endl;
}
};
class C : virtual public A {
public:
int mc;
void func() {
cout << "C :: func" << endl;
}
};
class D : public B, public C {
public:
int md;
void func() {
cout << "D :: func" << endl;
}
};
?内存分布
在MSVC编译器中,引入虚基类表,如果某个派生类有一个或者多个虚基类,编译器会在派生类对象中安插一个指针(vbptr)指向虚基类表(vbtable)。虚基类表放的是偏移量。
class A size(4):
+---
0 | ma
+---
class B size(12):
+---
0 | {vbptr}
4 | mb
+---
+--- (virtual base A)
8 | ma
+---
B::$vbtable@:
0 | 0
1 | 8 (Bd(B+0)A)
class C size(12):
+---
0 | {vbptr}
4 | mc
+---
+--- (virtual base A)
8 | ma
+---
C::$vbtable@:
0 | 0
1 | 8 (Cd(C+0)A)
class D size(24):
+---
0 | +--- (base class B)
0 | | {vbptr}
4 | | mb
| +---
8 | +--- (base class C)
8 | | {vbptr}
12 | | mc
| +---
16 | md
+---
+--- (virtual base A)
20 | ma
+---
D::$vbtable@B@:
0 | 0
1 | 20 (Dd(B+0)A)
D::$vbtable@C@:
0 | 0
1 | 12 (Dd(C+0)A)
?4、虚函数
????????如果一个类包含了虚函数,那么在创建该类的对象时就会额外地增加一个数组,数组中的每一个元素都是虚函数的入口地址。每定义的任何一个对象,都会有vfptr,vfptr指向的是同一块vftable。
? ? ? ? 子类继承了父类之后,同名函数(包括返回值,函数名,参数列表)也会自动是virtual类型。子类的vftable中的函数,只有 virtual类型,不是virtual类型的,不会在vftable中。
代码示例
class A {
public:
int ma;
virtual void func() {
cout << "A" << endl;
}
};
class B : public A {
public:
int mb;
void func() {
cout << "B" << endl;
}
void func2() {
cout << "B" << endl;
}
virtual void func3() {
}
};
内存分布
class A size(8):
+---
0 | {vfptr}
4 | ma
+---
A::$vftable@:
| &A_meta
| 0
0 | &A::func
class B size(12):
+---
0 | +--- (base class A)
0 | | {vfptr}
4 | | ma
| +---
8 | mb
+---
B::$vftable@:
| &B_meta
| 0
0 | &B::func
1 | &B::func3
5、虚继承(多)、虚函数
代码示例
class A {
public:
int ma;
virtual void func(){
cout << "A :: func" << endl;
}
};
class B :virtual public A {
public:
int mb;
void func() {
cout << "B :: func" << endl;
}
};
class C : virtual public A {
public:
int mc;
void func() {
cout << "C :: func" << endl;
}
};
class D : public B, public C {
public:
int md;
void func() {
cout << "D :: func" << endl;
}
};
?内存分布
class A size(8):
+---
0 | {vfptr}
4 | ma
+---
A::$vftable@:
| &A_meta
| 0
0 | &A::func
class B size(16):
+---
0 | {vbptr}
4 | mb
+---
+--- (virtual base A)
8 | {vfptr}
12 | ma
+---
B::$vbtable@:
0 | 0
1 | 8 (Bd(B+0)A)
B::$vftable@:
| -8
0 | &B::func
class C size(16):
+---
0 | {vbptr}
4 | mc
+---
+--- (virtual base A)
8 | {vfptr}
12 | ma
+---
C::$vbtable@:
0 | 0
1 | 8 (Cd(C+0)A)
C::$vftable@:
| -8
0 | &C::func
class D size(28):
+---
0 | +--- (base class B)
0 | | {vbptr}
4 | | mb
| +---
8 | +--- (base class C)
8 | | {vbptr}
12 | | mc
| +---
16 | md
+---
+--- (virtual base A)
20 | {vfptr}
24 | ma
+---
D::$vbtable@B@:
0 | 0
1 | 20 (Dd(B+0)A)
D::$vbtable@C@:
0 | 0
1 | 12 (Dd(C+0)A)
D::$vftable@:
| -20
0 | &D::func
?6、多态的指向
在多态中,基类指针指向派生类对象时候,永远指向的是派生类基类部分数据的起始地址。
父类并不能访问子类中在父类未出现的函数和变量。只能访问虚函数表中的函数。
|