Chinaunix首页 | 论坛 | 博客
  • 博客访问: 616046
  • 博文数量: 201
  • 博客积分: 3076
  • 博客等级: 中校
  • 技术积分: 2333
  • 用 户 组: 普通用户
  • 注册时间: 2009-08-02 19:44
文章分类

全部博文(201)

文章存档

2010年(118)

2009年(83)

我的朋友

分类: C/C++

2010-04-12 22:38:01

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

   2. 虚函数可以被直接使用,也可以被子类(sub class)重载以后以多态的形式调用,而纯虚函数必须在子类(sub class)中实现该函数才可以使用,因为纯虚函数在基类(base class)只有声明而没有定义,如果定义会报错。虚函数必须在父类中也定义,不能只声明。

 note: 基类的纯虚函数也可以实现,但必须是在类外定义,类中声明。

http://www.cnblogs.com/chio/archive/2007/09/10/888260.html

~bs/bs_faq2.html#pure-virtual


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

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

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

       virtual 不能少。
      在虚函数和纯虚函数的定义中不能有static标识符,原因很简单,被static修饰的函数在编译时候要求前期bind,然而虚函数却是动态绑定 (run-time bind),而且被两者修饰的函数生命周期(life recycle)也不一样。
   6. 如果一个类中含有纯虚函数,那么任何试图对该类进行实例化的语句都将导致错误的产生,因为抽象基类(ABC)是不能被直接调用的。必须被子类继承重载以后,根据要求调用其子类的方法。

------

虚函数为了重载和多态的需要,在基类中是有定义的,即便定义是空,所以子类中可以重写也可以不写基类中的函数!纯虚函数在基类中是没有定义的,必须在子类中加以实现,很像java 中的接口函数!

-----

父类Animal(虚函数):

class Animal{
public:
 
virtual void Eat()
 {
  cout << "Eat Something" << endl;
 }
};

子类Cat:

class Cat: public Animal{
public:
 virtual void Eat()   //此处去掉virtual会有同样的结果,但把父类的去掉就会不同
 {
  cout << "Eat Mouse" << endl;
 }
};

主函数使用:

Animal One; One.Eat();
Cat Two; Two.Eat();
Animal *Three = new Cat; Three->Eat(); delete Three;

三次输出结果为:

Eat Something
Eat Mouse
Eat Mouse

说明用动态分配的Cat是实际的Cat,即使指针为Animal *Three。那么为什么要引入纯虚函数呢?

     1、同“虚函数”,也就是说在Animal的Eat函数后加入“=0”后运行结果一样

 2、 在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出猫、老鼠等子类,但动物本身生成对象明显不合常理,它只是一个抽像的概 念,是一个纯虚的事物!

 3、纯虚函数不能实例化一个对象,而子类必须去实现里面的功能。只能通过“父类 *a = new 子类”这种方式使用。

总结一下(从网上查看到的):

一:

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

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

class A{

protected:

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

    virtual void foo1();//虚函数

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

}

观点 二:

虚函数在子类里面也可以不重载的;但纯虚必须在子类去实现,这就像Java的接口一样。通常 我们把很多函数加上virtual,是一个好的习惯,虽然牺牲了一些性能,但是增加了面向对象的多态性,因为你很难预料到父类里面的这个函数不在子类里面 不去修改它的实现

观点 三:

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

观点 四:

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

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

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

------

class awov {
public:
  virtual ~awov() = 0;      // 声明一个纯虚析构函数
};

      这个类有一个纯虚函数,所以它是抽象的,而且它有一个虚析构函数,所以不会产生析构函数问题。但这里还有一件事:必须提供纯虚析构函数的定义

awov::~awov() {}           // 纯虚析构函数的定义

这个定义是必需的,因为虚析构函数工作的方式是:最底层的派生类的析构函数最先被调用,然后各个基类的析构函数被调用。这就是说,即使是抽象类,编 译器也要产生对~awov的调用,所以要保证为它提供函数体。如果不这么做,链接器就会检测出来,最后还是得回去把它添上。


