目录
c++菱形继承 和虚拟菱形继承 以及这个 虚函数 和虚函数表:::
继承
1继承的概念及定义::::
继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用
继承分为 公有继承私有继承 和保护继承 一般来说公有继承用的是最多的
继承关系和访问限定符
总结::
(1) 继承 如果是基类private成员在派生类中无论以什么方式继承都是不可见的。
(2) 如果基类成员不想被类外访问 但是通过继承可以被访问 则可以是protected的方式
(3)使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式
基类和派生类对象赋值转换
(1)派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。
(2)基类对象不能赋值给派生类对象
(3)基类的指针可以通过强制类型转换赋值给派生类的指针。但是必须是基类的指针是指向派生类对象时才是安全的。
继承中的作用域
1. 在继承体系中基类和派生类都有独立的作用域。
2. 子类和父类中有同名成
员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)
3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
4. 注意在实际中在继承体系里面最好不要定义同名的成员。
派生类的默认成员函数
1.派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函
数,则必须在派生类构造函数的初始化列表阶段显示调用。
2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
3. 派生类的operator=必须要调用基类的operator=完成基类的复制。
4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类
对象先清理派生类成员再清理基类成员的顺序。
5. 派生类对象初始化先调用基类构造再调派生类构造。
6. 派生类对象析构清理先调用派生类析构再调基类的析构1
继承与友元 与静态成员
友元关系不能继承 也就是说 基类友元不能访问子类私有和保护成员。
基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例 。
用派生类建立一个对象时 执行构造函数的顺序是 先调用基类的构造函数 再执行派生类构造函数本身 释放对像时候 执行析构函数的顺序是 先调用子类的析构函数 再调用基类的构造函数。
复杂的菱形继承及菱形虚拟继承
单继承:一个子类只有一个直接父类时称这个继承关系为单继承
多继承:一个子类有两个或以上继承父类时称这个继承关系为多继承
菱形继承:菱形继承是多继承的一种特殊情况。
菱形继承的问题:从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。虚拟继承可以解决菱形继承的二义性和数据冗余的问题。
笔试面试题::
1. 什么是菱形继承?菱形继承的问题是什么?
菱形继承:菱形继承是多继承的一种特殊情况。 菱形继承的问题:,可以看出菱形继承有数据冗余和二义性的问题
2. 什么是菱形虚拟继承?如何解决数据冗余和二义性的
专门为解决菱形继承带来的数据冗余和二义性问题而给出的一种继承方式。 是靠指向虚基表的虚基表指针,而虚基表存的偏移量来实现的。
3. 继承和组合的区别?什么时候用继承?什么时候用组合?
哪些成员函数不能被继承???
多态
多态的概念
:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态
构成多态还有两个条件:
1. 必须通过基类的指针或者引用调用虚函数
2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写
虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。
虚函数重写的两个例外
1. 协变(基类与派生类虚函数返回值类型不同)
派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。
2. 析构函数的重写(基类与派生类析构函数的名字不同
如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同,看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor
C++对函数重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数名字母次序
写反而无法构成重载,而这种错误在编译期间是不会报出的,只有在程序运行时没有得到预期结果才来debug会得不偿失,因此:C++11提供了override和final两个关键字,可以帮助用户检测是否重写。
1. final:修饰虚函数,表示该虚函数不能再被重写
2.override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错
重载、覆盖(重写)、隐藏(重定义)的对比:::::
抽象类:
在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承
总结一下派生类的虚表生成:a.先将基类中的虚表内容拷贝一份到派生类虚表中 b.如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数 c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后
虚函数存在哪的?虚表存在哪的?
都是代码段
这里重新说明一下
c++菱形继承 和虚拟菱形继承 以及这个 虚函数 和虚函数表:::
菱形继承 他的缺点在于数据的冗余 以及二义性的问题, 我们为了解决数据冗余以及二义性的问题引入了菱形虚拟继承 菱形虚拟继承就是在中间继承上加入了 virtual 那么加入这个目的是什么呢? 是为了引入一个虚基表 和虚基表指针的概念 就是这里B 里面b 他不是直接把跟菱形继承一样 在C1 C2里面各一份然后继承给D 是产生一个虚基表指针这两个指针都指向一张表,表里存放的是该指针到_b的偏移量,这两个指针叫虚基表指针,这张表叫偏移量表格,也叫虚基表。通过偏移量就可以查到b 这就是虚拟菱形继承 中最重要的虚基表和虚基表指针 虚函数就是virtual在函数钱加入这个 虚函数表就是继承后产生一个指针 指针指向数组 数组里面每个元素都是对应函数的地址 虚函数 和虚函数表都是在代码段上面的,
动态绑定与静态绑定
1. 静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态,比如:函数重载
2. 动态绑定又称后期绑定(晚绑定),是在程序运
行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态