【C++】面向对象编程的三大特性:深入解析继承机制(三)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 【C++】面向对象编程的三大特性:深入解析继承机制

【C++】面向对象编程的三大特性:深入解析继承机制(二)https://developer.aliyun.com/article/1617389


三、菱形继承及菱形虚拟继承

3.1 继承分类

单继承:当一个子类只有一个直接父类时称这个继承关系为单继承

多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承

菱形继承:属于多继承的一种特殊情况

3.2 菱形继承问题

从上面可以看出来,菱形继承有数据冗余和二义性的问题,在Assistant的对象中Person成员会有两份。

class Person
{
    public :
    string _name ; // 姓名
};
class Student : public Person
{
    protected :
    int _num ; //学号
};
class Teacher : public Person
{
    protected :
    int _id ; // 职工编号
};
class Assistant : public Student, public Teacher
{
    protected :
    string _majorCourse ; // 主修课程
};
void Test ()
{
    // 这样会有二义性无法明确知道访问的是哪一个
    Assistant a ;
    a._name = "peter";
}

Assistant的对象中Person成员会有两份,那么如果单纯a._name = "peter"会产生二义性,编译器无法明确知道访问的是哪一个直接父类的成员。

解决办法:使用指定类域限定符访问

a.Student::_name = "xxx";
a.Teacher::_name = "yyy";

不足之处:需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决

3.3 虚继承(菱形虚拟继承)

3.3.1 虚继承概念

对此引入虚继承这个概念,可以用于解决菱形继承的二义性和数据冗余的问题。

class Person
{
    public :
    string _name ; // 姓名
};
class Student : virtual public Person
{
    protected :
    int _num ; //学号
};
class Teacher : virtual public Person
{
    protected :
    int _id ; // 职工编号
};
class Assistant : public Student, public Teacher
{
    protected :
    string _majorCourse ; // 主修课程
};
void Test ()
{
    Assistant a ;
    a._name = "peter";
}

我们需要在腰部位置上将继承改为虚继承(使用virtual)

3.3.2 虚拟继承解决数据冗余和二义性的原理

这里需要借助内存窗口观察对象成员的模型,调试窗口有时为了方便观察进行了调整,导致结果不准确。

class A
{
    public:
    int _a;
};
// class B : public A
class B : virtual public A
{
    public:
    int _b;
};
// class C : public A
class C : virtual public A
{
    public:
    int _c;
};
class D : public B, public C
{
    public:
    int _d;
};
int main()
{
    D d;
    d.B::_a = 1;
    d.C::_a = 2;
    d._b = 3;
    d._c = 4;
    d._d = 5;
    return 0;
}  

从下列两个模拟来看,一个是没有虚继承导致数据冗余和二义性,一个是完成虚继承解决了数据冗余和二义性

具体说明:

  • 第一种,这里B和C类都存储了一个变量_a,导致数据冗余和二义性。
  • 第二种,这里可以看出D对象中将A放到了对象组成的最下面,这个_a同时属于B和C,修改这个 _a同样会影响到其他类的 _a。
  • 这里我们需要知道B和C如何去找到公共的A,这里是通过B和C的两个指针(就是属于x类内存中第一个存储的地址),去指向一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量(十六进制)可以找到下面的_a。

注意:

  • 内存存储的顺序是按照声明顺序或者继承顺序存储的。

3.3.3 多继承指针偏移特性

继承的指针偏移特性,为了支持动态绑定和多态性。多继承的情况下,由于不同的父类可能有不用的虚函数表,因此需要通过指针偏移来正确访问不同父类的虚函数表。这种指针偏移通常由编译器自动生成的,确保访问父类的成员或者调用父类的虚函数时,能够正确地定位到父类对象内存位置。

四、继承总结和反思

4.1 设计继承建议

一般不建议设计出多继承,一定不要设计出菱形继承,不然在复杂度及性能上都有问题。实践中可以设计多继承,但是切记不要设计菱形继承,因为太复杂,容易出现各种问题

4.2 继承和组合

  • public继承:是一种is-a的关系,也就是说每个派生类对象都是一个基类对象
  • 组合:是一种has-a的关系,假设B组合了A,每个B对象中都有一个A对象

继承允许你根据基类的实现派生类的实现。这种通过生成派生类的复用通常被称为为白箱复用(white-box reuse),术语"白箱"是相对可视性而言:在继承方式中,基类的内部细节对子类可见。继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关系很强,耦合性度高

对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组合或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse),因为对象的内部细节是不可见的。对象只以"黑箱"的形式出现。组合类之间没有很强的依赖关系,耦合度低。优先实现对象组合有助于你保持每个类被封装

实践尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用继承,可以用组合,就用组合

组合比如:

class A
{
private:
    int _ a;
}
//组合
class B
{
  private:
    A _aa;
    int _b;
}

4.3 低耦和与高内聚

  • 低耦和:类和类之间、模块与模块之间关系不那么紧密,关联不高
  • 高耦合:类和类之间、模块与模块之间关系很紧密,关联很高

五、笔试面试题

