目录
什么时候用到多态
当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态
多态的作用
调用成员函数时,会根据调用函数的对象的类型来执行不同的函数,同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果
多态简单的理解
用基类的指针指向子类的对象
多态的分类
1. 函数重载,函数模板实现多态
同名函数不同返回值和不同参数叫做重载,重载也属于多态的一种
属于静态多态,编译时多态
2. 虚函数+继承实现多态
定义一个基类指针指向派生类对象,通过基类访问的是子类的虚函数
属于动态多态,编译时多态
例子1
定义一个计算机类,一个加法类,一个乘法类,加法和乘法继承计算机类,在外面定义一个函数,
函数里打印加法类和乘法类里的计算结果,参数是计算机类的地址
计算机类
//设计一个计算器类 --基类
class AbstractCalc
{
public:
AbstractCalc(int a,int b)
{
m_a = a;
m_b = b;
}
//计算接口,定义一个统一的接口 -虚函数
virtual int getResult()
{
return 0;
}
protected: //本类和派生类可以使用
int m_a;
int m_b;
private:
};
加法类
//设计一个加法类
class AddCalc:public AbstractCalc
{
public:
AddCalc(int a,int b):AbstractCalc(a,b)
{
}
//定义一个函数,跟基类中的函数同名
//在子类重写(重新实现)基类里面的虚函数(同名同参),那么原先继承过来的虚表里面记录的是 基类的虚函数的地址,现在替换为 子类同名的虚函数的地址,virtual只需要在基类写,派生类里可以省略
virtual int getResult()
{
return m_a +m_b;
}
int data;
};
乘法类
//设计一个乘法类
class MulCalc:public AbstractCalc
{
public:
MulCalc(int a,int b):AbstractCalc(a,b)
{
}
//定义一个函数,跟基类中的函数同名
virtual int getResult() override
{
return m_a * m_b;
}
};
自定义使用多态的函数
//其实我们是希望这个基类指针 调用的是派生类的函数
//也就是说,希望运行的时候 检查的是 指针所指向的对象数据类型, 调用指针所指向对象的里面的函数成员
int calcResult(AbstractCalc *p) //AbstractCalc *p = &a1 AbstractCalc *p = &m1
{
//此时编译器检查的是指针的数据类型,由于指针的数据类型是 基类的类型 AbstractCalc ,所以通过这个指针 调用的是 基类里面的函数
cout<<p->getResult()<<endl;
}
主函数
int main()
{
//直接调用函数,把对象直接当参数传入,就可以省一些事,不用自己打印,后面的派生类都可以这么实现
AddCalc a1(10,20);
calcResult(&a1);
//cout<<a1.getResult()<<endl;
MulCalc m1(10,20);
calcResult(&m1);
//cout<<m1.getResult()<<endl;
return 0;
}
因为基类的getResult函数用virtual来修饰,表示虚函数,当继承了基类的派生类创建对象时,就会生成一个虚函数表,函数的地址就会存放在虚函数表里,当我们在派生类重写基类的getResult函数时,c++就会把重写后的函数地址替换掉虚函数表里的基类getResult函数地址,当我们调用的时候就会查到虚函数表的地址,进行调用所对应地址的函数。
例子2
先不用多态设计
计算图形面积,计算矩形的面积和圆形的面积
矩形类
//矩形类---面积
class Rectangle
{
public:
Rectangle(int w,int h):m_width(w),m_height(h)
{
}
~Rectangle()
{
}
double area()
{
return m_width * m_height;
}
private:
int m_width;
int m_height;
};
圆形类
//圆形类--面积
class Circle
{
public:
Circle(int r):m_r(r)
{
}
~Circle()
{
}
double getarea(
{
return 3.14*m_r*m_r;
}
private:
int m_r;
};
主函数
int main()
{
Rectangle r1(10,20);
r1.area();
Circle c1(10);
c1.area();
return 0;
}
这样子虽然可以算出来,但如果我们还要计算三角形,多边形,正方形的面积时,就会多很多个函数,我们就可以使用多态进行遍历
使用多态来设计 计算图形面积
图形 基类
//图形--基类
class Sharpe
{
public:
Sharpe(){}
~Sharpe(){}
//统一的接口 ---虚函数
virtual double area(){return 0;}
};
矩形类
//矩形类---面积
class Rectangle:public Sharpe
{
public:
Rectangle(int w,int h):m_width(w),m_height(h)
{
}
~Rectangle()
{
}
virtual double area() override//virtual和override可以省略
{
return m_width * m_height;
}
private:
int m_width;
int m_height;
};
圆形类
//圆形类--面积
class Circle:public Sharpe
{
public:
Circle(int r):m_r(r)
{
}
~Circle()
{
}
double area() override
{
return 3.14*m_r*m_r;
}
private:
int m_r;
};
打印各个图形类对象的面积函数
//基类的指针或者引用 指向派生类对象 ,调用的是 派生类的虚函数 ---动态多态
void print_area(Sharpe &p) //Sharpe *p =&r1 Sharpe *p =&c1
{
cout<<p.area()<<endl;
}
主函数
int main()
{
Rectangle r1(10,20);
print_area(r1);
Circle c1(10);
print_area(c1);
return 0;
}
这样只需要调用函数把对象传入就可以直接得出答案,方便很多
注意
- 必须首先在基类中定义虚函数
- 定义一个基类指针指向派生类对象,通过基类访问的是子类的虚函数
- 派生类对应基类的虚函数的关键字可以省略
- 一般通过基类指针访问虚函数时才能体现多态性
- 子类重新实现重写父类中的虚函数,必须返回值函数名参数一致才叫重写
- 如果基类有标志虚函数,派生类没有定义该函数,就没有意义