C++中的虚函数(1)

虚函数语法形式

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修饰,这样代码含义更加清晰。