C++学习笔记——多态

虚函数

在类的定义中,前面有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是抽象类,不能创建对象
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值