详谈C++中的多态
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; }int main() { DoBussiness(new PlusCaculator); return EXIT_SUCCESS; }
运行结果:
2.抽象基类和纯虚函数(pure virtual function)
在设计时,常常希望基类仅仅作为其派生类的一个接口。这就是说,仅想对基类进行向上类型转换,使用它的接口,而不希望用户实际的创建一个基类的对象。同时创建一个纯虚函数允许接口中放置成员原函数,而不一定要提供一段可能对这个函数毫无意义的代码。
做到这点,可以在基类中加入至少一个纯虚函数(pure virtual function),使得基类称为抽象类(abstract class).
n 纯虚函数使用关键字virtual,并在其后面加上=0。如果试图去实例化一个抽象类,编译器则会阻止这种操作。
n 当继承一个抽象类的时候,必须实现所有的纯虚函数,否则由抽象类派生的类也是一个抽象类。
n Virtual void fun() = 0;告诉编译器在vtable中为函数保留一个位置,但在这个特定位置不放地址。
建立公共接口目的是为了将子类公共的操作抽象出来,可以通过一个公共接口来操纵一组类,且这个公共接口不需要事先(或者不需要完全实现)。可以创建一个公共类.
案例: 模板方法模式
//抽象制作饮品 class AbstractDrinking{ public: //烧水 virtual void Boil() = 0; //冲泡 virtual void Brew() = 0; //倒入杯中 virtual void PourInCup() = 0; //加入辅料 virtual void PutSomething() = 0; //规定流程 void MakeDrink(){ Boil(); Brew(); PourInCup(); PutSomething(); } }; //制作咖啡 class Coffee : public AbstractDrinking{ public: //烧水 virtual void Boil(){ cout << "煮农夫山泉!" << endl; } //冲泡 virtual void Brew(){ cout << "冲泡咖啡!" << endl; } //倒入杯中 virtual void PourInCup(){ cout << "将咖啡倒入杯中!" << endl; } //加入辅料 virtual void PutSomething(){ cout << "加入牛奶!" << endl; } }; //制作茶水 class Tea : public AbstractDrinking{ public: //烧水 virtual void Boil(){ cout << "煮自来水!" << endl; } //冲泡 virtual void Brew(){ cout << "冲泡茶叶!" << endl; } //倒入杯中 virtual void PourInCup(){ cout << "将茶水倒入杯中!" << endl; } //加入辅料 virtual void PutSomething(){ cout << "加入食盐!" << endl; } }; //业务函数 void DoBussiness(AbstractDrinking* drink){ drink->MakeDrink(); delete drink; } void test(){ DoBussiness(new Coffee); cout << "--------------" << endl; DoBussiness(new Tea); }
运行结果:
3.纯虚函数和多继承和虚析构函数
多继承带来了一些争议,但是接口继承可以说一种毫无争议的运用了。绝大数面向对象语言都不支持多继承,但是绝大数面向对象对象语言都支持接口的概念,c++中没有接口的概念,但是可以通过纯虚函数实现接口。
接口类中只有函数原型定义,没有任何数据定义。多重继承接口不会带来二义性和复杂性问题。接口类只是一个功能声明,并不是功能实现,子类需要根据功能说明定义功能实现。
注意:除了析构函数外,其他声明都是纯虚函数。
虚析构函数:
虚析构函数是为了解决基类的指针指向派生类对象,并用基类的指针删除派生类对象。
class People{ public: People(){ cout << "构造函数 People!" << endl; } virtual void showName() = 0; virtual ~People(){ cout << "析构函数 People!" << endl; } }; class Worker : public People{ public: Worker(){ cout << "构造函数 Worker!" << endl; pName = new char[10]; } virtual void showName(){ cout << "打印子类的名字!" << endl; } ~Worker(){ cout << "析构函数 Worker!" << endl; if (pName != NULL){ delete pName; } } private: char* pName; }; void test(){ People* people = new Worker; people->~People(); }
运行结果: