Chinaunix首页 | 论坛 | 博客
  • 博客访问: 493585
  • 博文数量: 72
  • 博客积分: 1851
  • 博客等级: 上尉
  • 技术积分: 1464
  • 用 户 组: 普通用户
  • 注册时间: 2010-09-16 17:50
文章分类

全部博文(72)

文章存档

2013年(1)

2012年(17)

2011年(51)

2010年(3)

分类: C/C++

2011-06-01 21:54:37

 在上一篇文档中,谈到子类型通过按值传递,按地址传递到以基类的行参中去,这称为”upcast”,因为子类型的对象也是基类的对象,所以理所当然就能被传递,只不过这样就丧失了子类的特征。这样的转换不需要任何的机制。

但是如果基类可不可以转换称为子类呢,也就是说,基类对象可不可以调用子类的方法呢?当然可以,需要通过虚函数实现,这就是鼎鼎大名的”多态”(polymorphism)。先看下面的用法:

  1. #include
  2. using namespace std;
  3. enum note {middleC,Csharp,Cflag};
  4. class Instrument {
  5. public:
  6. virtual void play(note)
  7. {cout<<"Instrument::play"<
  8. virtual ~Instrument(){}
  9. };
  10. class Wind:public Instrument{
  11. public:
  12. void play(note){
  13. cout<<"Wind::play"<
  14. }
  15. ~Wind(){}
  16. };
  17. void tune(Instrument& i){
  18. i.play(middleC);
  19. }
  20. int main()
  21. {
  22. Wind flute;
  23. tune(flute);
  24. Instrument *p = new Wind;
  25. p->play(middleC);
  26. delete p;
  27. return 0;
  28. }

 执行上面程序,结果如下:

  1. Wind::play
  2. Wind::play
  3. Wind::destructor
  4. Instrument::destructor
  5. Wind::destructor
  6. Instrument::destructor

 上面大致就是虚函数的用法,讲所需要进行向下转换的函数标记virtual关键字后,就能进行基类向子类的转换,不过实参必须是子类型或者基类的指针但实际指向为子类。

下面开始具体讨论

在编译其间就能决定所需要调用的具体函数,这称之为”早期绑定(静态绑定)”,而在运行时决定所需要调用的具体函数,称为”晚期绑定(动态绑定)”,而虚函数机制就是所谓的动态绑定,关键字virtual就是告诉编译器动态绑定,它通过建立虚表(VTABLE)来完成,其中的每一项就是virtual标识的每一个函数,顺序就是基类函数声明的顺序,另外还有一个vptr,虚表指针来指向具体的虚表VTALBE,每一个类缺一不可,这样就完成了具体的调用行为。

  1. #include <iostream>
  2. using namespace std;

  3. class NoVirtual
  4. {
  5. int a;
  6. public:
  7.  void x() const{}
  8.  int i() const {return 0;}
  9. };

  10. class OneVirtual
  11. {
  12. int a;
  13. public:
  14.   virtual void x() const {}
  15.    int i()const {return 0;}
  16. };
  17. class OneVirtualInherit:public OneVirtual
  18. {
  19. public:
  20.  void x() const{}
  21.  int i()const{return OneVirtual::i();}
  22. };
  23. class TwoVirtual
  24. {
  25. int a;
  26. public:
  27.  virtual void x() const{}
  28.  virtual int i() const{return 0;}
  29. };
  30. int main()
  31. {
  32.  cout<<"int: "<<sizeof(int)<<endl;
  33.  cout<<"NoVirtual: "<<sizeof(NoVirtual)<<endl;
  34.  cout<<"void* :"<<sizeof(void*)<<endl;
  35.  cout<<"OneVirtual: "<<sizeof(OneVirtual)<<endl;
  36.  cout<<"OneVirtualInherit:"<<sizeof(OneVirtualInherit)<<endl;
  37.  cout<<"TwoVirutal: "<<sizeof(TwoVirtual)<<endl;
  38.  return 0;
  39. }

 执行结果如下:

  1. int: 4
  2. NoVirtual: 4
  3. void* :4
  4. OneVirtual: 8
  5. OneVirtualInherit:8
  6. TwoVirutal: 8

 可以看出,安装了虚拟函数后,明显增加了4个字节,这就是vptr(虚表指针)

继续第一个例子,在通过基类指针指向子类后,就会讲基类的虚表指针VPTR指向子类的虚表起始地址vtable,实现的调用图(第二个方法为虚构)如下:

 进行对象初始时,虚函数指针的初始化,虚表的建立过程就在构造函数中实现,所以C++不准构造函数被定义成虚函数形式(编译报错),而在构造函数中调用,不过这些都是自动完成,但需要考虑效率问题:

编译器隐藏插入到构造函数中,初始化虚表指针VPTR,检查this的值(防止operator new 返回0),

调用基类构造函数,这些加上去,可能会抵消内联构造函数的避免函数调用开销,当出现虚函数时,最好不要内联形式的构造函数。

如果调用内联函数,可以下面形式:

  1. public:
  2. Wind(){lay(middleC);}
  3. Wind(int i){
  4.  Instrument *p = new Wind();
  5.  cout<<"call in constructor"<<endl;
  6.  p->play(middleC);
  7.  cout<<"end call"<<endl;
  8.  delete p;
  9. }

 上面的无参数构造函数明显调用的是本地play()函数,而Wind(int i)在一开始就调用无参数构造函数实际上的vtabl已经初始化完成了,之后的调用肯定能实现动态绑定。当对象生成后,构造函数被首先调用,基类的构造函数被调用,接着就是构造函数本身,等等。虚表被初始化,而虚表指针VPTR则指向初始化自己的本地虚表,而基类的虚表指针VPTR则指向派生类的虚表,这样讲来,基类虚表指针是在子类初始化一步步完成的,这样最后一个子类的构造函数调用决定基类虚表指针的指向。

当虚拟函数与重载函数相结合时,基类出现重载而重载函数为虚拟函数,如果没有被子类实现的函数将会被自动隐藏,而且与普通函数不同的是,这些被重载的函数可以修改行参,如果行参改变,也就意味着子类函数不能调用,但不能修改返回类型。

如果在继承基类后,在子类里添加一些虚拟函数,编译器就会为子类创建一个新的VTABLE,使用任何没有被改写的虚函数作为其中的项,并且添加新的项。但是如果用基类的指针来调用子类新增加的虚函数肯定出错,这样可以通过dynamic_cast进行转换。

  1. #include <iostream>
  2. #include <string>

  3. using namespace std;

  4. class Pet{
  5. string pname;
  6. public:
  7. Pet(const string& petName):pname(petName){}
  8. virtual string name()const{return pname;}
  9. virtual string speak()const{return "";}
  10. };

  11. class Dog:public Pet{
  12. string name;
  13. public:
  14.  Dog(const string& petName):Pet(petName){}
  15. //New virtual function in the Dog class
  16. virtual string sit()const {return Pet::name()+" sits";}
  17. string speak() const{return Pet::name()+" says 'Bark!'";}
  18. };
  19. int main()
  20. {
  21. Pet *p[]={new Pet("generic"),new Dog("bob")};
  22. cout<<"p[0]->speak()="<<p[0]->speak()<<endl;
  23. cout<<"p[1]->speak()="<<p[1]->speak()<<endl;
  24. cout<<"p[1]->sit()"<<(dynamic_cast<Dog*>(p[1]))->sit()<<endl;
  25. return 0;
  26. }

 运行结果如下:

  1. p[0]->speak()=
  2. p[1]->speak()=bob says '
  3. p[1]->sit()bob sits

阅读(921) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~