1.多态的概念及分类
多态是面向对象程序设计的一个重要特征。多态的字面意思就是多种状态,在面向对象的程序设计中,一个接口,多种实现即为多态。c++的多态性具体体现在编译和运行两个阶段。编译时多态是静态多态,在编译时就可以确定使用的接口。运行时多态是动态多态,具体引用的接口在运行时才能确定。
静态多态和动态多态区别其实只是在什么时候将函数实现和函数调用关联起来,是在编译时期还是运行时期,即函数地址是早绑定还是晚绑定。静态多态是指在编译期间就可以确定函数的调用地址,并产生代码,这是静态的,也就是说地址是早绑定。静态多态也被叫做静态联编。动态多态是指函数在运行期间才能确定函数的调用地址,这是动态的,也就是说地址是晚绑定。
1.1 静态多态
主要通过函数重载、泛型编程来实现。比如说,相同的函数名,可以通过不同的形参。重载出不同的函数,这就是多态的特性,但这里多态是编译阶段完成,也就是说编译器会将函数绑定到唯一确定的形式上去,这就是静态多态。
1.2 动态多态
动态多态是通过虚函数和类的继承来实现的。动态多态需要满足如下条件:
- 有继承关系;
- 子类要重写父类的虚函数(带virtual的函数);
- 父类指针指向子类对象;
1.3 虚函数、虚指针、虚表
对于类的成员函数,如果前面加上virtual,那么这个成员函数就是虚函数,这个类就是带有虚函数的类。带有虚函数的类比不带虚函数的类会多一个指针,这个指针就是虚指针,而 虚指针指向一个表,这个表就叫做虚表。虚表里面存的内容就是虚函数的地址。虚表是基于类的,也就是说任何带有虚函数的类都会有一张虚表,这张表是在编译时生成的。而虚指针是基于对象的,当初始化对象时,才会初始化虚指针,这是发生在程序运行时,也是动态多态的关键。
1.4 纯虚函数
纯虚函数是在基类中声明的虚函数,它在基类中是没有定义的,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加=0,形如以下格式:
virtual void fun()=0;
含有纯虚函数的类称之为抽象类,它不能生成对象即创建实列,只能创建它的派生类的实例。抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。如果派生类中没有重新定义纯虚函数,而只是继承基类的纯虚函数,则这个派生类仍然还是一个抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体的类。
1.5 虚函数和纯虚函数之间的区别
- 当基类中的某个成员方法,在大多数情形下都应该由子类提供个性化实现,但基类也可以提供缺省备选方案的时候,该方法应该设计为虚函数。
- 当基类中的某个成员方法,必须由子类提供个性化实现的时候,应该设计为纯虚函数。
构造函数和析构函数可以是虚函数吗?
答案是:构造函数不能是虚函数,析构函数可以是虚函数且推荐最好设置为虚函数。
1.6 向上类型转换和向下类型转换
向下类型转换是基类转为派生类的强制类型转换,这种类型转换方式是不安全的;向上类型转换是派生类向基类的类型转换是安全的,如果发生多态,总是安全的。
//向下类型转换 基类转派生类 不安全的
Animal *animal=new Animal;
Cat *cat=(Cat*) animal;
//向上类型转换 派生类转基类 安全的
Cat * cat=new Cat;
Animal *ani=(Aniaml *) cat;
//如果发生多态 总是安全的
Animal *animal=new Cat;
Cat *cat=(Cat *)animal;
2.例子
地址早绑定
#include <iostream>
using namespace std;
//基类 水果类
class Fruit
{
public:
void getClassName()
{
cout << "我是水果类" << endl;
}
private:
};
//派生类 苹果类
class Apple:public Fruit
{
public:
void getClassName()
{
cout << "我是苹果类" << endl;
}
private:
};
//地址早绑定 在编译阶段就已经确定了是执行基类的getClassName()函数
void GetClassName(Fruit &fruit) {
fruit.getClassName();
}
int main()
{
Apple apple;
GetClassName(apple);
return 0;
}
结果
地址晚绑定
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
//基类 水果类
class Fruit
{
public:
//加上virtual
virtual void getClassName()
{
cout << "我是水果类" << endl;
}
private:
};
//派生类 苹果类
class Apple:public Fruit
{
public:
void getClassName()
{
cout << "我是苹果类" << endl;
}
private:
};
//派生类 香蕉类
class Banana :public Fruit
{
public:
void getClassName()
{
cout << "我是香蕉类" << endl;
}
};
//给父类加上virtual 关键字 地址晚绑定 在运行时再确定调用谁的getClassName()
void GetClassName(Fruit &fruit) {
fruit.getClassName();
}
int main()
{
Apple apple;
Banana banana;
//GetClassName(apple);
GetClassName(banana);
waitKey(10000);
//return 0;
}
结果