多态(Polymorphism)按字面的意思就是“多种状态”。在面向对象语言中,接口的多种不一样的实现方式即为多态。引用Charlie Calverts对多态的描述——多态性是容许你将父对象设置成为一个或更多的他的子对象相等的技术,赋值以后,父对象就能够根据当前赋值给它的子对象的特性以不一样的方式运做(摘自“Delphi4 编程技术内幕”)。ios
简单的说,就是一句话:容许将子类类型的指针赋值给父类类型的指针。c++
多态按字面的意思就是多种形态。当类之间存在层次结构,而且类之间是经过继承关联时,就会用到多态。segmentfault
C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不一样的函数。 函数
虚基类使得其派生类在间接地屡次继承本类时,只继承本类的一份成员,避免出现屡次继承产生多份拷贝的二义性。this
/* VirtualBaseClass.cpp 虚基类实例*/ #include <iostream> class Animal // 定义动物类 { protected: int age; // 存储年龄 public: Animal(int age_t):age(age_t){}; // 参数列表初始化年龄 ~Animal(){}; // 析构函数,这里用不上 }; class Bird:virtual public Animal // 鸟虚继承动物类 { private: int flyHight; // 定义鸟飞的高度 protected: int getFlyHight(); // 获取鸟飞的高度 public: Bird(int flyHight_t, int age_t):flyHight(flyHight_t), Animal(age_t){}; // 参数列表初始化高度和年龄 ~Bird(){}; // 析构函数 }; int Bird::getFlyHight() { return flyHight; // 返回鸟飞的高度 } class Fish:virtual public Animal // 鱼虚继承动物类 { private: int divingDepth; // 定义鱼潜水深度 protected: int getDivingDepth(); // 获取鱼潜水深度 public: Fish(int divingDepth_t, int age_t):divingDepth(divingDepth_t), Animal(age_t){}; // 参数列表初始化深度年龄 ~Fish(){}; // 析构函数 }; int Fish::getDivingDepth() { return divingDepth; // 返回鱼潜的深度 } class WaterBird:virtual public Bird, Fish // 多重继承鸟和鱼,此时间接地两次继承了动物类,可是以前鸟和鱼继承时使用了虚继承,所以此处默认虚继承,可是为了可读性,这里最好写上virtual { public: WaterBird(int flyHight_t, int divingDepth_t, int age_t):Bird(flyHight_t, 5), Fish(divingDepth_t, 6), Animal(age_t){}; ~WaterBird(){}; void displayInfos(); }; void WaterBird::displayInfos() { std::cout << "种族:飞鱼" << std::endl; std::cout << "年龄:" << age << std::endl; std::cout << "最高飞行高度:" << getFlyHight() << std::endl; std::cout << "最大潜水深度:" << getDivingDepth() << std::endl; } int main() { WaterBird wb(200, 50, 4); wb.displayInfos(); return 0; }
种族:飞鱼 年龄:4 最高飞行高度:200 最大潜水深度:50
咱们发现,在水鸟内继承鸟和鱼的age时,初始化的值分别为5和6,而后虚继承动物类的age时,赋值为4,最终的结果为4。设计
这意味着咱们经过建立虚基类继承的成员默认为最高基类的成员。指针
若是想要用鸟或者鱼的age,须要使用域做用符。code
多态的本质是同一个函数的多种形态
通常而言,C++支持的多态有两种:
静态联编在编译时就已经肯定了多态性,通常经过重载进行实现;
动态联编则在运行时才能肯定多态性 ,通常经过继承和虚函数来实现。
若某个基类函数 声明为虚函数,当派生类使用 基类指针或 基类引用操做派生类对象时,系统会自动用 派生类的 同名函数代替基类虚函数。
若是基类函数没有声明为虚函数,那么使用基类指针调用时,调用到的是基类的函数。
/* VirtualFunction.cpp 虚函数实例*/ #include <iostream> class BaseClass // 基类 { private: public: BaseClass(){}; ~BaseClass(){}; void speakNormal() // 提示信息的函数,这里是普通函数 { std::cout << "这里是基类!" << std::endl; } virtual void speakVirtual() // 提示信息的函数,这里是虚函数 { std::cout << "这里是基类!" << std::endl; } }; class DerivedClass:public BaseClass // 公有继承 { private: public: DerivedClass(){}; ~DerivedClass(){}; void speakNormal() // 提示信息的函数,这里是普通函数 { std::cout << "这里是派生类!" << std::endl; } virtual void speakVirtual() // 提示信息的函数,这里是虚函数,虚函数默认继承时虚函数,可是为了可读性,建议在写的时候加上virtual关键字 { std::cout << "这里是派生类!" << std::endl; } }; int main() { DerivedClass dercs; // 建立一个普通的派生类对象 BaseClass* bascs = &dercs; // 建立一个基类指针指向刚刚的派生类对象 bascs->speakNormal(); // 基类指针调用普通函数 bascs->speakVirtual(); //基类指针调用虚函数 return 0; }
这里是基类! 这里是派生类!
咱们能够看到:
使用普通函数,基类指针调用的是基类同名函数;
使用虚函数,基类指针调用的是基类指针指向对象的同名函数。
注意:派生类有时候须要销毁资源,若是使用基类指针,那么必需要将基类析构函数设为虚函数,不然没法销毁派生类资源。
另外,构造函数不能做为虚函数。
纯虚函数是指以下模式的函数:
virtual 返回值 函数名( 参数列表 ) = 0;
抽象类是指包含至少一个纯虚函数的类:
class 类名 { public: virtual 返回值 函数名( 参数列表 ) = 0; 其它函数声明 }
有时候咱们不知道如何实现某个功能,好比要给一个形状求面积,可是三角形和矩形的求面积方法并不相同,可是求面积又是必需要作的。
这种状况咱们就可使用纯虚函数。
纯虚函数只需声明,无需实现,具体的实如今其派生出子类之后再实现,若是子类声明了这个纯虚函数可是没有实现,那么这个函数依然被视做纯虚函数。
例如定义一个形状类,里面有一个求面积的纯虚函数做为接口,在三角形和矩形、圆形等不一样的形状里具体实现便可。
包含至少一个纯虚函数的类是抽象类,抽象类不能实例化对象,必需要全部纯虚函数实现的子类才能实例化对象,若是子类依然有纯虚函数,那么这个类依然是一个抽象类。
/* AbstractClass.cpp 抽象类实例*/ #include <iostream> class Shapes // 定义形状类 { private: public: Shapes(){}; ~Shapes(){}; virtual void disp() = 0; // 定义求面积的纯虚函数,此时形状类做为抽象类,不能实例化对象,纯虚函数要在派生类中实现 }; class Squera:public Shapes { public: Squera(int length, int width); // (矩形长度, 矩形宽度) ~Squera(){}; class Squera_data // 定义一个内部类 { public: int length_t; // 长 int width_t; // 宽 }; void disp(); // 实现基类的纯虚函数 private: Squera_data data_t; // 存储矩形的数据 }; Squera::Squera(int length, int width) { data_t.length_t = length; data_t.width_t = width; } void Squera::disp() { std::cout << "矩形面积为:" << data_t.length_t * data_t.width_t << std::endl; } class Triangle { private: double bottomL_l; // 底边 double heightL_l; // 高 class Triangle_run // 内部类来运算面积 { private: public: Triangle_run(Triangle& tri_l){ std::cout << "三角形面积为:" << tri_l.bottomL_l * tri_l.heightL_l / 2 << std::endl; } }; public: Triangle(double bottomL, double heightL):bottomL_l(bottomL), heightL_l(heightL){}; // (底, 高),三角形的属性 ~Triangle(){}; void disp(); // 实现纯虚函数 }; void Triangle::disp() { Triangle_run tri_r_l(*this); // 把刚刚实例化的本类做为参数传入 } int main() { Squera squera_l(3, 4); // 矩形求面积实现 squera_l.disp(); Triangle tri_t(3, 4); // 三角形求面积实现 tri_t.disp(); return 0; }
矩形面积为:12 三角形面积为:6
咱们发现,咱们实现了子类不一样求面积的函数,可是都以disp为名字,这样,咱们直接调用disp就能够在不一样的派生类中作到同一件事情——求面积,尽管它们的具体实现方式不一样。
值得注意的是,这里用到了内部类,又称嵌套类,在类中定义类,内部类和外部类能够互相调用对方私有成员,可是内部类调用外部类时须要传入参数。
参考资料: