目录

  • 一、派生类和基类的构造析构关系
    • 1、派生类并不继承基类的构造和析构函数,只继承成员变量和普通成员方法
    • 2、派生类的构造函数一定会调用基类的构造函数,析构也一样
    • 3、为什么派生类的构造(析构)必须调用基类的某个构造(析构)
    • 4、其他几个细节
    • 5、派生类做的三件事
  • 二、派生类和基类的同名成员问题
    • 1、派生类中再实现一个基类中的方法会怎样
    • 2、派生类中如何访问被隐藏的基类方法
    • 3、注意和总结
  • 三、子类和父类的类型兼容规则
    • 1、何为类型兼容规则
    • 2、类型兼容规则的常见情况及演示
    • 3、总结
  • 四、继承的优势与不良继承
    • 1、为什么会有继承
    • 2、何为不良继承
    • 3、如何解决不良继承
  • 五、组合介绍以及与继承对比
    • 1、什么是组合
    • 2、继承与组合的特点对比
  • 六、多继承及其二义性问题
    • 1、多继承
    • 2、多继承的二义性问题1
    • 3、多继承的二义性问题2
    • 4、总结
  • 七、虚继承解决菱形继承的二义性问题
    • 1、虚继承怎么用
    • 2、虚继承的实现原理

一、派生类和基类的构造析构关系

1、派生类并不继承基类的构造和析构函数,只继承成员变量和普通成员方法

(1)不继承,意思是派生类中确实没有,不包含基类的构造和析构函数
(2)派生类自己有自己的构造和析构,规则和之前讲过的完全一样
(3)研究构造和析构函数时,一定要注意默认规则

2、派生类的构造函数一定会调用基类的构造函数,析构也一样

(1)代码验证:在基类和派生类中都显式提供“默认构造”并添加打印信息,通过执行结果来验证

  通过代码执行结果看到的现象总结:派生类的构造函数执行之前,会先调用基类的构造函数,然后再调用自己的构造函数。而在派生类的析构函数之后,会先执行自己的析构函数,再执行基类的析构函数。

(2)代码验证:派生类的任意构造函数,可以显式指定调用基类的任意一个构造函数,通过参数匹配的方式(类似于函数重载)

3、为什么派生类的构造(析构)必须调用基类的某个构造(析构)

(1)牢记构造函数的2大作用:初始化成员,分配动态内存

(2)派生类和基类各自有各自的构造函数和析构函数,所以是各自管理各自的成员初始化,各自分配和释放各自所需的动态内存

(3)继承的语言特性,允许派生类调用基类的构造和析构函数,以管理派生类从基类继承而来的那些成员。

(4)明确:派生类的构造和析构处理的永远是派生类自己的对象,只是派生类对象模板中有一部分是从基类继承而来的而已。

4、其他几个细节

(1)派生类构造函数可以直接全部写在派生类声明的class中,也可以只在clas中声明时只写派生类构造函数名和自己的参数列表,不写继承基类的构造函数名和参数列表,而在派生类的cpp文件中再写满整个继承列表,这就是语法要求

(2)派生类析构函数则不用显式调用,直接写即可直接调用基类析构函数。猜测是因为参数列表问题。

(3)构造函数的调用顺序是先基类再派生类,而析构函数是先派生类再基类,遵循栈规则。

(4)派生类的构造函数可以在调用基类构造函数同时,用逗号间隔同时调用初始化式来初始化派生类自己的成员

5、派生类做的三件事

(1)吸收基类成员:除过构造和析构函数以外的所有成员全部吸收进入派生类中

(2)更改继承的成员。1是更改访问控制权限(根据继承类型还有成员在基类中的访问类型决定) 2是同名覆盖(派生类中同名成员覆盖掉基类中)

(3)添加派生类独有的成员。

二、派生类和基类的同名成员问题

1、派生类中再实现一个基类中的方法会怎样

(1)代码实验:派生类和基类中各自实现一个内容不同但函数原型完全相同的方法,会怎么样?

(2)结论:基类对象调用的是基类的方法,派生类对象调用执行的是派生类中重新提供的方法

(3)这种派生类中同名同参方法替代掉基类方法的现象,叫做:重定义(redefining),也有人叫做隐藏

(4)隐藏特性生效时派生类中实际同时存在2份同名同参(但在不同类域名中)的方法,同时都存在,只是一个隐藏了 另一个

2、派生类中如何访问被隐藏的基类方法

(1)派生类对象直接调用时,隐藏规则生效,直接调用的肯定是派生类中重新实现的那一个

(2)将派生类强制类型转换成基类的类型,再去调用则这时编译器认为是基类在调用,则调用的是基类那一个,隐藏规则被绕过了

(3)在派生类内部,使用父类::方法()的方式,可以强制绕过隐藏规则,调用父类实现的那一个

3、注意和总结

(1)其实不止成员方法,成员变量也遵循隐藏规则。

(2)隐藏规则本质上是大小作用域内同名变量的认领规则问题,实际上2个同名成员都存在当前派生类的对象内存中的

(3)隐藏(重定义,redefining),与重载(overload)、重写(override,又叫覆盖),这三个概念一定要区分清楚。

三、子类和父类的类型兼容规则

1、何为类型兼容规则

(1)C和C++都是强类型语言,任何变量和对象,指针,引用等都有类型,编译器根据类型来确定很多事

(2)派生类是基类的超集,基类有的派生类都有(但构造函数和析构函数例外),派生类有的基类不一定有,所以这2个类型间有关联

(3)派生类对象可以cast(类型转换相关的关键字)后当作基类对象,而基类对象不能放大成派生类对象,否则就可能会出错

(4)考虑到指针和引用与对象指向后,派生类和基类对象的访问规则就是所谓类型兼容规则。

2、类型兼容规则的常见情况及演示

(1)子类对象可以当作父类对象使用,也就是说子类对象可以无条件隐式类型转换为一个父类对象,但是将父类强转成子类是隐藏危险的。

(2)子类对象可以直接初始化或直接赋值给父类对象

(3)父类指针可以直接指向子类对象

(4)父类引用可以直接引用子类对象

下图程序中:person是父类,man是子类

3、总结

(1)派生类对象可以作为基类的对象使用,但是只能使用从基类继承的成员。

(2)类型兼容规则是多态性的重要基础之一。

(3)总结:子类就是特殊的父类 (base *p = &child;)

四、继承的优势与不良继承

1、为什么会有继承

(1)本质上为了代码复用

(2)继承方式很适合用来构建复杂框架体系

(3)用继承来设计类进而构建各层级对象,符合现实中的需要。举例:描述人的种群

2、何为不良继承

(1)鸵鸟不是鸟问题。因为鸵鸟从鸟继承了fly方法但是鸵鸟不会飞

(2)圆不是椭圆问题。因为圆从椭圆继承了长短轴属性然而圆没有长短轴属性

(3)不良继承是天然的,是现实世界和编程的继承特性之间的不完美契合

3、如何解决不良继承

(1)修改继承关系设计。既然圆继承椭圆是一种不良类设计就应该杜绝。去掉继承关系,两个类可以继承自同一个共同的父类,不过该类不能执行不对称的setSize计算,然后在圆和椭圆这2个子类中分别再设计以区分

(2)所有不良继承都可以归结为“圆不是椭圆”这一著名具有代表性的问题上。在不良继承中,基类总会有一些额外能力,而派生类却无法满足它。这些额外的能力通常表现为一个或多个成员函数提供的功能。要解决这一问题,要么使基类弱化,要么消除继承关系,需要根据具体情形来选择。

五、组合介绍以及与继承对比

1、什么是组合

(1)composition,组合,就是在一个class内使用其他多个class的对象作为成员

(2)用class tree做案例讲解

(3)组合也是一种代码复用方法,本质也是结构体包含

class A
{}
class B
{}
class C
{}//用组合的方式实现
class D
{A a;B b;C c;short s1;
}
//用继承的方式实现
class C:public A, public B, public C
{short s1;
}

2、继承与组合的特点对比

(1)继承是a kind of(is a)关系,具有传递性,不具有对称性。

(2)组合是a part of(has a)的关系,

(3)继承是白盒复用。因为类继承允许我们根据自己的实现来覆盖重写父类的实现细节,父类的实现对于子类是可见的。

(4)继承的白盒复用特点,一定程度上破坏了类的封装特性,因为这会将父类的实现细节暴露给子类

(5)组合属于黑盒复用。被包含对象的内部细节对外是不可见的,所以它的封装性相对较好,实现上相互依赖比较小

(6)组合中被包含类会随着包含类创建而创建,消亡而消亡。组合属于黑盒复用,并且可以通过获取其它具有相同类型的对象引用或指针,在运行期间动态的定义组合(例如上边提到的子类对象可作为一个父类对象使用)。而缺点就是致使系统中的对象过多。

(7)OO设计原则是优先组合,而后继承

六、多继承及其二义性问题

1、多继承

(1)多继承就是一个子类有多个父类

class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,…
{<派生类类体>
};

(2)多继承演示

