C++继承与菱形继承

继承是⾯向对象复⽤的重要⼿段。
通过继承定义⼀个类,继承是类型之间的关系建模,共享公有的东西,实现各⾃本质不同的东西。

我们类成员访问限定符有三种:
public(公有),protected(保护),private(私有)
我们的继承关系也有三种:
public(公有继承),protected(保护继承),private(私有继承)

下面是一个简单的继承:

class A//基类(父类)
{
public :
 int _a ; 
};
//B为派生类(子类),public为继承关系
class B : public A
{
public :
 int _b;
};

FF

总结:

  1. 基类的私有成员在派⽣类中是不能被访问的,如果⼀些基类成员不想被基类对象直接访问,但需要在派⽣类中能访问,就定义为保护成员。可以看出保护成员限定符是因继承才出现的。
  2. public继承是⼀个接口继承,保持is-a(包含了所有父类的性质)原则,每个⽗类可⽤的成员对⼦类也可⽤,因为每个⼦类对象也都是⼀个⽗类对象。
  3. protetced/private继承是⼀个实现继承,基类的部分成员并未完全成为⼦类接⼜的⼀部分,是 has-a(有父类的性质) 的关系原则,所以⾮特殊情况下不会使⽤这两种继承关系,在绝⼤多数的场景下使⽤的都是公有继承。
  4. 不管是哪种继承⽅式,在派⽣类内部都可以访问基类的公有成员和保护成员,但是基类的私有成员存在但是在⼦类中不可见(不能访问)。
  5. 使⽤关键字class时默认的继承⽅式是private,使⽤struct时默认的继承⽅式是public,不过最好显⽰的写出继承⽅式。
  6. 在实际运⽤中⼀般使⽤都是public继承。

继承与转换–赋值兼容规则–public继承(前提条件)
1. ⼦类对象可以赋值给⽗类对象(切割/切⽚)
2. ⽗类对象不能赋值给⼦类对象
3. ⽗类的指针/引⽤可以指向⼦类对象
4. ⼦类的指针/引⽤不能指向⽗类对象(可以通过强制类型转换完成)

class A//基类(父类)
{
public :
 int _a ; 
};
//B为派生类(子类),public为继承关系
class B : public A
{
public :
 int _b;
};
void Test()
{
A p;
B s;
}

// 1.子类对象可以赋值给父类对象(切割 /切口)
 p = s ;
 // 2.父类对象不能赋值给子类对象
 //s = p;
 // 3.父类的指针/引用可以指向子类对象
 Person* p1 = &s;
 Person& r1 = s;
 // 4.子类的指针/引用不能指向父类对象(可以通过强制类型转换完成)
 Student* p2 = (Student*)& p;
 Student& r2 = (Student&) p;

隐藏(重定义)

如果父类子类中有相同的函数,子类对象调用该会调用哪一个呢

class A//基类(父类)
{
public:
 void Fun()
 {
 }
public :
 int _a ; 
};
//B为派生类(子类),public为继承关系
class B : public A
{
public :
 void Fun()
 {
 }
public :
 int _b;
};
void Test()
{
A p;
B s;
s.Fun()
}

我们发现这个函数会先调用子类自己的函数
我们把在不同作用域(在父类与子类中)相同的函数名叫做重定义(或隐藏)

继承体系中的作⽤域
1. 在继承体系中基类和派⽣类都有独⽴的作⽤域。
2. ⼦类和⽗类中有同名成员,⼦类成员将屏蔽⽗类对成员的直接访问。(在⼦类成员函数中,可以使⽤基类::基类成员 访问)–隐藏 –重定义

我们会先构造父类,在构造子类,析构时会先析构子类,在析构父类。

菱形继承

单继承&多重继承
1. 单继承–⼀个⼦类只有⼀个直接⽗类时称这个继承关系为单继承
2. 多继承–⼀个⼦类有两个或以上直接⽗类时称这个继承关系为多继承

菱形继承是多继承

class A
{
public:
    int _a;
};
class B : public A
{
public:
    int _b;
};
class C : public A
{
public:
    int _c;
};
class D : public B, public C
{
public:
    int _d;
};

fg

#include<iostream>
#include<windows.h>
using namespace std;
class A
{
public:
    int _a;
};
class B : public A
{
public:
    int _b;
};
class C : public A
{
public:
    int _c;
};
class D : public B, public C
{
public:
    int _d;
};
int main()
{
    D b;
    b.B::_a = 1;
    b.C::_a = 2;
    b._b = 3;
    b._c = 4;
    b._d = 5;
    system("pause");
    return 0;
}

菱形继承的对象模型
发
菱形继承的内存分配:
豆腐干

sizeof(b)的结果是20;

我们可以看出D中有两份_a浪费空间
当我们 d._a时就会有调用不明确的问题,这就是二义性与数时据冗余。
所以菱形继承存在二义性和数据冗余性。

那么这个问题怎么解决呢

解决方案1:加作用域限定符

fd

如图我们要修改B或者C中的_a我们就要加作用域

解决方案2:虚继承——解决菱形继承的二义性和数据冗余问题

虚继承

虚继承——解决菱形继承的⼆义性和数据冗余的问题
1. 虚继承解决了在菱形继承体系⾥⾯⼦类对象包含多份⽗类对象的数据冗余和浪费空间的问题。
2. 虚继承体系看起来好复杂,在实际应⽤我们通常不会定义如此复杂的继承体系。⼀般不到万不得已都不要定义菱形结构的虚继承体系结构,因为使⽤虚继承解决数据冗余问题也带来了性能上的损耗。

什么是虚继承?
鬼地方

如上图,虚继承即让B和C在继承A时加上virtural关键字,这里需要记住不是D使用虚继承

那么虚继承在这里是如何解决这个问题的

#include<iostream>
#include<windows.h>
using namespace std;
class A
{
public:
    int _a;
};
class B :virtual  public A
{
public:
    int _b;
};
class C :virtual  public A
{
public:
    int _c;
};
class D : public B, public C
{
public:
    int _d;
};
int main()
{
    D b;
    b._a = 1;
    b._a = 2;
    b._b = 3;
    b._c = 4;
    b._d = 5;
    cout << sizeof(b) << endl;
    system("pause");
    return 0;
}

这个程序输出的结果是24

我们把A叫做虚基类,虚基表存放的是虚基类的偏移量

我们发现这样修改的_a的值_a都会改变,这样_a就只有一份
发生的

不

我们可以看见在B和C中不再保存A中的内容,而是保存了一份偏移地址,然后将A的数据保存在一个公共位置处这样保证了数据冗余性的降低同时,我们也能直接的使用b._a来调用A里的_a。

所以它的对象模型是如图:

SD

sizeof(b)=24.

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值