实验五第1题
实验五 虚函数表及其多态性的实现
一.实验目的
1.理解并掌握虚函数表的内部实现机制及其遍历方法;
2.理解并掌握利用虚函数表实现多态性的原理和使用方法;
3.理解并掌握虚析构函数的原理和使用方法;
二. 实验内容
1.理解并掌握虚函数表的内部实现机制及其遍历方法
第1题:下面是一个通过类的实例得到该类虚函数表的程序,请根据程序中注释的要求,完善该程序。
#include <iostream>
using namespace std;
class Base {
public:
virtual void f() { cout << "Base::f" << endl; }
virtual void g() { cout << "Base::g" << endl; }
virtual void h() { cout << "Base::h" << endl; }};
typedef void(*Fun)(void); //定义一个函数指针类型Fun
int main(){
Base b;
Fun pFun = NULL;
cout << "虚函数表地址:" <<* (long*)(&b) << endl;
cout << "虚函数表中— 第一个虚函数地址:" << *(long*)*(long*)(&b) << endl;
// Invoke the first virtual function
pFun = (Fun)*((long*)*(long*)(&b));
//cout <<(long*)pFun<<endl;
pFun();
//请仿造上面的方法,改动程序相应的语句,能够分别输出Base::g()和Base::h()的函数地址。
// (Fun)*((int*)*(int*)(&b)+0); // Base::f() 32位计算机
// (Fun)*((int*)*(int*)(&b)+1); // Base::g() 32位计算机
// (Fun)*((int*)*(int*)(&b)+2); // Base::h() 32位计算机
}
- 请给出程序完整源代码,并粘贴程序运行结果截图。
#include <iostream>
using namespace std;
class Base {
public:
virtual void f() { cout << "Base::f" << endl; }
virtual void g() { cout << "Base::g" << endl; }
virtual void h() { cout << "Base::h" << endl; }
};
typedef void(*Fun)(void); //定义一个函数指针类型Fun
int main() {
Base b;
Fun pFun = NULL;
cout << "虚指针的值,即:虚函数表地址:" << (long*)(*(long*)(&b)) << endl << endl;
cout << "虚表vptr[0]的内容,即:虚函数表 — 第1个虚函数f()地址:" << (long*)(*((long*)(*(long*)(&b)) + 0)) << endl;
cout << "虚表vptr[1]的内容,即:虚函数表 — 第2个虚函数f()地址:" << (long*)(*((long*)(*(long*)(&b)) + 2)) << endl; //绿色部分为虚指针
cout << "虚表vptr[2]的内容,即:虚函数表 — 第3个虚函数f()地址:" << (long*)(*((long*)(*(long*)(&b)) + 4)) << endl << endl;
//也可以用给函数指针赋值的方法
pFun = (Fun)(*(long*)(*(long*)(&b)));
cout << "虚函数表 — 第1个函数f()地址:" << (long*)pFun << endl;
pFun();
pFun = (Fun)(*((long*)(*(long*)(&b)) + 2));
cout << "虚函数表 — 第2个函数g()地址:" << (long*)pFun << endl;
pFun();
pFun = (Fun)(*((long*)(*(long*)(&b)) + 4));
cout << "虚函数表 — 第3个函数h()地址:" << (long*)pFun << endl;
pFun();
system("pause");
}
(2) 请根据程序运行结果,画出Base类虚函数表的示意图。
作业4
一、从虚函数的内部实现机制,理解利用虚函数实现的C++的多态性(动态绑定)
- 请阅读下列程序,然后回答问题:
#include <iostream>
using namespace std;
class Base1 { //基类Base1定义
public:
virtual void display() const; //虚函数
};
void Base1::display() const {
cout << "Base1::display()" << endl;
}
class Base2:public Base1 { //公有派生类Base2定义
public:
void display() const; //覆盖基类的虚函数
};
void Base2::display() const {
cout << "Base2::display()" << endl;
}
class Derived: public Base2 { //公有派生类
public:
void display() const; //覆盖基类的虚函数
};
void Derived::display() const {
cout << "Derived::display()" << endl;
}
void fun(Base1 *ptr) { //参数为指向基类对象的指针
ptr->display(); //"对象指针->成员名"
}
int main() { //主函数
Base1 base1; //定义Base1类对象
Base2 base2; //定义Base2类对象
Derived derived; //定义Derived类对象
//cout<<sizeof(base1)<<endl;
//cout<<sizeof(base2)<<endl;
//cout<<sizeof(derived)<<endl;
fun(&base1);//用Base1对象的指针调用fun函数
fun(&base2);//用Base2对象的指针调用fun函数
fun(&derived);//用Derived对象的指针调用fun函数
return 0;
}
- 写出上面程序的运行结果
- 画出上面程序创建的base1,base2,derived 三个对象的内存结构及各自的虚函数指针,虚函数表和装入内存的Base1::display(),Base2::display(),Derived::display()的相互关系。
- 上面程序中哪个语句体现了c++的多态性?是如何体现的?
- 根据对上述程序的理解,请给出实现c++的多态性,需要具备哪些条件?
答:(1)
(2)
(3)void fun(Base1 *ptr)语句体现了程序的多态性。根据C++的类型转换规则,基类指针可以指向派生类对象,当基类指针ptr 指向派生类对象base2时,程序执行派生类对象base2的类Base2对 display()的重写函数:Base2:: display(),当基类指针ptr 指向派生类对象derived时,程序执行派生类对象derived的类Derived对 display()的重写函数:Derived:: display(),从而利用虚函数实现了C++的多态性。
(4) 需要具备三个条件:有继承;有虚函数重写;用父类指针(父类引用)指向子类对象。
二.从开发实际应用的功能含义方面,理解C++虚函数实现多态的灵活性和可扩展性
1. 下面的程序是一款打斗小游戏中类设计的框架:
- 游戏描述如下:
(1)游戏有骑士、天使、狼、鬼等多种角色;
(2) 每种角色都有生命力、攻击力两种属性;
(3)每种角色都有攻击,受伤,反击三种行为;
(4)关于攻击行为:当一个角色攻击另一个角色时,被攻击者会受伤;同时,被攻击者会反击;
(5)关于受伤行为:攻击者生命值减半(具体释义见代码段中的注释);
(6)关于反击行为:每一种角色反击的力量较弱,只是其自身攻击力的 1/2(具体释义见代码段中的注释);
- 类设计框架如下:(请忽略代码中因word格式,造成的偶尔首字母大小写不符合语法的问题)
class CDragon
{
private:
int power; //攻击力
int lifeValue; //生命值
public:
void Attack(CWolf * p); //攻击“狼”的成员函数
void Attack(CGhost* p); //攻击“鬼”的成员函数
//……其他 Attack 重载函数
//表现受伤的成员函数
void Hurted(int nPower);
void FightBack(CWolf * p); //反击“狼”的成员函数
void FightBack(CGhost* p); //反击“鬼”的成员函数
//......其他FightBack重载函数
};
各成员函数如下:
void CDragon::Attack(CWolf* p) // 参数 p 指向被攻击的 CWolf 对象
{
p->Hurted(power); // 在 p 所指向的对象上执行 Hurted 成员函数,使被攻击的“狼”对象受伤;调用 Hurted 成员函数时,参数是攻击者“龙”对象的攻击力
p->FightBack(this); //以指向攻击者自身的 this 指针为参数,调用被攻击者的 FightBack 成员函数,接受被攻击者的反击
}
void CDragon::Attack(CGhost* p)
{
p->Hurted(power);
p->FightBack(this);
}
void CDragon::Hurted(int nPower)
{
lifeValue -= nPower;
}
void CDragon::FightBack(CWolf* p) // 一个对象的 Hurted 成员函数被调用,会导致该对象的生命值减少,减少的量等于攻击者的攻击力;在真实的程序中,Hurted 成员函数还应包含表现受伤时动作的代码,以及生命值如果减至小于或等于零,则倒地死去的代码。
{
p->Hurted(power / 2);
}
void CDragon::FightBack(CGhost* p)// p 指向的是实施攻击者;对攻击者进行反击,实际上就是调用攻击者的 Hurted 成员函数使其受伤;其受到的伤害的大小等于实施反击者的攻击力的一半(反击的力量不如主动攻击大);FightBack 成员函数中其实也应包含表现反击动作的代码。
{
p->Hurted(power / 2);
}
- 上述游戏将每一种角色设计为一个类,一个角色就是该类的一个对象,没有应用继承和多态特性,针对上述设计,请回答以下问题:
(1)上述游戏中如果有 n 种角色,CDragon 类中要有多少个Attack 成员函数用于攻击 n 种角色?要有多少个 FightBack 成员函数(以上两个问题,假设两条龙也能互相攻击)?对于其他类,是不是也是如此呢?
答:CDragon 类中要有n个Attack 成员函数,要有n个FightBack 成员函数,对于其他类,也是如此。
(2)现应游戏版本升级的需要,将增加新的角色“雷鸟”,假设其类名为 CThunderBird,那么,在现有的类框架下(没有应用继承和多态),程序需要做哪些改动呢?如果在角色类很多的情况下,这种设计会有什么弊端?
答:除了需要编写一个 CThiinderBird 类外,所有的类都需要增加Attack 成员函数和FightBack 成员函数,用以对“雷鸟”实施攻击,以及在被“雷鸟”攻击时对其进行反击。
void Attack(CThunderBird* p);
void FightBack(CThunderBird* p);
弊端是:在角色种类很多的情况下,工作量会增加很多。
(3) 如果应用继承改进上述的设计:将 CDragon、CWolf 等类的共同特点抽取出来,形成一个 CCreature 类,然后再从 CCreature 类派生出 CDragon、CWolf 等类,这样的改进是否能够明显精简上面的代码设计?请说明原因。
答:不能。因为,每种角色进行攻击、反击和受伤时的表现动作各不相同,每一种角色,例如: CDmgon、CWdf 这些类还要实现各自的 Hurted 成员函数,以及一系列 Attack、FightBack 成员函数。所以,即使抽象出各种角色的父类,但是各个类共同的行为(抽象为:父类的Public成员函数)有限,每个类仍然需要编写大量自己的成员函数,以实现各自类自己的行为;因此,对本游戏的设计,引人基类 CCreature,并不能从根本上精简程序代码。
(4)引入多态机制能够完美解决上述游戏的功能扩充问题,大大精简之前的设计代码,下面是应用多态后的改进代码,请在此基础上,编写新角色“雷鸟”类CThunderBird的设计。
class CCreature {
//设置一个基类 CCreature,概括所有角色的共同特点;所有具体的角色类,如 CDragon、CWolf、CGhost 等,均从 CCreature 类派生而来
protected:
int lifeValue, power;
public:
virtual void Attack(CCreature* p) {};
virtual void Hurted(int nPower) {};
virtual void FightBack(CCreature* p) {};
};
class CDragon : public CCreature
{
public:
virtual void Attack(CCreature* p) {
p->Hurted(power);
p->FightBack(this);
}
virtual int Hurted(int nPower) {
lifeValue -= nPower;
}
virtual int FightBack(CCreature* p) {
p->Hurted(power / 2);
}
};
答:CThunderBird 类的设计,不需要在已有的各个类中专门为新角色增加 void Attack(CThunderBird * p) 和 void FightBack(CThunderBird* p)这两个成员函数,即:其他类不用做任何修改,只需要编写新类 CThunderBird 即可。新类 CthunderBird的设计同Cdragon类的设计,只需将Cdragon类设计中的dragon替换为ThunderBird即可。代码(略)
(5)根据下面主函数中的代码,请指出dragon实现了对哪几种动物的攻击?并结合语句,说明dragon是如何实现对这些动物的攻击的?
int main(){
CDragon dragon;
CWolf wolf;
CGhost ghost;
CThunderBird bird;
Dragon.Attack(&wolf);
Dragon.Attack(&ghost);
Dragon.Attack(&bird);}
答:dragon实现了对三个角色:wolf,ghost和bird的攻击。根据赋值兼容规则,上面第 5、6、7 行中的参数都与基类指针类型 CCreature* 相匹配,系统自动将基类引用转为对派生类wolf,ghost和bird的引用,从 5、6、7 三行进入 CDragon::Attack 函数后,执行 p-> Hurted(power) 语句时,p 分别指向的是 wolf、ghost 和 bird,根据对多态虚函数指针的调用,分别调用的就是 CWolf::Hurted、CGhost::Hurted 和 CBird: Hurted 函数,从而实现了对wolf,ghost和bird的攻击。
(6)如果要实现对其他动物,例如:fox,tiger的攻击,上述程序需要做怎样的改动?
答:需要增加Fox 类和Tiger类,并在主函数中增加如下的语句:
CFox fox;
CTiger tiger;
fox.Attack(&wolf);
tiger.Attack(&ghost);
(7)经过上述的思考和分析,请说明改进后的游戏代码,是如何实现多态的?并总结多态的一般性实现方法。
答:首先,以基类的虚函数virtual void Ccreature:: Attack(CCreature* p) {};为入口函数,各个派生类Dragon、 Wolf、Ghost 和Bird通过重写基类的虚函数,从而在各自类的对象中获得指向自己类虚函数的虚指针,那么,当父类的指针指向该派生类时,调用的就是该派生类的虚函数;如果有多个派生类,则实现了多态。
多态的一般性实现方法:基类设有虚函数,各个派生类重写该虚函数,当主函数中,父类的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。