您的位置:首页 > 编程语言 > C语言/C++

[c++基础]继承和多态

2017-03-20 19:36 176 查看
1.     重载,覆盖,隐藏

隐藏: 

1) 如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏。

 2) 如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏。

覆盖:如果派生类的函数与基类的函数同名,并且参数也相同,基类函数有virtual关键字。此时,基类的函数被覆盖。

重载:在同一个类中,函数与基类的函数同名,但是参数不同。(另外关于重载:传送门


  


隐藏虽然调用的不是派生类函数,但参数值还是用的派生类时的参数值,比如:


   

  下面输出的a的值是2
而对于覆盖,虽然调用的是派生类函数,但缺省参数值还是用的基类时的参数值,比如:


  
输出结果是:B->1

解析:

p->test()执行过程理解:

       (1) 由于B类中没有覆盖(重写)基类中的虚函数test(),因此会调用基类A中的test();

       (2) A中test()函数中继续调用虚函数 fun(),因为虚函数执行动态绑定,p此时的动态类型(即目前所指对象的类型)为B*,因此此时调用虚函数fun()时,执行的是B类中的fun();所以先输出“B->”;

       (3) 缺省参数值是静态绑定,即此时val的值使用的是基类A中的缺省参数值,其值在编译阶段已经绑定,值为1,所以输出“1”;

       最终输出“B->1”。所以大家还是记住上述结论:绝不重新定义继承而来的缺省参数值!



总结:

对于隐藏,指针是什么类型,就调用什么类里的函数;

对于覆盖,就看最原来指向的地址是哪个类就是哪个。

隐藏和覆盖的原理传送门


  

(1)对于隐藏:

C++编译器在编译的时候,要确定每个对象调用的函数的地址,这称为早期绑定(early
binding)

对于简单的继承关系,其子类内存布局,是先有基类数据成员,然后再是子类的数据成员



我们构造fish类的对象时,首先要调用animal类的构造函数去构造animal类的对象,然后才调用fish类的构造函数完成自身部分的构造,从而拼接出一个完整的fish对象。当我们将fish类的对象转换为animal类型时,该对象就被认为是原对象整个内存模型的上半部分,也就是图中的“animal的对象所占内存”。因此,输出animal
breathe, 下半部分被隐藏

(2)对于覆盖:
前面输出的结果是因为编译器在编译的时候,就已经确定了对象调用的函数的地址,要解决这个问题就要使用迟绑定(late binding)技术。当编译器使用迟绑定时,就会在运行时再去确定对象的类型以及正确的调用函数。而要让编译器采用迟绑定,就要在基类中声明函数时使用virtual关键字。
当fish类的fh对象构造完毕后,其内部的虚表指针也就被初始化为指向fish类的虚表。在类型转换后,调用pAn->breathe(),由于pAn实际指向的是fish类的对象,该对象内部的虚表指针指向的是fish类的虚表,因此最终调用的是fish类的breathe()函数。

  2.
虚函数的工作原理,虚表   
传送门 

   A *pa = new B();  
   pa->fun1();  
执行过程:
调用了B::fun1(),但是B::fun1()不是像普通函数那样直接找到函数地址而执行的。真正的执行方式是:首先取出pa指针所指向的对象的vptr的值,这个值就是vtbl的地址,由于调用的函数B::fun1()是第一个虚函数,所以取出vtbl第一个表项里的值,这个值就是B::fun1()的地址了,最后调用这个函数。因此只要vptr不同,指向的vtbl就不同,而不同的vtbl里装着对应类的虚函数地址,所以这样虚函数就可以完成它的任务,多态就是这样实现的。

 3. 纯虚函数

(1)纯虚函数就是基类只定义了函数体,没有实现过程定义方法如下virtual voidEat() = 0;纯虚函数有点像java中的接口,自己不去实现过程,让继承他的子类去实现

(2)一个类中有纯虚函数,则该类不可以实例化

(3)虚函数和纯虚函数的区别:虚函数中的函数是实现的哪怕是空实现,它的作用是这个函数在子类里面可以被覆盖,运行时动态绑定;纯虚函数是个接口,是个函数声明,在基类中不实现,要等到子类中去实现

4. 公有,私有,保护继承
通过继承,一个对象可以获得另一个对象的属性(包括函数),并可向其中加入属于自己的一些特征。

http://blog.csdn.net/fanyun_01/article/details/50985330

5. 派生类的构造函数
(1)缺省参数:

double sum(float nNum1, float nNum2 = 10);

注意:

A、 默认参数只要写在函数声明中即可。

B、 默认参数应尽量靠近函数参数列表的最右边,以防止二义性。

 

(2)多级继承时,只需要写出上一层派生类的构造函数,不用列出每一层派生类的构造函数

构造函数的调用顺序 :基类构造函数总是被优先调用,这说明创建派生类对象时,会先调用基类构造函数,再调用派生类构造函数,如果继承关系有好几层的话,例如:A
--> B --> C
那么创建C类对象时构造函数的执行顺序为:A类构造函数 --> B类构造函数 --> C类构造函数
构造函数的调用顺序是按照继承的层次自顶向下、从基类再到派生类的。

(3)多重继承,构造函数的执行顺序是按照派生类声明时基类出现的顺序,如:class C: public A, public B  构造函数的执行顺序就是A->B->C本身。

二义性:A中有int a, B中有int a,C中有int a, 且C继承于A和B, 此时,C c1; c1.a将调用C类中的a,要调用A类中的可以写成c1.A: : a

 
6. 虚继承
classA

classB: virtual public A  //A是B的虚基类

classC: virtual public A   //A是C的虚基类

class D:public B, public C

虚基类使在继承间接共同基类时只保留一份成员,注意,此时D的构造函数就要把A也写进去,而不是仅仅写B,C和自身就可以。

例如:



 7.虚析构函数
传送门  中的第4部分

最好把基类的析构函数声明为虚函数,这样,若程序中显示地使用了delete运算符删除一个对象,而delete运算符的操作对象用了指向派生类对象的基类指针,即Base *a= new Child(); delete a;则系统会自动调用子类Child的析构函数,以免造成内存泄漏。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  c++