简介
多态按字面的意思就是多种形态。当类之间存在层次结构,并且类之间是通过继承关联时,就可能会用到多态。C++多态就是当调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。
1、静态多态
我们以前说过的函数重载就是一个简单的静态多态,静态多态是编译器在编译期间完成的,编译器会根据实参类型来选择调用合适的函数,如果有合适的函数可以调用就调,没有的话就会发出警告或者报错。
#include using namespace std;class Gril{public: char m_name[50]; // 姓名 int m_age; // 年龄 int m_height; // 身高,单位:厘米cm char m_body[30]; // 身材,火辣;普通;飞机场。 char m_face[30]; // 颜值,漂亮;一般;歪瓜裂枣。 void Show() // 显示超女基本信息的成员函数体{ printf("姓名:%s,年龄:%d,身高:%d,身材:%s,颜值:%s\n", m_name, m_age, m_height, m_body, m_face); } /* virtual void Show() // 显示超女基本信息的成员函数体 { printf("姓名:%s,年龄:%d,身高:%d,身材:%s,颜值:%s\n", m_name, m_age, m_height, m_body, m_face); }*/protected:private:};class SuperGril:public Gril{public: char m_nick[50]; // 称号 char m_palace[50]; // 居住的宫殿 int m_salary; // 奉禄 void Show() // 显示王妃的称号、宫殿和奉禄{ printf("姓名:%s,称号:%s,栖:%s,奉禄:%d两银子。\n", m_name, m_nick, m_palace, m_salary); }protected:private:};int main(){ SuperGril sgirl; strcpy_s(sgirl.m_name, "杨玉环"); sgirl.m_age = 28; sgirl.m_height = 168; strcpy_s(sgirl.m_body, "火辣"); strcpy_s(sgirl.m_face, "漂亮"); strcpy_s(sgirl.m_nick, "杨贵妃"); strcpy_s(sgirl.m_palace, "华清宫"); sgirl.m_salary = 10000; Gril *pGirl; // 基类的指针 SuperGril *pSuperGril; // 派生类的指针 pGirl = pSuperGril = &sgirl; // 都指向派生类 pGirl->Show(); // 将调用的是基类的Show方法 pSuperGril->Show(); // 将调用的是派生类的Show方法 system("pause"); return 0;}
上段代码在visual stdio 2017上面去验证,运行效果:
main函数中,创建的是sgril对象。
pGirl = pSuperGril = &sgirl; // 都指向派生类
为什么调用pGirl->Show();调用的不是派生类中show的方法呢?
pGirl->Show方法在程序编译期间就已经设置好了
如果基类指针调用派生类的方法,必须动态的指向哪个对象,得声明为virtual函数。
2、动态多态
动态多态,是指在程序运行时根据基类的指针或者引用指向哪个对象,来确定调用哪一个类的虚函数。让我们对程序稍作修改,在Girl类中,Show方法的声明前放置关键字 virtual,如下所示:
virtual void Show() // 显示超女基本信息的成员函数体{ printf("姓名:%s,年龄:%d,身高:%d,身材:%s,颜值:%s\n", m_name, m_age, m_height, m_body, m_face);}
其它的代码都不变,编译并运行,结果如下:
此时,编译器看的是指针的内容,而不是它的类型。这就是多态的一般使用方式。
3、虚函数
虚函数是在基类中使用关键字 virtual 声明的函数,在派生类中重新定义虚函数。我们想要的是在程序中可以根据所调用的对象类型来选择调用的函数,这种操作被称为动态链接,或后期绑定。
4、纯虚函数
我们可以在基类中只声明虚函数,没有函数的定义,在派生类中去实现函数的定义,这个时候就会用到纯虚函数。
class Girl // 定义超女类{public: char m_name[50]; // 姓名 int m_age; // 年龄 int m_height; // 身高,单位:厘米cm char m_body[30]; // 身材,火辣;普通;飞机场。 char m_face[30]; // 颜值,漂亮;一般;歪瓜裂枣。 virtual int Show()=0; // 申明一个纯虚函数};
virtual int Show()=0;告诉编译器,函数只有声明,没有定义,是纯虚函数。
5、C++ 接口(抽象类)
接口描述了类的行为和功能,是标准和规范,而不需要完成类的功能实现。
C++接口是用抽象类来实现的,如果类中至少有一个函数被声明为纯虚函数,则这个类就是抽象类。
设计抽象类的目的,是为了给其他类提供一个可以继承的基类。抽象类不能用于实例化对象,它只能作为接口使用。如果试图实例化一个抽象类的对象,会导致编译错误。
如果一个基类的派生类需要实例化,则必须实现每个虚函数的定义,如果没有在派生类中纯虚函数的定义会导致编译错误。
可用于实例化对象的类被称为具体类。
6、应用经验
对初学者来说,类的多态、虚函数这些概念可能难以理解,因为您想不到它的应用场景,在实际开发中,这些知识的应用场景也比较少,在一些大型的项目开发中才可能用到。现在我以Android camera框架为例子,找一段代码说说这个功能:
例如,camera对应不同的回调有监听执行不同方法,声明在camera.h中:
/03-L9500/android/frameworks/av/camera/include/camera/Camera.hclass CameraListener: virtual public RefBase{public: virtual void notify(int32_t msgType, int32_t ext1, int32_t ext2) = 0; virtual void postData(int32_t msgType, const sp& dataPtr, camera_frame_metadata_t *metadata) = 0; virtual void postDataTimestamp(nsecs_t timestamp, int32_t msgType, const sp& dataPtr) = 0; virtual void postRecordingFrameHandleTimestamp(nsecs_t timestamp, native_handle_t* handle) = 0;};
具体的监听设置在:android_hardware_Camera_native_setup.cpp函数中去实现,Camera.h只是一个抽象接口类函数。
推荐阅读:
专辑|C++专辑
专辑|Camera专业知识