虚函数与多态
虚函数
virtual关键字
class basr { virtual int get(); }; int base::get(){ }
virtual关键字只用在类定义里的函数声明中,写函数体的时候不需要
构造函数和静态成员函数不能是虚函数
虚函数可以参与多态
其余函数不能
多态的表现形式
- 派生类的指针可以赋给基类指针
- 派生类的对象可以赋给基类引用
- 通过基类指针调用基类和派生类中同名虚函数时:
- 若指针指向基类对象,则调用基类的虚函数
- 若指针指向派生类对象,则调用派生类虚函数
- 这种机制称为“多态”
//派生类的指针可以赋给基类指针
class CBase
{
public:
virtual void SomeVirtualFunction(){ }
};
class CDerived:public CBase
{
public:
virtual void SomeVirtualFunction(){ }
};
int main()
{
CDerived ODerived;
CBase* p=&ODerived;
p->SomeVirtualFunction();//这里调用的时派生类的虚函数
//因为这个基类指针,指向的是派生类的对象ODerived
}
//派生类的对象可以赋给基类引用
class CBase
{
public:
virtual void SomeVirtualFunction(){ }
};
class CDerived:public CBase
{
public:
virtual void SomeVirtualFunction(){ }
};
int main()
{
CDerived ODerived;
CBase& r=ODerived;
r.SomeVirtualFunction();
//这里调用的时派生类的虚函数
//因为这个基类引用,是派生类的对象ODerived
}
多态的作用
在面向对象的程序设计中使用多态,能够增强程序的可扩充性,即程序需要修改或增加功能的时候,需要改动和增加的代码较少
多态实例——魔法门之英雄无敌
- 每一个英雄都应该是一个类
- 每个怪物类编写Attack、FightBack、Hurted成员函数
- Attack函数表现攻击动作,攻击某个怪物,并调用被攻击怪物的Hurted函数,以减少被攻击怪物的生命值,同时也调用被攻击怪物的FightBack函数,进行反击
class CCreature
{
protected:
int nPower;//代表攻击力
int nLifeValue;//代表生命值
};
class CDragon:public CCreature
{
public:
void Attack(CWolf* pWolf)
{
pWolf->Hurted(nPower);
pWolf->FightBack(this);
}
void Attack(CGhost* pGhost)
{
pGhost->Hurted(nPower);
pGhost->FightBack(this);
}
//如果不使用多态,这个Attack需要攻击多个怪物对象
//就需要好多个攻击函数
};
//如果游戏版本更新,增加新怪物,则需要全部类都要增加多这个怪物的攻击程序和反击程序
使用多态
class CCreature
{
protected:
int m_nPower;//代表攻击力
int m_nLifeValue;//代表生命值
public:
virtual void Attack(CCreature* pCreature){ }
virtual void Hurted(int nPower){ }
virtual void FightBack(CCreature* pCreature){ }
//基类只有一个Attack、FightBack、Hurted成员函数
};
class CDragon:public CCreature
{
public:
virtual void Attack(CCreature* pCreature);
virtual void Hurted(int nPower){ }
virtual void FightBack(CCreature* pCreature){ }
//本质是,派生类都算是基类的对象
//派生类指针可以赋值给基类指针!!!(这是多态的表现形式)
};
void CDragon::Attack(CCreature* p)
{
p->Hurted(m_nPower);
p->FightBack(this);
}
void CDragon::Hurted(int nPower)
{
m_nLifeValue-=nPower;
}
void CDragon::FightBack(CCreature* p)
{
p->Hurted(m_nPower/2);
}
多态实例——几何形体程序
输入若干个几何形体的参数,要求按面积排序输出,输出时要指明形状
形状:面积
class CShape
{
public:
virtual double Area()=0;//纯虚函数
//后面等于0,就是纯虚函数
//纯虚函数就是函数体都没有的虚函数
//无法规矩地让所有形体满足统一的规矩
virtual void PrintInfo()=0;
};
class CRectangle:public CShape
{
public:
int w,h;
virtual double Area();
virtual void PrintInfo();
};
class CCircle:public CShape
{
public:
int r;
virtual double Area();
virtual void PrintInfo();
};
class CTriangle:public CShape
{
public:
int a,b,c;
virtual double Area();
virtual void PrintInfo();
};
double CRectangle::Area()
{
return w*h;
}
void CRectangle::PrintInfo()
{
cout<<"Rectangle:"<<Area()<<endl;
}
double CCircle::Area()
{
return 3.14*r*r;
}
void CCircle::PrintInfo()
{
cout<<"Circle:"<<Area()<<endl;
}
double CTriangle::Area()
{
double p=(a+b+c)/2;
return sqrt(p*(p-a)*(p-b)*(p-c));
}
void CTriangle::PrintInfo()
{
cout<<"Triangle:"<<Area()<<endl;
}
CShape* pShapes[100];
int MyCompare(const void* s1,const void* s2);
//多态的作用就是——使得可以以基类并存在一起,而且通过虚函数统一调用面积,方便排序
int main()
{
/*怎么存放不同的形体
不要放三个数组,存放不同形体
1.未来多加一个形体,就需要开多一个数组,显然是不划算的
2.需要按面积排序输出,如果这样,还需要重载不同类直接的面积比较*/
int i,n;
CRectangle* pr;
CCircle* pc;
CTriangle* pt;
cin>>n;
for(int i=0;i<n;i++)
{
char c;
cin>>c;
switch(c)
{
case 'R':
pr=new CRectangle();
cin>>pr->w>>pr->h;
pShapes[i]=pr;
break;
case 'C':
pc=new CCircle();
cin>>pc->r;
pShapes[i]=pc;
break;
case 'T':
pt=new CTriangle();
cin>>pt->a>>pt->b>>pt->c;
pShapes[i]=pt;
break;
}
}
qsort(pShapes,n,sizeof(CShape*),MyCompare);
for(int i=0;i<n;i++)
{
pShapes[i]->PrintInfo();
}
return 0;
}
int MyCompare(const void* s1,const void* s2)
{
double a1,a2;
CShape** p1;//s1和s2都是void*,不可以用*s1来取得s1指向的内容
CShape** p2;
p1=(CShape**)s1;//s1.s2指向pShapes数组中的元素,数组元素类型是CShape
p2=(CShape**)s2;//因此s1/s2都是指向指针的指针
a1=(*p1)->Area();//*p1的类型是CShape*,是基类指针,故此句为多态
a2=(*p2)->Area();
if(a1<a2)
return -1;
else if(a2<a1)
return 1;
else
return 0;
}
//这个时候,如果你想加入多一个形状,就需要加一个类,增加switch case
用基类指针数组存放指向各种派生类对象的指针,然后遍历该数组,就能对各个派生类对象做各种操作,是很常用的做法
注意程序
!!!在非构造函数,非析构函数的成员函数中调用虚函数,是多态!!!
派生类中和基类中虚函数同名同参数表的函数,不加virtual也自动称为虚函数1
多态的实现原理
关键:通过基类指针或引用调用一个虚函数时,编译时不确定到底调用的时基类还是派生类的函数,运行时才能确定,这就叫**“动态联编”**
多态的代价就是对时间和空间都有额外的开销
多态判断指向的对象是谁,利用的是,看指向的是谁的虚函数表
指针提供了一种随机访问内存的实力
虚析构函数、纯虚函数和抽象类
虚析构函数
通过基类的指针删除派生类对象时,通常情况下只调用基类的析构函数
- 但,我们需要删除派生类对象时,我们希望先调用派生类的析构函数,再调用基类的析构函数
- 问题:(在上述的程序中,利用基类指针,统一化函数,这样会使得析构可能不够完整)
解决方法:把基类的析构函数声明为virtual
- 派生类不用virtual,也是自动认为时virtual
一般来说,一个类如果定义了虚函数,则应该将析构函数也定义成虚函数
或者来说,一个类作为基类使用,也应该将析构函数定义成虚函数
不允许以虚函数作为构造函数
class son
{
public:
~son()
{
cout<<"bye from son"<<endl;
}
};
class grandson:public son
{
public:
~grandson()
{
cout<<"bye from grandson"<<endl;
}
};
int main()
{
son* pson;
pson=new grandson();
delete pson;
// >> bye from son
//只调用基类的析构函数
return 0;
}
#include<iostream>
#include<cstring>
using namespace std;
class son
{
public:
virtual ~son()
{
cout<<"bye from son"<<endl;
}
};
class grandson:public son
{
public:
~grandson()
{
cout<<"bye from grandson"<<endl;
}
};
int main()
{
son* pson;
pson=new grandson();
delete pson;
// >> bye from son
//双调用
return 0;
}
纯虚函数
定义:没有函数体的虚函数
class A
{
private:
int a;
public:
virtual void print()=0;
//这就是纯虚函数,用于比如说各种派生类的输出格式都一样的情况
void fun()
{
cout<<"fun";
}
}
抽象类
定义:包含纯虚函数的类就叫抽象类
抽象类只能作为基类来派生新的类进行使用,不能创建抽象类的对象
抽象类的指针和引用可以指向由抽象类派生出来的类的对象
就是基类指针
A a;//error A *pa;//ok,用来指向派生类 pa=new A;//error A时抽象类,只能作为基类,不能创建对象
如果一个类从抽象类中派生而来,那么只有这个类实现了基类中所有纯虚函数,它才能称为非抽象类
- 即让所有继承下来的纯虚函数,都有具体的函数体
易错
1.继承本身一个基类的对象,你的构造函数要包括基类的成员变量的构造