首先推荐一本书《C++高级编程》,花了一个下午的时间在看这本书,感觉这本书翻译的相当不错,讲的也通俗易懂。
说来惭愧,虚函数是一个我一直很迷惑的东西,但后来发现这个确实是C++中非常强大的一个特性。书上建议把所有的成员函数都声明成虚函数,至少析构函数必须是虚函数。先运行两段试验的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | #include <iostream> using namespace std; class Super{ public: void foo(){ cout<<"Super Foo"<<endl; } void bar(){ cout<<"Super Bar"<<endl; } }; class Sub:public Super{ public: void foo(){ cout<<"Sub Foo"<<endl; } }; int main() { Sub sub; sub.foo(); Super& xx = sub; xx.foo(); return 0; } |
输出的结果:
[xiaoxin@Archlinux test]$ ./test
Sub Foo
Super Foo
上面,子类重载了父类的foo方法,但当它转换成超类时,确输出了超类的方法,超类的foo方法还在
另一段代码,将函数都声明成虚函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | #include <iostream> using namespace std; class Super{ public: virtual void foo(){ cout<<"Super Foo"<<endl; } virtual void bar(){ cout<<"Super Bar"<<endl; } }; class Sub:public Super{ public: virtual void foo(){ cout<<"Sub Foo"<<endl; } }; int main() { Sub sub; sub.foo(); Super& xx = sub; xx.foo(); return 0; } |
代码的输出为
[xiaoxin@Archlinux test]$ ./test
Sub Foo
Sub Foo
这里尽管子类被转换成超类,但并没有实现超类的foo方法,也就是说,超类的foo被子类所覆盖
做个类比,子类中有堆中的数据,这些数据在子类的虚构函数中销毁,如果析构函数没声明成虚函数,而且子类指针又恰好不小心被转换成了超类的指针,然后delete it,那么它将不会调用子类的虚构函数,然后将内存泄漏事故
为何如此还得看虚函数在c++中的实现:
其实很像unix文件系统中硬连接的实现方法。在包含有虚函数的类中都会另外开辟一片内存区域一存放虚函数表,虚函数表存放的是相应函数的指针,若子类没重载超类的虚函数,则直接继承超类的指针,否则,将子类的指针指向重载的实现。当子类被强制转换成了超类时,其重载的指针指向的是同一个内存区,也就相当与真正实现了覆盖(其实java中就是这么干的)。如果没声明成虚函数,那么子类仅仅是隐藏了超类的实现,当它又“变回超类”时就原形毕露了。
正是由于这个特性,虚函数才是实现多态的基础。因为子类指针向上转换成超类指针时,相应的方法并没改变,也就实现了安全的转换。