C++——面向对象三大特性——继承
什么是继承?
现实中,不同的事物之间往往不是独立存在的,很多事物之间都有着复杂的联系,继承就是众多联系中的一种。
课本概念:面向对象的程序设计中提供了类的继承机制,允许程序员在保持原有类特性的基础上,进行更具体,更详细的类定义。继承机制是面向对象程序设计使代码可以复用的最重要的手段,以前我们接触的复用都是函数复用,继承是类设计层次的复用。。
理解继承:继承就是从先辈那里得到属性和行为特征;比如一个人从父母处继承了基因,就拥有了父母已经有的特征。面向对象的继承派生机制,最主要的目的就在于实现代码的重用和扩充。
需要注意的几点:
1、父类的构造函数和析构函数是继承不了的——理解:除了先辈的生死以外的东西我们是可以继承的。
2、继承方式规定了如何访问从基类继承的成员,公有继承中——那些成员能访问,那些不能直接访问?
在对象角度——只能访问公有成员
在子类角度——只能访问父类的公有和保护成员不能访问私有成员
在类自己内部——全部可以访问
3、构造析构的先后顺序?
子类生成的过程:吸收父类成员,改造父类成员,添加新成员
先构造父类再构造子类 ,先析构子类再析构父类 ;从上到下;从左到右
如果子类成员中有自定义类型的对象,先构造父类、再构造自定义类型、最后构造子类自己
如果父类构造函数有参:则需要通过参数列表初始化,初始化列表不影响构造顺序,只与声明顺序有关
4、特别注意继承实际上是子类继承了父类的除了构造析构以外的所有属性和行为,包括私有成员,只不过在子类里将父类的私有成员隐藏了。可以证明:
class Base
{
private:
int a;
};
class B {};
class D:public Base{};
int main()
{
cout<<sizeof(Base)<<endl;//输出为4,Base类的大小
cout << sizeof(B) << endl;//没有继承,输出为一个空类大小 1
cout<<sizeof(D)<<endl;//空类继承Base输出为4,即继承了父类的私有属性,但在子类中隐藏了。
return 0;
}
5、友元关系不能被继承——即先辈的朋友也许不是自己的朋友,也有可能是敌人呀。
6、static成员:
基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例 。
赋值语句:
A = B 类型不同 不允许赋值
赋值兼容规则(类型兼容规则):——向上转换,向下转换(一般不允许)
1、子类对象可以给父类对象赋值(子类对象可以隐式转换为父类对象)
2、子类对象的地址可以给父类指针赋值(子类的指针可以隐式转换为父类的指针)
3、子类对象可以初始化父类的引用(子类对象可以初始化父类的引用)
//父类对象不能赋值给子类对象
//父类的指针可以通过强制类型转换赋值给子类的指针
隐藏——同名隐藏规则
:
如果子类中声明了与基类成员函数同名的新函数,即使函数的参数列表不同,基类继承的同名函数的所有重载形式也都会被隐藏。如果想要访问被隐藏的成员,就需要使用作用域分辨符和类名来限定(父类与子类之间不属于函数重载,只有在相同定义域中定义的函数才可以重载)
菱形继承——虚继承——虚基类
菱形继承:
菱形继承的问题:从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。在Derived的对象中Base0成员会有两份。在我们用Derived对象调用fun0()函数时会出现二义性问题,因为fun0()函数有两个来源——Base1类和Base2类
虚拟继承
虚拟继承如何解决菱形继承中的数据冗余和二义性问题?
在我们加上virtual关键字后内存模型变化:
虚拟继承解决数据冗余和二义性的原理——借助内存窗口观察对象成员的模型。
下图是菱形虚拟继承的内存对象成员模型:这里可以分析出Derived对象中将Base放到的了对象组成的最下面,这个Base同时属于Base1和Base2,那么Base1和Base2如何去找到公共的Base呢?这里是通过了Base1和Base2的两个指针,指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到下面的Base。
虚基类
在菱形继承的情况下,在最下面的子类的直接基类中从上一级共同基类继承来的成员就拥有相同的名称,在派生类的对象中,这些同名数据成员在内存中同时拥有多个副本,同一个函数名会有多个映射。造成了数据冗余和二义性的问题,为了解决这个问题,可以将共同基类设置为虚基类,这时从不同路径继承过来的同名数据成员在内存中就只有一个副本,同一个函数名也只有一个映射。这样就解决了同名成员的唯一标识问题。
在多继承的情况下,虚基类关键字的作用范围和继承方式关键字相同,只对跟在他身后的基类起作用。声明了虚基类后,虚基类的成员在进一步派生过程中和派生类一起维护同一份内存数据副本。