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(); //子类中没有该成员函数,所以调用的是基类中的

纯虚函数

引入原因:

     1、同“虚函数”;

     2、在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生

成对象明显不合常理。

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

// virtual void Eat() = 0; 直接=0 不要 在cpp中定义就可以了

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

//有的人可能在想,定义这些有什么用啊 ,我觉得很有用

//比如你想描述一些事物的属性给别人,而自己不想去实现,就可以定

//义为纯虚函数。说的再透彻一些。比如盖楼房,你是老板,你给建筑公司

//描述清楚你的楼房的特性,多少层,楼顶要有个花园什么的

//建筑公司就可以按照你的方法去实现了,如果你不说清楚这些,可能建筑

//公司不太了解你需要楼房的特性。用纯需函数就可以很好的分工合作了

 

虚函数和纯虚函数区别

观点一:

类里声明为虚函数的话,这个函数是实现的,哪怕是空实现,它的作用就是为了能让这个函数在它的子类里面可以被重载,这样的

 

话,这样编译器就可以使用后期绑定来达到多态了

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

class A{

protected:

    void foo();//普通类函数

    virtual void foo1();//虚函数

    virtual void foo2() = 0;//纯虚函数

}

观点二:

虚函数在子类里面也可以不重载的;但纯虚必须在子类去实现,这就像Java的接口一样。通常我们把很多函数加上virtual,是一

 

个好的习惯,虽然牺牲了一些性能,但是增加了面向对象的多态性,因为你很难预料到父类里面的这个函数不在子类里面不去修改

 

它的实现

观点三:

虚函数的类用于“实作继承”,继承接口的同时也继承了父类的实现。当然我们也可以完成自己的实现。纯虚函数的类用于“介面

 

继承”,主要用于通信协议方面。关注的是接口的统一性,实现由子类完成。一般来说,介面类中只有纯虚函数的。

观点四:

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

虚函数是为了继承接口和默认行为

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

 

二、补充:

   1. 虚函数和纯虚函数可以定义在同一个类(class)中,含有纯虚函数的类被称为抽象类(abstract class),而只含有虚函数

 

的类(class)不能被称为抽象类(abstract class)。

 

   2. 虚函数可以被直接使用,也可以被子类(sub class)重载以后以多态的形式调用,而纯虚函数必须在子类(sub class)

 

中实现该函数才可以使用,因为纯虚函数在基类(base class)

只有声明而没有定义。

 

   3. 虚函数和纯虚函数都可以在子类(sub class)中被重载,以多态的形式被调用。

 

   4. 虚函数和纯虚函数通常存在于抽象基类(abstract base class -ABC)之中,被继承的子类重载,目的是提供一个统一的

 

接口。

 

   5. 虚函数的定义形式:virtual    {method body} 

       纯虚函数的定义形式:virtual    { } = 0; 

      在虚函数和纯虚函数的定义中不能有static标识符,原因很简单,被static修饰的函数在编译时候要求前期bind,然而虚函

 

数却是动态绑定(run-time bind),而且被两者修饰的函数生命周期(life recycle)也不一样。

   6. 如果一个类中含有纯虚函数,那么任何试图对该类进行实例化的语句都将导致错误的产生,因为抽象基类(ABC)是不能被

 

直接调用的。必须被子类继承重载以后,根据要求调用其子类的方法。

 

三、什么是虚函数表?

 

      虚函数是如何做到的(如果你没有看过《Inside The C++ Object Model》这本书,但又急切想知道,那你就应该从这里开始)

  虚函数是如何做到因对象的不同而调用其相应的函数的呢?现在我们就来剖析虚函数。我们先定义两个类

  class A{ //虚函数示例代码

  public:

  virtual void fun(){cout<<1<<endl;}

  virtual void fun2(){cout<<2<<endl;}

  };

  class B:public A{

  public:

  void fun(){cout<<3<<endl;}

  void fun2(){cout<<4<<endl;}

  };

  由于这两个类中有虚函数存在,所以编译器就会为他们两个分别插入一段你不知道的数据,并为他们分别创建一个表。那段数据叫做vptr指针,指向那个表。那个表叫做vtbl,每个类都有自己的vtbl,vtbl的作用就是保存自己类中虚函数的地址,我们可以把vtbl形象地看成一个数组,这个数组的每个元素存放的就是虚函数的地址,请看图

 


  通过上图,可以看到这两个vtbl分别为class A和class B服务。现在有了这个模型之后,我们来分析下面的代码
  A *p=new A;
  p->fun();

  毫无疑问,调用了A::fun(),但是A::fun()是如何被调用的呢?它像普通函数那样直接跳转到函数的代码处吗?No,其实是这样的,首先是取出vptr的值,这个值就是vtbl的地址,再根据这个值来到vtbl这里,由于调用的函数A::fun()是第一个虚函数,所以取出vtbl第一个slot里的值,这个值就是A::fun()的地址了,最后调用这个函数。现在我们可以看出来了,只要vptr不同,指向的vtbl就不同,而不同的vtbl里装着对应类的虚函数地址,所以这样虚函数就可以完成它的任务。

  而对于class A和class B来说,他们的vptr指针存放在何处呢?其实这个指针就放在他们各自的实例对象里。由于class A和class B都没有数据成员,所以他们的实例对象里就只有一个vptr指针。

 

转载自:http://www.softhouse.com.cn/news/show/8695.html

            http://www.91linux.com/html/article/program/java/20090313/16081.html

            http://baike.baidu.com/view/161302.htm

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值