目录
一,继承是什么?
我们说面向对象的语言有三大特性:1.封装,2,继承,3,多态。这里的继承便是面向对象的语言的三大特性之一。继承也像我们在日常生活中所理解的那样,继承便是儿子继承父亲的遗产然后自己用。
二,继承的权限
在C++语言中,我们有三大权限:1.private 2.protected 3.public。相信大家都知道这三大权限。继承也有三大权限,也是这三个权限。所以子类继承父类的对象的权限组合就会有九种。这九种组合如下:
虽然说这里有九种组合,但是我们的子类对象里继承父类的成员对象的权限只会有四种情况:
**1.**当父类中的权限是private时,子类中只会继承这个数据但是在子类中的这个成员或方法是不可见的。
**2.**当子类以private的权限来继承父类的成员或者方法时在子类中的该成员或者方法的权限也是private。在子类中可以访问继承而来的成员或者方法。
**3.**其它情况下,子类中继承而来的对象或者方法的权限大小取继承权限和父类中的对象或者方法权限的较小值。
如:
cppclass parent { public : protected://父类中的成员权限为protected int a; int b; int c; }; class child :public parent//继承权限为public { public: int d; };
那么在子类中继承而来的父类成员权限就是min(public,protected) = protected。
三,继承赋值兼容规则
在这里我们要知道的便是:
1.子类对象可以赋值给父类对象但是父类对象不能赋值给子类对象 。
**2.**子类对象引用父类对象不需要加上const也可以引用
首先来解释第一个:
**1.**能得到第一个结论是因为子类对象一般都是大于或者等于父类对象的大小的,所以当我们用子类对象给父类对象赋值时子类对象里的成员个数是足够的,但是父类对象要赋值给子类对象的话成员的个数大概率是不够的。
2.我们都知道,当两个对象的类型不一样时比如一个int 型的对象a和double型的对象b:
在两者中间会生成一个叫做临时对象的东西来讲double类型的b改为int型然后再赋值给a。并且因为这个临时对象具有常性所以我们在使用引用时就要加上const才能引用:
但是对于具有继承关系的两个类型的对象我们便可以不用加上const,我们可以直接引用:
能实现这一点是因为在继承中有一个叫做《父子兼容规则》的设计让具有父子关系的两个对象在赋值时在中间不会产生一个临时对象。
四,继承中的作用域
**1.**在继承关系中如果在父类与子类中的成员中如果有重名的在子类中会默认使用谁的呢?答案当然是要使用子类自己的。父类中的对象其实会被子类给隐藏掉,然后我们便只能使用子类自己的成员了。
**2.**在继承中如果子类与父类中的函数名相同相同参数不同能不能构成重载呢?答案是不能,因为构成重载的前提是在同一个作用域内。所以当父类与子类中的函数重名时父类中的函数在子类中也会被隐藏。
3.当父类与子类中都写了析构函数的话,即使这两个析构函数的名字是不一样的这两个析构函数在被编译以后也会拥有同一个名字叫做destroctor。也会构成隐藏关系。要使用的话就得显示使用:parent::~parent()。但是编译器其实会为我们主动调用父类的析构函数,所以我们只需要使用子类的析构函数便可以了。
**4.**父类成员的构造必须显示的调用父类的构造函数来实现。
五,子类/基类的默认构造函数
当我们的父类里默认构造函数时,我们在创建一个子类对象时就会自动调用父类的构造函数完成对变量的初始化工作,例如以下例子:
这个测试用例的结果如下:
它会自动调用父类的构造函数,但是如果我的父类里面没有实现一个默认构造函数呢?
这时我们就得在子类里面显示的调用父类的构造函数了:
对于构造函数来说也是一样的。如果有默认的拷贝构造函数我们便不用显示调用父类的拷贝构造函数,如果没有默认的拷贝构造函数我们便要显示的调用父类的拷贝构造函数。在显示的调用父类的拷贝构造函数时,传参时我们将我们的子对象传给父类的拷贝钩造函数。
但是对于析构函数来说便是不一样的了我们基本上是不用管这个函数的,系统会帮我们自动调用析构函数,遵循的顺序是先父后子。
六,继承中的友元关系
在这里只需要知道一个点,子类不能继承父类的友元关系。你爸爸的财产是你的,但是你爸爸的朋友不一定是你的!!!
七,多继承与菱形继承
1.什么是多继承
简单的理解便是,我们的一个子类有多个直接父类。这样子便构成了多继承。如下图便是多继承:
但是下面的便不是多继承:
2.什么是菱形继承
其实菱形继承便是多继承的一种变式。如下便是菱形继承:
菱形继承便是会造成代码冗余和二义性的问题,比如上图的最后一个class D类里面便会有两份继承于class A的成员,一份由B继承而来,一份由C继承而来。
以下面的代码为研究对象:
可以看到当我们的d对象想要直接调用_a时便会报错,因为我们的d对象里面不止继承了一个_a对象,相反d里面继承了多个_a。所以当我们的d想要直接调用_a时便会出现二义性,编译器不知道该用那个。但是只要我们指明了便可以成功的调用_a了。
3.解决方法
1.在调用对象时指定一下作用域
这个方法其实我们已经用过了,就在前面:
2.使用虚函数
这个方法是祖师爷幸苦增加的一个方法。我们只要在继承的时候加上vitural便可以了。注意**加virtual的地方一定得在腰部(也就是产生菱形关系的最后一个对象的父类那里)**使用如下:
可以看到这个时候我们的_a便可以不指定作用域直接调用了。
原理:
首先我我们先来打开监视窗口看看不加virtual时我们的d对象里面的数据:
从这里便可以知道我们的d对象里面有两个_a变量。
现在加上virtual看看,内存显示如下:
从这里可以看到_a的数量便在d对象里只剩下一个了。但是为什么能够做到这个呢?这就要先看看这里多出来的两个地址:
1.dc 7b 1a 00
2.e4 7b 1a 00
通过内存窗口查看如下(记住是小端显示):
通过内存窗口的显示我们可以知道这个dc 7b 1a 00里面放的便是存放这个地址的地址与_a的偏移量,所以这个地址可以通过偏移量便可以找到_a变量。
再来看看第二个地址:
同理,这个地址也可以通过它存放的地址里存的偏移量找到_a。有了这个偏移量找_a的方法于是我们的代码冗余问题和二义性问题便解决了,因为在d对象里只有一个_a变量。