1 多态的概述
在生活中,也有各种各样的多态情景:比如,当班主任说,8:50分钟,同学们要进直播间,上课,同学们分别进入各班级的直播间。
多态意义:
C++中所谓的多态(Polymorphism)是指,是由继承而产生的相关的不同的类,其对象对同一个消息会作不同响应。
意义在于能增加程序的灵活性,可以减轻系统升级,维护,调用的工作量和复杂度,程序的可扩展性会大大增强。
比如:
在设计动物类时,会设计比如吃,睡觉等方法,对于现在的条件,只能领养狗
动物类:吃,睡觉
狗类
等五年后,发财了,又想领养动物,比如:猫
程序员只需要自定义一个猫的类,来继承于动物类,以前设计好的动物类,狗类都不需要来进行修改。
2 多态形成条件
- 首先要有继承,并且基类中有虚函数 ---- 基类中完成
- 派生类中要重写(复写---override)基类的虚函数 ---- 派生类完成
- 派生类对象的地址赋值给基类的指针,通过基类的指针,调用虚函数 (基类指针(*)指向派生类对象)或基类的引用(&)引用派生类对象 ----> 具体实际应用来完成
3 多态实现
#include <iostream>
using namespace std;
//设计一个基类,用来绘制形状
class Sharp
{
protected:
int x;
int y;
public:
Sharp(int x=0,int y=0):x(x),y(y)
{
}
//设计一个虚函数:关键字(virtual) + 普通成员函数
virtual void draw()
{
cout << " Sharp:" << "("<<x << "," << y << ")"<<endl;
}
};
class Circle:public Sharp
{
public:
Circle(int x,int y,int radius):Sharp(x,y)
{
this->radius = radius;
}
// override 在派生类中重写基类的虚函数,可以加virtual也可以不加,都不影响
virtual void draw()
{
cout << " Circle:" << "("<<x << "," << y << ")" << "radius = " << this->radius<<endl;
}
private:
int radius;
};
///
//在设计新的需求功能时,原来的功能模型和框架仍不需要进行修改
class Rect:public Sharp
{
public:
Rect(int x=0,int y=0,int w = 0,int h=0):Sharp(x,y)
{
this->height = h;
this->weight = w;
}
// override 在派生类中重写基类的虚函数,可以加virtual也可以不加,都不影响
virtual void draw()
{
cout << " Rect:" << "("<<x << "," << y << ")";
cout << "weight = " << this->weight;
cout << "height = " << this->height << endl;
}
private:
int weight;
int height;
};
//在具体过程中,可以通过全局函数 或者 通过其他类(设计师类)
void draw_function(Sharp* pr) //很多年后,接口还是可以继续使用
{
pr->draw();
}
void draw_function(Sharp& pr)
{
pr.draw();
}
//关于多态的注意事项:
//1.基类中的虚函数重写对于派生类是可选的,如果不重写,那么该函数在被调用时,会调用基类中的虚函数
//把这个过程叫作事件的传递给基类,由基类来处理事件,派生类是不知道基类处理了没有,它只起到一个传递的作用
//如果派生类重写,那么该函数在被调用时,会调用派生类中的虚函数,那这个过程叫作事件的拦截,该事件不再会
//传递给基类
int main()
{
Circle c(3,4,5);
//调用绘制形状函数
draw_function(&c); //基类指针指向了派生类对象,那么在程序运行时,发生了一个多态现象
//执行了派生类中的 draw函数,而不是基类中的draw函数。
//当去掉基类中关键字(virtual)后,就不会调用派生类中的draw函数
//所以说,多态的实现就是通过virtual关键字来完成。
draw_function(c);
Rect r(1,2,8,9);
draw_function(&r);
draw_function(r);
return 0;
}
4 重载 、重写(覆盖)和重定义
重载:
- 在同一个类中(相同范围)
- 函数名字相同
- 参数不同(参数个数 参数类型 参数顺序)
- virtual关键字不作为判断标准,可有可无
重写(覆盖):是指派生类函数重写基类的函数
- 不在同一个类中,分别位于基类和派生类中
- 参数相同
- 函数名相同
- 基类中函数 必须有virtual关键字,派生类可有可无
重定义(隐藏):是指派生类的函数屏蔽了与其同名的基类函数
- 如果派生类的函数和基类的函数同名,但是参数不同,此时,不管有无virtual,基类的函数被隐藏
- 如果派生类的函数 与基类的函数同名,并且参数也相同,但是,基类函数没有virtual关键字,此时,基类的函数被隐藏。
class Base
{
public:
void func(int a = 0)
{
cout << "Base::func() " << endl;
}
};
class Derived :public Base
{
public:
#if 1
void func(int a = 0)
{
cout << "Derived::func() " << endl;
}
#endif
};
int main()
{
Derived d;
d.func();
Base* pd = &d;
pd->func();
/*
Derived::func()
Base::func() ---->基类中,没有虚函数,是不会发生多态,而派生类中和基类中同名函数,这种关系被叫作重定义
*/
return 0;
}
总结:
- 在继承时,加virtual,可以实现虚继承
- 继承中,在基类中函数定义时,加virtual,可以实现多态
- 关键字同一个,但是意义不一样,实现方式不一样,虚继承时,在访问基类成员,进行拷贝操作,真正的成员存储只有一份;多态时,在访问虚函数时,进行动态绑定,但是都是使用virtual来实现,管理方式都是一样,虚继承,继承的派生类中,会分配一个vptr指针,在多态中,分配一个vptr指针 ,还有一个vptrtable
5 多态的原理实现
虚函数表(vtable)和vptr
- 当类中声明虚函数时,编译器会在类中生成一个虚函数表
- 虚函数表是一个存储成员函数指针的结构
- vtable和vptr都是由编译器自动生成和维护
- vitrual成员函数会被编译器放入到虚函数表中
- vptr指向vtalbe这个过程,也是由编译器来维护
总结:
在一个类中,成员函数:普通成员函数和虚函数
它们之间有所不同,在函数调用时,那么普通成员函数的调用效率要高于虚函数,所以,在进行类设计时,如果没有要实现多态,原则上不要把函数声明为虚函数,原因在于普通成员函数是直接调用(在编译时,根据类型来确定调用关系),而虚函数通过vptr指针,进行虚函数表中查找,重新进行寻址操作才能确定真正应该要调用的函数。
6 纯虚函数 和抽象类
在多继承和多态中,对于基类的虚函数 而言,通常情况下,在派生类中,都会进行重写,从面可以实现多态,那么,对于基类中虚函数 实现,在一些情况下,意义不大。从而在C++中,有了更好的解决方案,使用纯虚函数处理。
纯虚函数语法:使用0来给函数进行赋值,那么该函数就是纯虚函数
virtual 函数声明 = 0;
抽象类:一个类中,拥有纯虚函数 的类,它是一个抽象类,该类不能实例化.