C++程序设计 - Week 6 多态与虚函数

虚函数和多态

class base { 
    virtual int get() ; 
}; 
int base::get() { }

virtual 关键字只用在类定义里的函数声明中,写函数体时不用。

构造函数和静态成员函数不能是虚函数

  • 多态的表现形式一
    派生类的指针可以赋给基类指针,通过基类指针调用基类和派生类中的同名虚函数时
    若该指针指向一个基类的对象,那么被调用的是基类的虚函数;若该指针指向一个派生类的对象,那么被调用的是派生类的虚函数。这种机制就叫做多态。

  • 多态的表现形式二
    派生类的对象可以赋给基类引用。若该引用引用的是一个基类的对象,那么被调用是基类的虚函数;若该引用引用的是一个派生类的对象,那么被调用的是派生类的虚函数。

在面向对象的程序设计中使用多态,能够增强程序的可扩充性,即程序需要修改或增加功能的时候,需要改动和增加的代码较少。

使用多态的游戏程序实例

为每个怪物类编写 Attack、FightBack和 Hurted成员函数。

Attact函数表现攻击动作,攻击某个怪物,并调用被攻击怪物的Hurted函数,以减少被攻击怪物的生命值,同时也调用被攻击怪物的FightBack成员函数,遭受被攻击怪物反击。

Hurted函数减少自身生命值,并表现受伤动作。

FightBack成员函数表现反击动作,并调用被反击对象的Hurted成员函数,使被反击对象受伤。

设置基类 CCreature,并且使CDragon, CWolf等其他类都从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);
};

void CDragon::Attack(CCreature * p)
{ // …表现攻击动作的代码
    p->Hurted(m_nPower); //多态
    p->FightBack(this); //多态
}

更多多态程序实例

几何形体处理程序

注意MyCompare函数的实现

class CShape
{
public:
    virtual double Area() = 0; //纯虚函数
    virtual void PrintInfo() = 0;
};

qsort(pShapes, n, sizeof( CShape*), MyCompare);

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; // 故 p1,p2都是指向指针的指针,类型为 CShape **
    a1 = (*p1)->Area(); // * p1 的类型是 Cshape * ,是基类指针,故此句为多态
    a2 = (*p2)->Area();
    if( a1 < a2 )
        return -1;
    else if ( a2 < a1 )
        return 1;
    else
        return 0;
}

用基类指针数组存放指向各种派生类对象的指针,然后遍历该数组,就能对各个派生类对象做各种操作,是很常用的做法。

在非构造函数,非析构函数的成员函数中调用虚函数,是多态!!!

在构造函数和析构函数中调用虚函数,不是多态。编译时即可确定,调用的函数是自己的类或基类中定义的函数,不会等到运行时才决定调用自己的还是派生类的函数。(构造函数中,基类先调用,而派生类的函数可能还没有初始化)

派生类中和基类中虚函数同名同参数表的函数,不加virtual也自动成为虚函数

多态实现原理

“多态”的关键在于通过基类指针或引用调用一个虚函数时,编译时不确定到底调用的是基类还是派生类的函数,运行时才确定 —- 这叫“动态联编”

class Base {
private:
    int i; // 4 Bytes
public:
    virtual void Print(); // 4 Bytes
};
sizeof(Base) = 8;

每一个有虚函数的类(或有虚函数的类的派生类)都有一个虚函数表,该类的任何对象中都放着虚函数表的指针。虚函数表中列出了该类的虚函数地址。多出来的4个字节就是用来放虚函数表的地址的。

多态的函数调用语句被编译成一系列根据基类指针所指向的(或基类引用所引用的)对象中存放的虚函数表的地址,在虚函数表中查找虚函数地址,并调用虚函数的指令。

虚析构函数

通过基类的指针删除派生类对象时,只调用基类的析构函数

删除一个派生类的对象时,先调用派生类的析构函数,再调用基类的析构函数

把基类的析构函数声明为virtual,派生类的析构函数 virtual可以不进行声明

类如果定义了虚函数, 则最好将析构函数也定义成虚函数

纯虚函数和抽象类

纯虚函数: 没有函数体的虚函数

class A {
private:
    int a;
public:
    virtual void Print() = 0 ; //纯虚函数 
    void fun() { std::cout << “fun”; }
};

抽象类: 包含纯虚函数的类。只能作为基类来派生新类使用,不能创建抽象类的对象

抽象类的指针和引用,由抽象类派生出来的类的对象

抽象类中,在成员函数内可以调用纯虚函数在构造函数/析构函数内部不能调用纯虚函数

如果一个类从抽象类派生而来,它实现了基类中的所有纯虚函数,才能成为非抽象类

Question

类型大小?
依旧有虚函数表。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值