赞
踩
之前学过继承,子类继承父类的属性,多态就是基于继承而来的,我们在如果只用继承,那么子类继承父类的各种属性在编译环节,就已经被确认了,导致代码不灵活。如果继承下来的某个子类不支持某种问题的解决,那么父类就需要重新编写代码,这样这个子类ok了,其他子类却有可能出现问题。
多态的好处:灵活,提高代码的可复用性。
1、多态的分类
1、静态多态:函数重载和运算符重载属于静态多态,复用函数名(编译期多态);
2、动态多态:派生类和虚函数实现运行时多态(运行期多态)。
函数重载就不必多说了,就是函数名的复用;运算符重载也类似于函数重载,相当于把符号的功能扩大了,同一个符号或者函数,能够实现多种功能,它们都是在编译的时候绑定了,以后都不能改变了,所以叫编译器多太。
2、动态多态的实现
我们以汉堡为例,汉堡为父类,鸡腿堡和牛肉保都继承了汉堡类。
代码:
- #include<iostream>
- using namespace std;
-
- class baseHamburger//父类------汉堡类
- {
- public:
-
- void Material()
- {
- cout << "汉堡的配方为:面包、酱汁、蔬菜、肉饼"<<endl ;
- }
- };
-
- class chickenHamburger:public baseHamburger//子类1------鸡腿堡类
- {
- public:
-
- void Material()
- {
- cout << "汉堡的配方为:面包、酱汁、蔬菜、鸡肉饼" << endl;
- }
- };
-
- class beefHamburger:public baseHamburger//子类2------牛肉堡类
- {
- public:
-
- void Material()
- {
- cout << "汉堡的配方为:面包、酱汁、蔬菜、牛肉饼" << endl;
- }
- };
-
- int main()
- {
- chickenHamburger p1;
- p1.Material();
-
- beefHamburger p2;
- p2.Material();
-
- }
从代码可知,我们父类和2个子类内存在3个同名的函数Material(),我们创建p1和p2,他们都是默认调用类内的Material()函数,而不是调用父类的Material()函数,在程序编译的时候就确认了Material()函数的地址。如果我们想要调用父类的Material()函数,那么只需要加作用域就行。这也是我们最常见的情况。
如果我们这样写
- #include<iostream>
- using namespace std;
-
- class baseHamburger//父类------汉堡类
- {
- public:
-
- void Material()
- {
- cout << "汉堡配方为:面包、酱汁、蔬菜、肉饼" << endl;
- }
- };
-
- class chickenHamburger:public baseHamburger//子类1------鸡腿堡类
- {
- public:
-
- void Material()
- {
- cout << "汉堡配方为:面包、酱汁、蔬菜、鸡肉饼" << endl;
- }
- };
-
- class beefHamburger :public baseHamburger//子类2------牛肉堡类
- {
- public:
-
- void Material()
- {
- cout << "汉堡配方为:面包、酱汁、蔬菜、牛肉饼" <<endl;
- }
- };
-
- //创建一个吃的函数
- void eat(baseHamburger & Hamburger)
- {
- cout << "我吃的";
- Hamburger.Material();
-
- }
-
- int main()
- {
- chickenHamburger p1;
- eat(p1);
-
- beefHamburger p2;
- eat(p2);
- }
我们创建一个吃的eat()函数,函数参数为父类对象,函数体为父类对象指向和子类同名的函数,
此时,编译器默认调用了父类的Material()函数,为什么呢?
1、我们明明创建的是子类对象,那应该调用的是子类的Material()呀!
2、传入eat()函数的参数是父类对象,那应该就是调用父类的Material()呀!
是不是感觉有歧义?
那怎么解决呢?
只需要在父类的Material()函数前加上virtual关键字就可以解决问题。
- class baseHamburger//父类------汉堡类
- {
- public:
-
- virtual void Material()//虚函数
- {
- cout << "汉堡配方为:面包、酱汁、蔬菜、肉饼" << endl;
- }
- };
加上virtual关键字后函数就变成了虚函数,程序运行的时候才确定Material()函数的地址。
有的同学可以说,我直接创建几个不同的函数名不就行了?
这样确实可以,但是你别忘了这个问题是在同名函数,我们要复用函数名前提下提出来的。
而且就算你创建很多个不同的函数名,那代码量不就上来了?且每一个eat()函数的参数都要额外写一个子类对象,函数体实现也就变得复杂了。
我们的目的是传入什么对象,那么就调用什么对象的函数,这也是动态多态的好处。
3、 动态多态的满足条件
1、有继承关系;
2、子类重写父类中的虚函数。动态多态使用:
父类指针或引用指向子类对象,就像我们的eat()函数一样。
4、多态内部原理
当我们继承了父类中的函数的时候,那仅仅是继承了这个函数,其内部是通过虚函数表指针(vfptr)的和虚函数表(vftable)来控制的,vfptr又指向vftable,vftable内存放了所有父类的函数地址,vfptr就指向这些地址,所以子类的内部就固定存在父类的一份vfptr和vftable拷贝,这是最一般的情况。
但是当我们将父类的函数改为虚函数的时候,且在子类重写父类的虚函数,那就不一样了,当我们用父类指针指向子类对象的时候,vftable中同名函数的作用域就从父类变成的子类,相当于把父同名类函数的地址改为子类同名函数的地址。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。