问题:

  1. 什么是菱形继承?菱形继承的问题是什么?
  2. 什么是菱形虚拟继承、解决数据冗余和二义性的?
  3. 继承和组合的区别?什么时候用继承?什么时候用组合?

回答:

  1. 菱形继承是指在面向对象编程中的一种继承关系,其中一个列同时继承两个不同的类,而这两个类又都继承自同一个父类,形成了一个菱形的继承结构。
  2. 数据冗余和二义性问题,并且还有代码维护困难、不稳定性
  3. 菱形虚拟继承是C++中用来解决菱形继承问题的一种机制,派生类通过虚拟继承来自共同基类,被虚继承的基类只会在继承层次结构中存在一份实例,而不会出现多次复制
  4. 继承和组合是面向对象编程中两种不同的关系模式,继承是一种"is-a"关系,表示的是类之间的一种分类关系,即子类是父类的一种特殊形式,而组合是一种"has-a"关系,表示一个类包含另一个类作为基一部分,但是它们之间不具有层次关系。
  5. 当存在明确的"是一个is-a"关系,且子类需要继承父类的行为和属性时,使用继承,当需要在不同的类之间建立层次关系时,使用继承。
  6. 当对象之间存在包含关系,一个对象包含另一个对象作为其一部分时,使用组合。当对象之间的关系更加动态,或者不需要建立明确的层次关系时,使用组合。

六、继承是子类拷贝一份父类数据吗?(重点)

在继承中,子类和父类之间是一种逻辑关系子类没有真正地拥有一份父类代码的副本,而是通过继承机制在需要时访问父类的功能。继承的这种特性使得子类不仅可以直接使用父类已有的功能,还可以通过重写(override)或扩展(extend)来修改或增加功能,而这一切都是通过共享父类的代码实现的,而不是通过复制代码实现的。

继承的实现是在运行时,通过类之间的关系来实现的。子类通过引用父类的成员,而不是复制父类的代码。因此,子类并没有物理上拥有父类的代码,而是通过继承关系“共享”父类的代码。

因此,继承不是拷贝,它是一种更高级的代码复用机制,通过类的层次结构来共享行为,而不是简单地复制粘贴代码。这样不仅可以减少代码重复,还可以通过多态和重写机制来增强代码的灵活性和可扩展性。


以上就是本篇文章的所有内容,在此感谢大家的观看!这里是店小二呀C++笔记,希望对你在学习C++语言旅途中有所帮助!

相关文章
|
2天前
|
编译器 程序员 定位技术
C++ 20新特性之Concepts
在C++ 20之前,我们在编写泛型代码时,模板参数的约束往往通过复杂的SFINAE(Substitution Failure Is Not An Error)策略或繁琐的Traits类来实现。这不仅难以阅读,也非常容易出错,导致很多程序员在提及泛型编程时,总是心有余悸、脊背发凉。 在没有引入Concepts之前,我们只能依靠经验和技巧来解读编译器给出的错误信息,很容易陷入“类型迷路”。这就好比在没有GPS导航的年代,我们依靠复杂的地图和模糊的方向指示去一个陌生的地点,很容易迷路。而Concepts的引入,就像是给C++的模板系统安装了一个GPS导航仪
77 59
|
5天前
|
安全 编译器 PHP
PHP 7新特性深度解析与实践
【10月更文挑战第7天】在这篇文章中,我们将探索PHP 7带来的新特性和改进,以及如何利用这些新工具来提升你的代码效率。从性能优化到语法简化,再到错误处理的改进,本文将带你深入了解PHP 7的核心变化,并通过实际代码示例展示如何将这些新特性应用到日常开发中。无论你是PHP新手还是资深开发者,这篇文章都将为你提供有价值的见解和技巧。
17 6
|
6天前
|
Web App开发 前端开发 测试技术
Selenium 4新特性解析:关联定位器及其他创新功能
【10月更文挑战第6天】Selenium 是一个强大的自动化测试工具,广泛用于Web应用程序的测试。随着Selenium 4的发布,它引入了许多新特性和改进,使得编写和维护自动化脚本变得更加容易。本文将深入探讨Selenium 4的一些关键新特性,特别是关联定位器(Relative Locators),以及其他一些重要的创新功能。
36 2
|
7天前
|
自动驾驶 安全 物联网
|
14天前
|
存储 编译器 C++
【C++】面向对象编程的三大特性:深入解析多态机制(三)
【C++】面向对象编程的三大特性:深入解析多态机制
|
14天前
|
存储 编译器 C++
【C++】面向对象编程的三大特性:深入解析多态机制(二)
【C++】面向对象编程的三大特性:深入解析多态机制
|
14天前
|
编译器 C++
【C++】面向对象编程的三大特性:深入解析多态机制(一)
【C++】面向对象编程的三大特性:深入解析多态机制
|
14天前
|
存储 安全 编译器
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值(一)
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值
|
14天前
|
编译器 C++
【C++】面向对象编程的三大特性:深入解析继承机制(二)
【C++】面向对象编程的三大特性:深入解析继承机制
|
14天前
|
安全 程序员 编译器
【C++】面向对象编程的三大特性:深入解析继承机制(一)
【C++】面向对象编程的三大特性:深入解析继承机制

推荐镜像

更多