#include <iostream>using namespace std;// 基类 Shape
class Shape
{public:void setWidth(int w){width = w;}void setHeight(int h){height = h;}protected:int width;int height;
};// 基类 PaintCost
class PaintCost
{public:int getCost(int area){return area * 70;}
};// 派生类
class Rectangle: public Shape, public PaintCost
{public:int getArea(){ return (width * height); }
};int main(void)
{Rectangle Rect;int area;Rect.setWidth(5);Rect.setHeight(7);area = Rect.getArea();// 输出对象的面积cout << "Total area: " << Rect.getArea() << endl;// 输出总花费cout << "Total paint cost: $" << Rect.getCost(area) << endl;return 0;
}

(3)多继承和单继承的原理,效果并无明显区别

(4)多继承会导致二义性问题

2、多继承的二义性问题1

(1)场景:C多继承自A和B,则C中调用A和B的同名成员时会有二义性

(2)原因:C从A和B各自继承了一个同名(不同namespace域)成员,所以用C的对象来调用时编译器无法确定我们想调用的是哪一个

(3)解决办法1:避免出现,让A和B的public成员命名不要重复冲突。但这个有时不可控。

(4)解决办法2:编码时明确指定要调用哪一个,用c.A::func()明确指定调用的是class A的func而不是class B的

(5)解决办法3:在C中重定义func,则调用时会调用C中的func,A和B中的都被隐藏了

(6)总结:能解决,但是都没有很好的解决。

3、多继承的二义性问题2

(1)场景:菱形继承问题。即A为祖类,B1:A, B2:A, C:B1,B2,此时用C的对象调用A中的某个方法时会有二义性

class A{......};
class B1: public A{......};
class B2: public A{......};
class C: public B1, public B2{.....};

(2)分析:c.func()有二义性,c.A::func()也有二义性,但是c.B1::func()和c.B2::func()却没有二义性

(3)解决办法:和问题1中的一样,但是问题2更隐蔽,也更难以避免

4、总结

(1)二义性就是歧义,好的情况表现为编译错误,不好的情况表现为运行时错误,最惨的情况表现为运行时莫名其妙

(2)随着系统的变大和变复杂,难免出现二义性,这不是程序员用不用心的问题,是系统自身带来的

(3)解决二义性问题不能靠程序员个人的细心和调试能力,而要靠机制,也就是编程语言的更高级语法特性

(4)虚函数、虚继承、纯虚函数、抽象类、override(重写,覆盖)、多态等概念就是干这些事的

(5)感慨:欲戴王冠必承其重,要揽瓷器活就得有金刚钻,C++学得越清楚就越能想象将来用C++去解决的都是些什么层次的问题

七、虚继承解决菱形继承的二义性问题

1、虚继承怎么用

(1)场景:菱形继承导致二义性问题,本质上是在孙子类C中有B1和B2中包含的2份A对象,所以有了二义性。

(2)虚继承解决方案:让A和B虚继承D,C再正常多继承A和B即可,虚继承会使得C继承A、B时只选择AB其中的一份D,不会重复,类似于头文件不重复包含

#include <iostream>using namespace std;//基类
class D
{public:D(){cout<<"D()"<<endl;}~D(){cout<<"~D()"<<endl;}
protected:int d;
};class B:virtual public D
{public:B(){cout<<"B()"<<endl;}~B(){cout<<"~B()"<<endl;}
protected:int b;
};class A:virtual public D
{public:A(){cout<<"A()"<<endl;}~A(){cout<<"~A()"<<endl;}
protected:int a;
};class C:public B, public A
{public:C(){cout<<"C()"<<endl;}~C(){cout<<"~C()"<<endl;}
protected:int c;
};int main()
{cout << "Hello World!" << endl;C c;   //D, B, A ,Ccout<<sizeof(c)<<endl;return 0;
}

(3)虚继承就这么简单,就是为了解决菱形继承的二义性问题而生,和虚函数(为了实现多态特性)并没有直接关系

2、虚继承的实现原理

(1)虚继承的原理是:虚基类表指针vbptr 和 虚基类表virtual table

  vbptr指的是虚基类表指针(virtual base table pointer),该指针指向了一个虚基类表(virtual table),虚表中记录了虚基类与本类的偏移地址;通过偏移地址,这样就找到了虚基类成员,而虚继承也不用像普通多继承那样维持着公共基类(虚基类)的两份同样的拷贝,节省了存储空间。

  菱形继承中,两个虚基类表指针vbptr通过指针偏移量来找寻虚基类表(避免了二义性),但通过两个虚基类表最终找到的是同一个基类。

详解参考:https://blog.csdn.net/xiejingfa/article/details/48028491

注:本文章参考了《朱老师物联网大讲堂笔记》,并结合了自己的实际开发经历以及网上他人的技术文章,综合整理得到。如有侵权,联系删除!水平有限,欢迎各位在评论区交流。

