1. 多态基本概念
多态是面向对象程序设计语言中数据抽象和继承之外的第三个基本特征。
多态性(polymorphism)提供接口与具体实现之间的另一层隔离,从而将”what”和”how”分离开来。多态性改善了代码的可读性和组织性,同时也使创建的程序具有可扩展性,项目不仅在最初创建时期可以扩展,而且当项目在需要有新的功能时也能扩展。
c++支持编译时多态(静态多态)和运行时多态(动态多态),运算符重载和函数重载就是编译时多态,而派生类和虚函数实现运行时多态。
静态多态和动态多态的区别就是函数地址是早绑定(静态联编)还是晚绑定(动态联编)。如果函数的调用,在编译阶段就可以确定函数的调用地址,并产生代码,就是静态多态(编译时多态),就是说地址是早绑定的。而如果函数的调用地址不能编译不能在编译期间确定,而需要在运行时才能决定,这这就属于晚绑定(动态多态,运行时多态)。
//计算器 class Caculator{ public: void setA(int a){ this->mA = a; } void setB(int b){ this->mB = b; } void setOperator(string oper){ this->mOperator = oper; } int getResult(){ if (this->mOperator == "+"){ return mA + mB; } else if (this->mOperator == "-"){ return mA - mB; } else if (this->mOperator == "*"){ return mA * mB; } else if (this->mOperator == "/"){ return mA / mB; } } private: int mA; int mB; string mOperator; }; //这种程序不利于扩展,维护困难,如果修改功能或者扩展功能需要在源代码基础上修改 //面向对象程序设计一个基本原则:开闭原则(对修改关闭,对扩展开放) //抽象基类 class AbstractCaculator{ public: void setA(int a){ this->mA = a; } virtual void setB(int b){ this->mB = b; } virtual int getResult() = 0; protected: int mA; int mB; string mOperator; }; //加法计算器 class PlusCaculator : public AbstractCaculator{ public: virtual int getResult(){ return mA + mB; } }; //减法计算器 class MinusCaculator : public AbstractCaculator{ public: virtual int getResult(){ return mA - mB; } }; //乘法计算器 class MultipliesCaculator : public AbstractCaculator{ public: virtual int getResult(){ return mA * mB; } }; void DoBussiness(AbstractCaculator* caculator){ int a = 10; int b = 20; caculator->setA(a); caculator->setB(b); cout << "计算结果:" << caculator->getResult() << endl; delete caculator; }
2. 向上类型转换及问题
2.1 问题抛出
对象可以作为自己的类或者作为它的基类的对象来使用。还能通过基类的地址来操作它。取一个对象的地址(指针或引用),并将其作为基类的地址来处理,这种称为向上类型转换。
也就是说:父类引用或指针可以指向子类对象,通过父类指针或引用来操作子类对象。
class Animal{ public: void speak(){ cout << "动物在唱歌..." << endl; } }; class Dog : public Animal{ public: void speak(){ cout << "小狗在唱歌..." << endl; } }; void DoBussiness(Animal& animal){ animal.speak(); } void test(){ Dog dog; DoBussiness(dog); }
运行结果: 动物在唱歌
问题抛出: 我们给DoBussiness传入的对象是dog,而不是animal对象,输出的结果应该是Dog::speak。
2.2 问题解决思路
解决问题,我们需要了解下绑定(捆绑,binding)概念。
把函数体与函数调用相联系称为绑定(捆绑,binding)
当绑定在程序运行之前(由编译器和连接器)完成时,称为早绑定(early binding).C语言中只有一种函数调用方式,就是早绑定。
上面的问题就是由于早绑定引起的,因为编译器在只有Animal地址时并不知道要调用的正确函数。编译是根据指向对象的指针或引用的类型来选择函数调用。这个时候由于DoBussiness的参数类型是Animal&,编译器确定了应该调用的speak是Animal::speak的,而不是真正传入的对象Dog::speak。
解决方法就是迟绑定(迟捆绑,动态绑定,运行时绑定,late binding),意味着绑定要根据对象的实际类型,发生在运行。
C++语言要实现这种动态绑定,必须有某种机制来确定运行时对象的类型并调用合适的成员函数。对于一种编译语言,编译器并不知道实际的对象类型(编译器并不知道Animal类型的指针或引用指向的实际的对象类型)。
2.3 问题解决方案(虚函数,vitual function)
C++动态多态性是通过虚函数来实现的,虚函数允许子类(派生类)重新定义父类(基类)成员函数,而子类(派生类)重新定义父类(基类)虚函数的做法称为覆盖(override),或者称为重写。
对于特定的函数进行动态绑定,c++要求在基类中声明这个函数的时候使用virtual关键字,动态绑定也就对virtual函数起作用。
- 为创建一个需要动态绑定的虚成员函数,可以简单在这个函数声明前面加上virtual关键字,定义时候不需要.
- 如果一个函数在基类中被声明为virtual,那么在所有派生类中它都是virtual的.
- 在派生类中virtual函数的重定义称为重写(override).
- Virtual关键字只能修饰成员函数.
- 构造函数不能为虚函数
注意: 仅需要在基类中声明一个函数为virtual.调用所有匹配基类声明行为的派生类函数都将使用虚机制。虽然可以在派生类声明前使用关键字virtual(这也是无害的),但这个样会使得程序显得冗余和杂乱。(我建议写上)
class Animal{ public: virtual void speak(){ cout << "动物在唱歌..." << endl; } }; class Dog : public Animal{ public: virtual void speak(){ cout << "小狗在唱歌..." << endl; } }; void DoBussiness(Animal& animal){ animal.speak(); } void test(){ Dog dog; DoBussiness(dog); }