一直走,一直走……

C++的精髓 虚函数

1. 虚函数的作用

为了实现多态,引入虚函数。
那什么是多态?
一言以蔽之:多态是指同一个方法在派生类和基类中的行为是不同。
或者说:多态是指使用相同的函数名来调用函数不同的实现方法,即“一种接口,多种方法”。

C++实现多态的方法有两种:

  • 编译时多态:通过函数重载实现(静态联编)
  • 运行时多态:通过虚函数实现(动态联编)

2. 虚函数的定义和使用

在基类中定义虚函数:

class Student 
{
    char name[50];
public:
    virtual void show_name();    /* virtual修饰的函数就是虚函数 */
}

使用虚函数:

当函数是通过引用或者指针而不是对象调用的,这时需要注意:

  • (1)如果该方法没有使用关键字virtual,程序将根据引用类型或指针类型选择函数;
  • (2)如果使用了virtual,程序将根据引用或指针指向的对象的类型来选择函数。

3. 虚函数的工作原理

编译器处理虚函数的方法是:
给每个对象添加一个隐藏成员(取该对象占的内存空间不确定是否会看到?),隐藏成员中保存了一个指向函数地址数组的指针。该函数地址数组成为虚函数表(virtual function table, vtbl)。虚函数表中保存着类对象声明的虚函数的地址。

一个基类对象,该对象包含一个指针,指向保存基类中所有虚函数地址的地址表vtbl1;
一个派生类对象,该对象包含一个指针,指向保存派生类中所有虚函数地址的地址表vtbl2;
如果派生类提供了虚函数的新定义,则虚函数地址表中保存新的地址;如果派生类定义了新的虚函数,则新函数的地址也要加入到vtbl2中。

调用虚函数时:
程序查看对象中vtbl地址,然后转向相应的函数地址表中,再找到调用的虚函数地址,执行该虚函数。
虚函数表中地址的顺序

  • 虚函数按照其声明顺序放于表中
  • 父类的虚函数在子类的虚函数前面

最好能写程序验证一下。

4. 虚函数的代价

使用虚函数,在内存和执行速度方面有一定的成本:

  • (1)每个对象实际占的空间增大,增大量是存储地址的空间;
  • (2)对于每一个类,编译器都需要创建一个虚函数表;
  • (3)对于每个函数调用,都需要执行一项额外的操作,即表中查找地址。(主要是因为虚函数是动态联编,非虚函数是静态联编,则不要额外的操作)

5. 虚函数相关注意事项

5.1 构造函数

构造函数不能是虚函数.

5.2 析构函数

析构函数应该是虚函数,除非类不用做基类。
通常会给基类提供一个虚析构函数,即使它并不需要析构函数。

6. 虚函数与纯虚函数的区别

  • (1)虚函数可以被直接使用,也可以被子类重载以后以多态的形式调用,而纯虚函数必须在子类中实现该函数才可以使用,因为纯虚函数在基类只有声明而没有定义;
  • (2)虚函数和纯虚函数都可以在子类中被重载,以多态的形式被调用;
  • (3)定义不同
    • 虚函数定义:virtual func() {method body} ;
    • 纯虚函数定义:virtual func() = 0;
  • (4)含有纯虚函数的类才能称为抽象类。

参考资料

[1] 《C++ prime plus》第六版
[2] 《C++ prime plus》第五版
[3] http://blog.csdn.net/wuchuanpingstone/article/details/6742465
[4] http://www.cnblogs.com/bluestorm/archive/2012/08/29/2662350.html 虚函数和纯虚函数区别