C++继承和多态特性——继承详解(2)相关推荐

  1. 什么是多态,Python多态及用法详解

    什么是多态,Python多态及用法详解 在面向对象程序设计中,除了封装和继承特性外,多态也是一个非常重要的特性,本节就带领大家详细了解什么是多态. 我们都知道,Python 是弱类型语言,其最明显的特 ...

  2. java实验报告4继承与多态_Java继承与多态实验报告

    西 西 安 安 邮 邮 电 大 学 (计算机学院) 课内实验报告 实验名称: : 态 继承与多态 ﻩ ﻩ 专业名称: 计算机科学与技术 班 班 级: 计科 1405 班 学生姓名: 高宏伟 学 学 号 ...

  3. python装饰器性能_python装饰器的特性原理详解

    这篇文章主要介绍了python装饰器的特性原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 今天发现了装饰器的另一种用法,下面就先上代码: d ...

  4. 《HTTP/2基础教程》协议、特性、详解

    文章目录 <HTTP/2基础教程>协议.特性.详解 前言 第一章 HTTP进化史 第二章 HTTP/2 快速入门 第三章 Web优化"黑魔法"的动机与方式 HTTP/1 ...

  5. unity 继承会调用start吗_Unity 继承MonoBehaviour脚本 执行顺序 详解

    先看结果 Awake ->OnEnable-> Start ->-> FixedUpdate-> Update  -> LateUpdate ->OnGUI ...

  6. java 方法继承方法_java的继承原理与实现方法详解

    本文实例讲述了java的继承原理与实现方法.分享给大家供大家参考,具体如下: 继承 1.java中是单继承的.每个子类只有一个父类. 语法:子类 extends 父类 2.在java中,即使没有声明父 ...

  7. 【C++从入门到入土】第五篇:继承(爆肝画图详解)

    系列文章目录 [C++从入门到入土]第一篇:从C到C++. [C++从入门到入土]第二篇:类和对象基础. [C++从入门到入土]第三篇:类和对象提高. [C++从入门到入土]第四篇:运算符重载. 文章 ...

  8. C++虚继承和虚基类详解(二)

    虚继承(Virtual Inheritance) 为了解决多继承时的命名冲突和冗余数据问题,C++ 提出了虚继承,使得在派生类中只保留一份间接基类的成员. 在继承方式前面加上 virtual 关键字就 ...

  9. C++虚继承和虚基类详解(一)

    多继承(Multiple Inheritance)是指从多个直接基类中产生派生类的能力,多继承的派生类继承了所有父类的成员.尽管概念上非常简单,但是多个基类的相互交织可能会带来错综复杂的设计问题,命名 ...

最新文章

  1. STL vector list deque区别与实现
  2. java虚拟机资源根目录_Java路径问题最终解决方案—可定位所有资源的相对路径寻址 - java - CSDN技术......
  3. 【运筹学】线性规划 单纯形法 案例二 ( 案例解析 | 标准形转化 | 查找初始基可行解 | 最优解判定 | 查找入基变量与出基变量 | 第一次迭代 )
  4. Java开发与技术挑战——关于技术的技术思考
  5. 有没有比python更简单的语言排名_5月语言排行榜:R跌出前二十 Python紧咬C++
  6. mysql 安装 权限_MySQL的安装、使用及权限管理
  7. 内核引导参数IOMMU与INTEL_IOMMU有何不同?
  8. Office文档转pdf和图片之NodeJS
  9. mysql v$session_关于V$SESSION视图
  10. 十四步实现拥有强大AI的五子棋游
  11. 一、求100以内的素数
  12. comsol 计算机配置,[转]我需要一台什么样的电脑运行我的COMSOL Multiphysics - 仿真模拟 - 小木虫 - 学术 科研 互动社区...
  13. 万豪集团精细化在华发展策略;完美日记母公司将收购护肤品牌Eve Lom;赛生药业港交所主板挂牌上市 | 美通企业日报...
  14. 电脑出现错误信息的故障排除
  15. 使用pytorch获取bert词向量
  16. 大数据Flink安装部署
  17. 四足机器人技术及进展
  18. 5种数据分析常用的思维方法!
  19. 2021-2027全球及中国音圈马达驱动行业研究及十四五规划分析报告
  20. 酒精测试仪方案了解-PCBA电子设计

热门文章

  1. 关于path变量配置出现LRE的问题
  2. Linux高级指令(二)
  3. Spring--循环依赖导致启动失败
  4. 二台苹果电脑如何用网线直接连接
  5. 前端日历组价(包含新建日程,查看日程/切换年月查看日程)vue组件日历复制即用,日程可能要看这调一调。
  6. 倾斜校正-表格图像的校正
  7. 用了一个很蠢的方法定位服务不可用原因
  8. Schedule 定时任务cron表达式
  9. Python中封装、继承、多态的练习题
  10. linux 常用端口