1继承概念与定义
1.1继承的概念
继承是实现面向过程代码复用的一种手段,再原有的类特性上进行了扩展,产生了新类,派生类。继承呈现面向对象程序设计的层次结构。继承是类设计的层次的复用。
1.2继承的定义
定义格式:
Student 是派生类,Teacher是父类也叫基类,public是继承方式。
1.3访问限定符与继承关系
从上图中可以看出:
- 基类的private成员无论以何种方式继承到子类都是不可见的。可不见的意思是private成员继承到了派生类中,但是派生类外成员不可访问,派生类内也不可以进行访问。
- private与protected在类中无明显区别都是保护类内成员不被类外访问。但是在继承中就具有区别。基类的protected成员继承到派生类中是可以被类内成员进行访问的,而private却不行。
- 继承的方式有点类似与权限的缩小。权限大小比较:public>protected>private。protected以public继承的时候,在派生类中是protected,以private继承的时候是private。
继承的时候是选权限小的继承到派生类中的。(min(成员在类内的访问方式,继承方式))。
- 使用关键词class如果不写继承默认的继承方式是private。strcut则是public。
- 一般继承都是用public继承,不使用另外两种继承方式。另外两种继承方式只能在派生类内访问,实用性不高.
2基类和派生类对象赋值转换
- 派生类对象可以赋值给基类的对象/基类的引用/基类的指针,这种方法叫切片或者切割。
- 基类对象不能赋值给派生类
派生类赋值给基类就情况类似于派生类中包括了基类,派生类把自己包括的基类赋值给基类。基类不能赋值给派生类是因为基类不囊括派生类。
3继承中的作用域
- 在继承体系中基类和派生类都有不同的作用域
- 在派生类中定义了基类同名成员,派生类会对基类中同名成员进行屏蔽,这种情况叫隐藏。在派生成员函数要调用要采取 基类::基类成员.
- 成员函数相同,就会构成隐藏,而不会构成函数重载。因为函数重载是争对相同作用域。
- 防止这种屏蔽的情况,最好不要进行重定义。
4.派生类的默认成员函数
默认成员函数就是不写编译器自动生成。默认成员函数有六个。
- 派生类的构造函数会在初始化列表自动调用基类的默认构造函数,如果基类没有默认构造,则需要我们自己在派生类初始化列表调用。
- 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的初始化。
- 派生类的operator=必须调用基类的operator=完成基类的赋值
- 不能在派生类的析构函数中调用基类的析构函数。基类的析构函数编译器会自动调用。因为基类先实例化,派生类后实例,在派生类调用基类,会导致基类先析构,派生类后析构。栈是先进后出,应该先派生类析构,在基类析构。这也会导致派生类调用基类的指针,把基类先析构会让派生类出现野指针问题。
- 派生类构造会先调用基类在调用派生类。
- 派生类析构会先析构派生类在析构基类。
- 由于多态原因,一个类的析构函数会被处理成destructor。所以基类和派生类会构成隐藏,想要在派生类中调用基类析构需要加上 基类::。
先后顺序:先完成父类的构造函数
在完成子类的构造函数
进行派生类的行为
先完成子类的析构函数
最后完成父类析构函数
- 继承与友元
友元不能继承,基类友元不能访问子类的私有与保护。
- 继承与静态成员
静态成员一个类公用一个。基类定义了一个static成员,整个类只有一个这样的static成员。无论有多少子类,都只有一个这样的static实列他们共用一个空间。static实列化后所有的派生类都能访问,且static的地址都是一样的。
- 菱形继承及菱形虚拟继承
单继承:一个子类只有一个直接父类,这种继承方式叫单继承。
多继承:一个子类有两个或以上直接父类,这种继承方式叫多继承。
菱形继承:是多继承的一种特殊情况。
菱形继承具有的两个问题
- 菱形继承具有数据冗余性。
- 菱形继承具有二义性。
从上图中可以看出,Scretary这个类中Person1有两份,分别从Student与Teacher中继承。
为了解决菱形继承的二义性与数据冗余,就有了菱形虚拟继承。在腰部,也就是student与Person继承的时候加上关键词virtual,就可以解除问题。虚拟继承不要用在别的地方。
菱形虚拟继承:
将Secretary中Student具有Person1的变量与Teacher中具有Person1的变量存放到了相同的地址中。
菱形虚拟继承解决二义性与数据冗余的原理
给出如下代码:
通过这图片,D类中将A放到了最后,B类和C类增加了两个指针,这两个指针中存放了虚基表的地址。说明这两个指针指向了一张虚基表,虚基表中存放了B、C类距离A的偏移量,通过这个偏移量就可以找到A类。
- 当多个类从同一个虚基类中派生时,它们共享同一个虚基子对象。这个虚基类子对象只被派生一次,所以会消除数据的冗余与二义性。
- 虚拟继承中的 虚基类子对象的构造顺序 比较特殊,需要按照 “最远派生类优先”(Most Derived Class First)的顺序进行构造,也就是从最后一个派生类开始构造虚基类子对象,依次向上构造。
(建立一个对象时,如果对象中含有从虚基类继承来的成员,虚基类的成员由最远派生类的构造函数通过调用虚基类的构造函数进行初始化。
并且只有最远派生类的构造函数会调用虚基类的构造函数,该派生类的其他基类对虚基类构造函数的调用会自动忽略。)
- 同一种派生类的虚基表只有一张,它们共享同一张虚基表。
- 虚拟继承有一些性能上的开销,每次访问都需要间接寻址。虚拟继承还会导致代码的可读性变低,因为虚拟继承的存在并不直观。因此,使用虚拟继承需要平衡优缺点。
另外:虚基表是在编译的时候就生成的好的。
继承的总结与反思
- 一般不要设计多继承,一定不要设计出菱形继承。
- 多继承可以说是C++的缺陷。
- 继承与组合 public继承是 is-a的关系。每个派生类都是一个基类对象 组合是has-a的关系。假设B组合A,则每个B都有一个A对象。
- 优先使用组合而不是继承。
- 继承也可以说是白箱复用,基类的内部细节对子类可见,它一定程度破坏基类的封装。基类的改变会对子类具有很大的影响。派生类与基类的关系亲密度高,耦合性高。
- 对象组合是类继承之外的另一种复用手段。新的更复杂的功能可以通过组合来实现。对象组合要求组合对象有良好定义的接口,这种称为黑箱复用。因为对象内部的细节不可见,对象只以黑箱的方式出现。组合之间没有很强的依赖关系,耦合度低。
- 我们要求高内聚,低耦合。实际要多用组合,代码维护好。不过继承也有用武之地,多态就需要用到继承,而有些特地的地方也需要继承。类之间的关系可以用继承,也可以用继承,那就用组合。
is-a 人-学生 植物-花 颜色-红色
has-a 轮胎-车 眼睛-人