虚函数语法形式
class Foo{ ... virtual void func(); ... };
C++虚函数必须和C++的继承结合起来一起看。示例是最好的解释。
#include <iostream> using namespace std; class Base{ public: void func1(){ cout<<"I'm Base func1"<<endl; } virtual void func2(){ cout<<"I'm Base func2"<<endl; } }; class Derived:public Base{
public:
void func1(){
cout<<“I’m Derived func1“<<endl;
}
virtual void func2(){
cout<<“I’m Derived func2“<<endl;
}
}; int main(){
Derived d= Derived();
Base &o = d; //创建了一个指向子类对象的引用
o.func1(); //func1不是虚函数,因此使用了Base的func1方法
o.func2(); //func2是虚函数,因此根据引用所指向的实际对象的类型选择func2,即Derived的func2
return 0;
}
运行结果
I'm Base func1
I'm Derived func2
汇编代码分析
40091c: 48 c7 45 f0 00 00 00 movq $0x0,-0x10(%rbp) #-0x10(%rbp)中存放的是对象d,这个对象占用8个字节 400923: 00 #这8个字节将会用来存储一个指针,指针指向Derived类的虚函数表 400924: 48 8d 45 f0 lea -0x10(%rbp),%rax 400928: 48 89 c7 mov %rax,%rdi 40092b: e8 18 01 00 00 callq 400a48 <_ZN7DerivedC1Ev> #调用Derived的默认构造函数 400930: 48 8d 45 f0 lea -0x10(%rbp),%rax 400934: 48 89 45 f8 mov %rax,-0x8(%rbp) #-0x8(%ebp)存储引用o,o的内容实际上是对象d的起始地址。 400938: 48 8b 45 f8 mov -0x8(%rbp),%rax 40093c: 48 89 c7 mov %rax,%rdi #这两行准备this指针 40093f: e8 70 00 00 00 callq 4009b4 <_ZN4Base5func1Ev #不是虚函数,直接调用Base的func1 400944: 48 8b 45 f8 mov -0x8(%rbp),%rax #取出d的地址 400948: 48 8b 00 mov (%rax),%rax #取出d的内容,即指向Derived类虚函数表的指针 40094b: 48 8b 10 mov (%rax),%rdx #func2是Derived类的唯一虚函数,即第0项。取出func2的地址。 40094e: 48 8b 45 f8 mov -0x8(%rbp),%rax 400952: 48 89 c7 mov %rax,%rdi #这两行也是准备this指针 400955: ff d2 callq *%rdx #调用func2
如果感兴趣,可以用gdb实际调试一下,观察运行的过程。
NOTE 1: 当子类不override父类的虚函数,那么子类就自动继承父类的虚函数。
NOTE 2: 虚函数也可以有默认参数。如果用父类的指针或引用指向子类的对象,那么调用的函数是子类的,使用的默认参数却是父类函数的。(C++真是无比混乱啊!)
NOTE 3: 如果不想动态调用虚函数,可以使用类名+'::'来限定调用哪个函数。例如,上例中可以添加语句o.Base::func2()来调用父类的func2。
NOTE 4:父类的函数是虚函数,那么子类的对应的函数即使不添加virtual修饰,依然是虚函数。子类最好也添加上virtual修饰,这样代码含义更加清晰。