1. 重载,覆盖,隐藏
隐藏:
1) 如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏。
2) 如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏。
覆盖:如果派生类的函数与基类的函数同名,并且参数也相同,基类函数有virtual关键字。此时,基类的函数被覆盖。
重载:在同一个类中,函数与基类的函数同名,但是参数不同。(另外关于重载:传送门)
隐藏虽然调用的不是派生类函数,但参数值还是用的派生类时的参数值,比如:
下面输出的a的值是2
而对于覆盖,虽然调用的是派生类函数,但缺省参数值还是用的基类时的参数值,比如:
输出结果是: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的析构函数,以免造成内存泄漏。