----

http://blog.csdn.net/lyflower/archive/2009/05/07/4157987.aspx

如果在一个类中声明了纯虚函数,而在其派生类中没有对该函数定义,则该函数在派生类中仍为纯虚函数。

【6】一个成员函数被声明为虚函数后,在同一类族中的类就不能再定义一个非virtual的但与该虚函数具有相同参数(个数与类型)和函数返回值类型的同 名函数。
【7】静态成员函数不能是虚函数,因为静态成员函数不受限于某个对象。
【8】inline函数不能是虚函数,因为 inline函数是不能在运行中动态确定其位置的。即使虚函数在类的内部定义,编译时,仍将其视为非inline的。
【5】使用虚函数,系统要 有一定的空间开销。当一个类带有虚函数时,编译器会为该类构造一个虚函数表(virtual function tanle,vtable),它是一个指针数组,存放每个虚函数的入口地址。


5  纯抽象类

   从C++的 角度来看,一个抽象类和一个接口之间没有任何区别。有时,我们习惯使用“纯抽象类”这个词来表示某个类仅仅只含有纯虚函数(不包含任何数据成员),它是抽 象类的最常见的形式。

   使用纯抽象类有什么好处?最明显的例子就是“多接口、单实现”,这是一种很常见的情况。

   在C++中加入了 纯虚函数的概念,一个纯虚函数必须被其派生类重写。借助此概念,你可以在一个C++类中通过将其成员函数 声明为纯虚函数的方法表明该类是一个纯接口类。从那以后,我就一直强调在C++中,有一种主要的使用类的方法就是让该类不包含任何状态, 而仅仅作为一个接口。

6  抽象类

    将不用来定义对象而只作为一种基本类型用作继承的类,称为抽象类(abstract class),由于它常用作基类,通常称为抽象基类。凡是包含纯虚函数的类都是抽象类。
    如果在派生类中没有对所有的纯虚函数进行定义,则此派生类仍然是抽象类,不能用来定义对象。
可以定义指向抽象类数据的指针变量。当派生类成为具体 类后,就可以用这个指针指向派生类对象,然后通过该指针调用虚函数。

    带有纯虚函数的类称为抽象类。抽象类是一种特殊的类,它是为了抽象和设计的目的而建立的,它处于继承层次结构的较上层。抽象类是不能定义对象的,在实际中 为了强调一个类是抽象类,可将该类的构造函数说明为保护的访问控制权限。抽象类的主要作用是将有关的组织在一个继承层次结构中,由它来为它们提供一个公共 的根,相关的子类是从这个根派生出来的。抽象类刻画了一组子类的操作接口的通用语义,这些语义也传给子类。一般而言,抽象类只描述这组子类共同的操作接 口,而完整的实现留给子类。抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。如果派生类没有重新定义纯虚函数,而派生类只是继承基类的纯虚函 数,则这个派生类仍然还是一个抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体类了。

7 java 中的接口   

   不需要声明接口为抽象或虚拟(本来就是)
   接口不允许有构造函数(纯抽象了,根本不需要构造)
   接口不允许有析构函数(本来无构造,何需有析构)
   接口的所有成员都是抽象的(纯抽象类嘛)
   接口只可以从接口继承(因为只有接口可以保证使纯虚的,如果从抽象类继承,不能保证抽象类中可能存在非抽象的成员)
   接口成员不允许有任何修饰(默认就是public的,也只有是public的)
   一个类或结构可以实现多个接口


note:  纯虚析构

当用纯虚析构来构造抽象类的时候,必须在类外定义此纯虚析构函数,因为析构的时候会先调用派生类的析构,再调用基类的析构,如果不定义,则链接器会找不到函数定义(类中只有声明,不能定义),链接器会报错。

当然,也可以不用纯虚析构来构造抽象类,用其他函数,这样编译器会自己合成一个纯虚析构函数,则不会出错。

