文章目录
虚函数
在类的定义中,前面有virtual关键字的成员函数就是虚函数。
class base{
virtual int get();
};
int base::get() {}
virtual关键字只用在类定义里的函数声明中,写函数体时不用。
构造函数和静态成员函数不能是虚函数。
虚函数可以参与多态,普通成员函数不能。
多态
多态的表现形式一——基类指针
· 派生类的指针可以赋给基类指针。
· 通过基类指针调用基类和派生类中的同名虚函数时:
(1)若该指针指向一个基类的对象,那么被调用是基类的虚函数;
(2)若该指针指向一个派生类的对象,那么被调用的是派生类的虚函数。
这种机制叫做多态。
class CBase{
public:
virtual void SomeVirtualFunction() {}
};
class CDerived: public CBase{
public:
virtual void SomeVirtualFunction() {}
};
int main()
{
CDerived ODerived;
CBase *p = &ODerived; // 基类指针p指向派生类对象ODerived
p->SomeVirtualFunction(); // 调用哪个虚函数取决于p指向哪种类型的对象
// 这里执行派生类CDerived的SomeVirtualFunction
return 0;
}
多态的表现形式二——基类引用
· 派生类的对象可以赋给基类引用
· 通过基类引用调用基类和派生类中的同名虚函数时:
(1)若该引用引用的是一个基类的对象,那么被调用的是基类的虚函数;
(2)若该引用引用的是一个派生类的对象,那么被调用的是派生类的虚函数。
这种机制也叫做多态。
class CBase{
public:
virtual void SomeVirtualFunction() {}
};
class CDerived: public CBase{
public:
virtual void SomeVirtualFunction() {}
};
int main()
{
CDerived ODerived;
CBase &r = ODerived; // 基类引用p引用派生类对象ODerived
r.SomeVirtualFunction();
// 因为这个函数是虚函数,基类引用p引用了派生类对象ODerived
// 所以执行的是派生类ODerived的SomeVirtualFunction
// 如果这个函数不是虚函数,那么这里执行的就是基类的SomeVirtualFunction
return 0;
}
多态的简单示例:
class A{
public:
virtual void Print() { cout<<"A::Print"<<endl; }
};
class B:public A{
public:
virtual void Print() { cout<<"B::Print"<<endl; }
};
Class D:public A{
public:
virtual void Print() { cout<<"D::Print"<<endl; }
};
Class E:public B{
public:
virtual void Print() { cout<<"E::Print"<<endl; }
};
int main()
{
A a; B b; E e; D d;
A *pa=&a; B *pb=&b;
D *pd=&d; E *pe=&e;
pa->Print(); // a.Print()被调用,输出:A::Print
pa=pb;
pa->Print(); // b.Print()被调用,输出:B::Print
pa=pd;
pa->Print(); // d.Print()被调用,输出:D::Print
pa=pe;
pa->Print(); // e.Print()被调用,输出:E::Print
return 0;
}
多态的作用
多态能够增强程序的可扩充性,即程序需要修改或增加功能的时候,需要改动和增加的代码减少。
多态实例:魔法门之英雄无敌
非多态的实现方法
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);
}
void Hurted(int nPower){
nLifeValue-=nPower;
}
void FightBack(CWolf* pWolf){
pWolf->Hurted(nPower/2);
}
void FightBack(CGhost* pGhost){
pGhost->Hurted(nPower/2);
}
}
有n种怪物,CDragon类中就会有n个Attack成员函数,以及n个FightBack成员函数,对于其他类也是如此。
缺点:若增加新怪雷鸟CThunderBird,则程序改动较大。
所有类都需要增加两个成员函数:
void Attack(CThunderBird* pThunderBird);
void FightBack(CThunderBird* pThunderBird);
多态的实现方法
// 基类CCreature
class CCreature
{
protected:
int m_nLifeValue, m_nPower;
public:
virtual void Attack(CCreature *pCreature) {}
virtual void Hurted(int nPower) {}
virtual void FightBack(CCreature **pCreature) {}
};
// 派生类CDragon
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);
}
如果增加新怪雷鸟,只需要编写新类CThunderBird。
原理:
int main()
{
CDragon Dragon;
CWolf Wolf;
CGhost Ghost;
Dragon.Attack(&Wolf); //1
Dragon.Attack(&Ghost); //2
Dragon.Attack(&Bird); //3
}
根据多态的规则,上面的1、2、3进入到CDragon::Attack函数后,能分别调用:
CWolf::Hurted
CGhost::Hurted
CBird::Hurted
void CDragon::Attack(CCreature *p)
// 参数p是一个基类的指针,传进去的是派生类的指针(派生类的指针可以赋值给基类的对象)
{
// p是基类的指针,Hurted是基类和派生类都有的同名虚函数->多态
// p指向哪个类的对象,Hurted就是哪个类的Hurted
p->Hurted(m_nPower);
// this指向CDragon类的对象
p->FightBack(this);
}
如执行Dragon.Attack(&Wolf);
传递的指针指向派生类CWolf的对象Wolf,那么其中p->Hurted(m_nPower)
执行Wolf的Hurted()函数,即Wolf受到m_nPower点伤害,m_nPower是Dragon的。
然后执行p->FightBack(this);
,执行Wolf的FightBack()函数,传递的this为Dragon对象,即Wolf对Dragon进行反击。而对于Wolf的FightBack:
void CWolf::FightBack(CCreature *p)
{
p->Hurted(m_nPower/2);
}
传入指向Dragon的指针,即此时的p为Dragon对象,执行Dragon对象的Hurted()函数,而传入的m_nPower
是Wolf对象的。
多态实例:几何形体程序
#include<iostream>
#include<stdlib.h>
#include<math.h>
using namespace std;
class CShape
{
public:
virtual double Area()=0;
virtual void PrintInfo()=0;
};
多态的实现原理
动态联编:多态的关键在于通过基类指针或引用调用一个虚函数时,编译时不确定到底调用的是基类还是派生类的函数,运行时才确定。
class Base{
public:
int i;
virtual void Print() { cout<<"Base:Print"; }
};
class Derived: public Base{
public:
int n;
virtual void Print() { cout<<"Drived:Print"<<endl; }
};
int main()
{
Derived d;
cout<<sizeof(Base)<<","<<sizeof(Derived);
return 0;
}
// 结果: 8,12
// 多出来4个字节?
多态实现的关键——虚函数表
每一个有虚函数的类(或有虚函数的类的派生类)都有一个虚函数表,该类的任何对象中都放着虚函数表的指针。虚函数表中列出了该类的虚函数地址。多来的4个字节就是用来放虚函数表的地址的。
如:
pBase=pDerived;
pBase->Print();
通过基类指针pBase调用虚函数,多态的函数调用语句被编译成一系列根据基类指针所指向的(或基类引用所引用的)对象中存放的虚函数表的地址,在虚函数表中查找虚函数地址,并调用虚函数的指令。
多态的代价
多态的程序在运行上会有额外的时间和空间的开销。即每一个有虚函数的类的对象里,会多4个字节,存放虚函数表的地址。
虚析构函数
通过基类的指针删除派生类对象时,通常情况下只调用基类的析构函数。
但是,删除一个派生类的对象时,应该先调用派生类的析构函数,然后调用基类的析构函数。
把基类的析构函数声明为virtual
派生类的析构函数可以virtual不进行声明
通过基类的指针删除派生类对象时,首先调用派生类的析构函数,然后调用基类的析构函数
一般来说,一个类如果定义了虚函数,则应该将析构函数也定义成虚函数。或者,一个类打算作为基类使用,也应该将析构函数定义成虚函数。
注意:不允许以虚函数作为构造函数
纯虚函数和抽象类
纯虚函数:没有函数体的虚函数
class A{
private:
int a;
public:
virtual void Print()=0;
void fun() { cout<<"fun"; }
}
抽象类:包含纯虚函数的类
抽象类只能作为基类来派生新生类使用,不能创建抽象类的对象
抽象类的指针和引用可以指向由抽象类派生出来的类的对象
A a; // 错,A是抽象类,不能创建对象
A *pa; // 对,可以定义抽象类的指针和引用
pa = new A; // 错,A是抽象类,不能创建对象