C++的精髓—虚函数

  虚函数是为了重载和多态的需要,在基类中是有定义的,即便定义是空,所以子类中可以重写也可以不写基类中的函数!

  纯虚函数在基类中是没有定义的,必须在子类中加以实现,很像java中的接口函数!

虚函数

引入原因:为了方便使用多态性,我们常常需要在基类中定义虚函数。

class CMan
{
public:
    virtual void Eat(){......};
    void Move();
private:
};

class CChild:public CMan
{
public:
    virtual void Eat(){......};
private:
};
CMan m_man;
CChild m_child;
//这才是使用的精髓,**如果不定义基类的指针去使用,没有太大的意义**
CMan * p;
p = &m_man;
p->Eat(); //始终调用CMan的Eat成员函数,不会调用CChild的
p = &m_child;
p->Eat(); //如果子类实现(覆盖)了该方法,则始终调用CChild的Eat函数
//不会调用CMan的Eat方法,如果子类没有实现该函数,则调用CMan的Eat函数
p->Move(); //子类中没有改成员函数,所以调用的是基类中的

注:指针的类型
看下面的例子:

class ZooAnimal{
public:
    ZooAnimal();
    virtual ~ZooAnimal();
    //...
    virtual void rotate();

protected:
    int loc;
    String name;
};

class Bear : public ZooAnimal{
public:
    Bear();
    ~Bear();
    //...
    void rotate();
    virtual void dance();
    //...
protected:
    enum Dances {...};

    Dances dances_known;
    int cell_block;
};

好了, 在主函数中我们有如下的定义:

Bear b;
ZooAnimal* pz = &b;
Bear* pb = &b;

它们每个都指向Bear object的第一个byte。其间的差别是,pb所涵盖的地址包含整个Bear object,而pz所涵盖的地址只包含Bear object中的ZooAnimal subobject。

除了ZooAnimal subobject中出现的members,你不能够使用pz来直接处理Bear中的任何members。唯一例外是通过virtual机制(也就是说pz不能够访问Bear中的非虚函数以及非ZooAnimal中的数据成员):

// 不合法:cell_block不是ZooAnimal的一个member,
// 虽然我们知道pz目前指向一个Bear object
pz->cell_block;

// ok:经过一个显示的downcast操作就没问题!
(static_cast<Bear*>(pz))->cell_block;

// 下面这样更好,但它是一个run—time operation(译注:成本较高)
Bear* pb2 = dynamic_cast<Bear*>(pz);
pb2->cell_block;

// ok:因为cell_block是Bear的一个member
pb->cell_block;

纯虚函数

引入原因:
  1. 同“虚函数”;
  2. 在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。

  // 纯虚函数就是基类只定义了函数体,没有实现过程定义方法如下

// virtual void Eat() = 0;

  // 纯虚函数相当于接口,不能直接实例化,需要派生类来实现函数定义

虚函数和纯虚函数区别

观点一:
  类里声明为虚函数的话,这个函数时实现的,哪怕是空实现,它的作用就是为了能让这个函数在它的子类里面可以被重载,这样的话,编译器就可以了使用后期绑定来达到多态了

  纯虚函数只是一个接口,是个函数的声明而已,它要留到子类里面去实现。

class A
{
protected:
    void foo(); // 普通类函数
    virtual void foo1(); // 虚函数
    virtual void foo2() = 0; // 纯虚函数
};

观点二:

  虚函数在子类里面也可以不重载的;但纯虚函数必须在子类中去实现。通常我们把很多函数加上virtual是一个好的习惯,虽然牺牲了一些性能,但是增加了面向对象的多态性。

观点三:

  虚函数的类用于“实作继承”,继承接口的同时也继承了父类的实现。当然我们也可以自己完成实现。纯虚函数的类用于“介面继承”,主要用于通信协议方面。关注的是接口的统一性,实现由子类完成。一般来说,介面类中只有纯虚函数的。

观点四:

  错误: 带纯虚函数的类叫虚基类,这种基类不能直接生成对象,而只有被继承,并重写其虚函数,才能使用。这样的类也叫抽象类。

虚函数是为了继承接口和默认行为
纯虚函数只是继承接口,行为必须重新定义

  虚基类的初始化 虚基类的初始化与一般多继承的初始化在语法上是一样的,但构造函数的调用次序不同

  派生类构造函数的调用次序有三个原则:

  1. 虚基类的构造函数在非虚基类之前调用;
  2. 若同一层次中包含多个虚基类,这些虚基类的构造函数按它们说明的次序调用;
  3. 若虚基类由非虚基类派生而来,则仍先调用基类构造函数,再调用派生类的构造函数

C++的虚基类
  在派生类继承基类时,加上一个virtual关键词则为虚拟基类继承,如:

class derive:virtual public base
{};

  虚基类主要解决在多重继承时,基类可能被多次继承,虚基类主要提供一个基类给派生类,如:

class B
{};
class D1:public B
{};
class D2:public B
{};
class C:public D1,public D2
{};

  这里C在D1、D2上继承,但有两个基类,造成混乱。因而使用虚基类,即:

class B
{};
class D1:virtual public B
{};
class D2:virtual public B
{};
class C:public D1,public D2

  在使用虚基类时要注意:
  1. 一个类可以在一个类族中既被用作虚基类,也被用作非虚基类。
  2. 在派生类的对象中,同名的虚基类只产生一个虚基类子对象,而某个非虚基类产生各自的子对象。
  3. 虚基类子对象是由最远派生类的构造函数通过调用虚基类的构造函数进行初始化的。
  4. 最远派生类是指在继承结构中建立对象时所指定的类。
  5. 派生类的构造函数的成员初始化列表中必须列出对虚基类构造函数的调用;如果未列出,则表示使用该虚基类的缺省构造函数。
  6. 从虚基类直接或间接派生的派生类中的构造函数额成员初始化列表中都要列出对虚基类构造函数的调用。但仅仅用建立对象的最远派生类的构造函数调用虚基类的构造函数,而该派生类的所有基类中列出的对虚基类的构造函数的调用在执行中被忽略,从而保证对虚基类子对象 只初始化一次。
  7. 在一个成员初始化列表中同时出现对虚基类和非虚基类构造函数的调用时,虚基类的构造函数先于非虚基类的构造函数执行。

静态联编:在程序链接阶段就可以确定的调用。
动态联编:在程序 执行时才能确定的调用。

相关文章:C++虚函数使用方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值