-------------------------------------------

1、c++实现多态的方法

其实很多人都知道,虚函数在c++中的实现机制就是用虚表和虚指针,但是具体是怎样的呢?从more effecive c++其中一篇文章里面可以知道:是每个类用了一个虚表,每个类的对象用了一个虚指针。具体的用法如下:

class A
{
public:
    virtual void f();
    virtual void g();
private:
    int a
};

class B : public A
{
public:
    void g();
private:
    int b;
};

//A,B 的实现省略

因为A有virtual void f(),和g(),所以编译器为A类准备了一个虚表vtableA,内容如下:

A::f 的地址
A::g 的地址

B 因为继承了A,所以编译器也为B准备了一个虚表vtableB,内容如下:

A::f 的地址
B::g 的地址

注意:因为B::g是重写 了的,所以B的虚表的g放的是B::g的入口地址,但是f是从上面的A继承下来的,所以f的地址是A::f的入口地址。

然后某处有语句 B bB;的时候,编译器分配空间时,除了A的int a,B的成员int b;以外,还分配了一个虚指针vptr,指向B的虚表vtableB,bB的布局如下:

vptr : 指向B的虚表vtableB
int a: 继承A的成员
int b: B成员

当 如下语句的时候:
A *pa = &bB;

pa的结构就是A的布局(就是说用pa只能访问的到bB对象的前两项,访问不 到第三项int b)

那么pa->g()中,编译器知道的是,g是一个声明为virtual的成员函数,而且其入口地址放在表格 (无论是vtalbeA表还是vtalbeB表)的第2项,那么编译器编译这条语句的时候就如是转换:call *(pa->vptr)[1](C语言的数组索引从0开始哈~)。

这一项放的是B::g()的入口地址,则就实现了多态。(注意 bB的vptr指向的是B的虚表vtableB)

另外要注意的是,如上的实现并不是唯一的,C++标准只要求用这种机制实现多态,至于虚 指针vptr到底放在一个对象布局的哪里,标准没有要求,每个编译器自己决定。我以上的结果是根据g++ 4.3.4经过反汇编分析出来的。

2、 两种多态实现机制及其优缺点

除了c++的这种多态的实现机制之外,还有另外一种实现机制,也是查表,不过是按名称查表,是 smalltalk等语言的实现机制。这两种方法的优缺点如下:

(1)、按照绝对位置查表,这种方法由于编译阶段已经做好了索引和表项 (如上面的call *(pa->vptr[1]) ),所以运行速度比较快;缺点是:当A的virtual成员比较多(比如1000个),而B重写的成员比较少(比如2个),这种时候,B的vtableB 的剩下的998个表项都是放A中的virtual成员函数的指针,如果这个派生体系比较大的时候,就浪费了很多的空间。

比如:GUI库, 以MFC库为例,MFC有很多类,都是一个继承体系;而且很多时候每个类只是1,2个成员函数需要在派生类重写,如果用C++的虚函数机制,每个类有一个 虚表,每个表里面有大量的重复,就会造成空间利用率不高。于是MFC的消息映射机制不用虚函数,而用第二种方法来实现多态,那就是:

(2)、 按照函数名称查表,这种方案可以避免如上的问题;但是由于要比较名称,有时候要遍历所有的继承结构,时间效率性能不是很高。(关于MFC的消息映射的实 现,看下一篇文章)

3、总结:

如果继承体系的基类的virtual成员不多,而且在派生类要重写的部分占了其中的大多数 时候,用C++的虚函数机制是比较好的;

但是如果继承体系的基类的virtual成员很多,或者是继承体系比较庞大的时候,而且派生类中 需要重写的部分比较少,那就用名称查找表,这样效率会高一些,很多的GUI库都是这样的,比如MFC,QT
阅读(811) | 评论(0) | 转发(0) |
0

上一篇:poj 题型

下一篇:1.2中国象棋将帅问题

给主人留下些什么吧!~~