二十二、多态
1.虚函数覆盖(函数重写),多态的概念
如果将基类中的成员函数声明为虚函数,那么其子类中具有相同原型的成员函数就也是虚函数,并且可以对基类的版本形成覆盖,即函数重写。这时,通过指向子类对象的基类指针,或者引用子类对象的基类引用,调用虚函数,实际被执行的将是子类中的覆盖版本,而不再是基类中原始版本,这种语法现象称为多态。
class Base{
virtual boid func(){}
};
class Derived:public Base{
void func(){} //自动变虚,并对基类版本形成覆盖
};
Derived d;
Base* pb = &d;
Base& rb = d;
pb->func();
rb.func();
2.虚函数的覆盖条件
1)只有类的成员函数才能被声明为虚函数,全局函数、静态成员函数、构造函数都不能为虚函数,析构函数可以声明为虚函数
2)只有在基类中以virtual关键字修饰的成员函数才能作为虚函数被子类覆盖,而与子类中版本的vitrual关键字无关
3)虚函数在子类中的覆盖版本和积累中的原始版本要求具有相同的函数签名,即函数名,参数表,常属性一致。
4)如果基类的虚函数返回基本的类型的数据,那么子类的覆盖版本必须返回相同的数据类型
5)如果基类的虚函数返回类类型的指针(A*)或引用(A&),那么允许子类的覆盖版本返回其子类类型的指针(B*)或引用(B&)
参考代码:
#include <iostream>
using namespace std;
class A{};
class B:public A{};
class Base{
public:
virtual void func(void)const{
cout << "Base::func" << endl;
}
virtual A& foo(void){
cout << "Base::foo" << endl;
}
};
class Derived:public Base{
public:
/*virtual*/ void func(void)const{
cout << "Derived::func" << endl;
}
B& foo(void){
cout << "Derived::foo" << endl;
}
};
int main(void)
{
Derived d;
Base* pb = &d;
pb->func();
pb->foo();
return 0;
}
总结:虚函数的覆盖条件:
①必须是成员函数
②virtual声明在基类中
③函数签名一致:函数名,形参表,常属性
④返回基本类型一致,类类型可返回当前
3.多态条件
1)多态的语法特征除了要满足虚函数覆盖,还必须是通过指针或引用去调用虚函数,才能表现出来
3)调用虚函数的指针也可以是this指针,如果使用子类对象调用基类中的成员函数,在该成员函数中的this指针将是一个指向子类对象的基类指针,再通过它去调用虚函数,也可以表现多态的语法特征。//重点
参考代码:
#include <iostream>
using namespace std;
class Base{
public:
virtual int cal(int a,int b){
return a+b;
}
//void func(Base* this=&d)
void func(void){
//cout << this->cal(100,200)...
cout << cal(100,200) << endl;
}
};
class Derived:public Base{
public:
int cal(int a,int b){
return a*b;
}
};
int main(void)
{
Derived d;
Base b = d;
cout << b.cal(100,200) << endl;
d.func();//?
}
4.多态原理(虚函数表和动态绑定)
1)虚函数表会增加内存开销
2)动态绑定过程增加时间开销
3)虚函数不能内联优化
结论:实际开发中如果没有多态语法的要求,最好不要使用虚函数
5.纯虚函数、抽象类和纯抽象函数
1)纯虚函数
virtual 返回类型 函数名(形参表) = 0;
2)抽象类
如果类中包含了纯虚函数,那么该类就是抽象类
注:抽象类不能创建对象,如果子类没有覆盖基类中的全部虚函数,那么该子类也是抽象类,类的抽象属性可以被继承
3)纯抽象类
如果一个抽象类所有的成员函数都是虚函数,那么该抽象类就是纯抽象类
#include <iostream>
using namespace std;
//图形基类,抽象类,纯抽象类
class Shape{
public:
Shape(int x=0,int y=0):m_x(x),m_y(y){}
virtual void draw(void)=0;//纯虚函数
protected:
int m_x;//坐标
int m_y;
};
//圆形子类
class Circle:public Shape{
public:
Circle(int x,int y,int r)
:Shape(x,y),m_r(r){}
void draw(void){//自动变成虚函数
cout << "绘制圆形:" << m_x << "," <<
m_y << "," << m_r << endl;
}
private:
int m_r;
};
//矩形子类
class Rect:public Shape{
public:
Rect(int x,int y,int w,int h)
:Shape(x,y),m_w(w),m_h(h){}
void draw(void){//自动变成虚函数
cout << "绘制矩形:" << m_x << ',' <<
m_y << ',' << m_w << ',' << m_h
<< endl;
}
private:
int m_w;
int m_h;
};
void render(Shape* buf[]){
/* 正常通过指针调用函数,根据指针的类型;
* 但如果通过指针调用虚函数,根据实际指向
* 目标对象类型----多态
* */
for(int i=0;buf[i]!=NULL;i++)
buf[i]->draw();
}
//如果子类没有覆盖基类的纯虚函数,那么它就
//也是抽象类
class Triangle:public Shape{};
int main(void)
{
Shape* buf[1024] = {NULL};
buf[0] = new Rect(1,2,3,4);
buf[1] = new Circle(5,6,7);
buf[2] = new Circle(11,12,71);
buf[3] = new Rect(8,16,7,8);
buf[4] = new Circle(6,9,10);
render(buf);
//Shape s;//抽象类不能创建对象
//Triangle t;
return 0;
}
6.虚析构函数
1)基类的析构函数不能调用子类的析构函数,所以对一个指向子类对象的基类指针,使用delete操作符,实际被执行的仅是基类的析构函数,子类的析构函数执行不到,有内存泄露的风险
2)可以将基类的析构函数声明为虚函数,这时子类的析构函数就也是虚函数,并可以对基类中的虚析构函数形成有效的覆盖,也可以表现多态的语法特性;这时再delete一个指向子类对象的基类指针,实际被执行的将是子类的析构函数,子类析构函数结束后会自动调用基类的析构函数,避免内存泄露。
#include <iostream>
using namespace std;
class Base{
public:
Base(void){
cout << "基类动态资源分配" << endl;
}
virtual ~Base(void){//虚析构函数
cout << "基类动态资源释放" << endl;
}
};
class Derived:public Base{
public:
Derived(void){
cout << "子类动态资源分配" << endl;
}
~Derived(void){//自动变虚函数
cout << "子类动态资源释放" << endl;
}
};
int main(void)
{
Base* pb = new Derived;
//pb->析构函数()
delete pb;
return 0;
}