C++笔记——虚函数与动态联编
动态联编?静态联编?
程序调用函数时,将使用哪个可执行代码块呢?编译器负责回答这个问题。将源代码中的函数调用解释为执行特定的函数代码块被称为函数名联编(binding)。在C语言中,这非常简单,因为每个函数名部对应一个不同的函数。在C++中,由于函教重载的缘故,这项任务更复杂。编译器必须查看函数参数以及函数名才能确定使用哪个函数。然而,C/C++编译器可以在编译过程完成这种联编。在编译过程中进行联编被称为静态联编(static binding), 又称为早期联编(earfy binding)。**然而虚函数使这项工作变得更困难:使用哪一个函数是不能在编译时确定的, 因为编译器不知道用户将选择哪种类型的对象。**所以,编译器必须生成能够在程序运行时选择正确的虚方法的代码,这被称为动态联编(dynamic-binding),又称为晚期联编(late binding)。
代码示例:
#include <iostream>
using namespace std;
class CA
{
public:
virtual void f1() {
cout << "CA::f1()" << endl;
f2();
}
void f2() {
cout << "CA::f2()" << endl;
}
};
class CB : public CA
{
public:
void f1() {
cout << "CB::f1()" << endl;
f2();
}
void f2() {
cout << "CB::f2()" << endl;
}
};
class CC : public CB
{
public:
virtual void f2() {
cout << "CC::f2()" << endl;
};
};
int main()
{
CC c; // 创建CC类对象
CA *pA = &c; // 基类指针 指向 派生类对象
pA->f1();
return 0;
}
程序运行结果为:
CA* pa = &c;
这句话是父类指针指向子类对象,调用pa->f1()
时,因为父类中的f1()
是虚函数,所以将发生动态绑定,调用子类CB中的f1()函数,先输出CB::f1()
;- 在CB类的
f1()
函数中,调用非虚函数f2()
,但因为其父类CA中的f2()函数并不是虚函数(并且CB类重载了f2()函数),所以将调用CB类中的f2()
函数,输出CB::f2()
;- 如果将CA类中的
f2()
改成虚函数,保持其他代码不变,那么将输出CB::f1() CC::f2()
; (注意:在基类方法的声明中使用关键字virtual 可使该方法在基类以及所有的派生类(包括从派生类派生出来的类)中是虚的)- 如果将CB类中的
f2()
改成虚函数,保持其他代码不变,也将输出CB::f1() CC::f2()
。
同理:基类的析构函数应定义为虚函数
如果派生类中申请了内存空间,并在其析构函数中对这些内存空间进行释放。假设基类中采用的是非虚析构函数,当删除基类指针指向的派生类对象时就不会触发动态绑定,因而只会调用基类的析构函数,而不会调用派生类的析构函数。那么在这种情况下,派生类中申请的空间就得不到释放从而产生内存泄漏。所以,为了防止这种情况的发生,C++中基类的析构函数应采用virtual虚析构函数。
关于虚函数的总结
- 在基类方法的声明中使用关键字virtual 可使该方法在基类以及所有的派生类(包括从派生类派生出来的类)中是虚的。
- 如果使用指向对象的引用或指针来调用虚方法,程序将使用为对象类型定义的方法,而不使用为引用或指针类型定义的方法,这称为动态联编或晚期联编。这种行为非常重要,因为这样基类指件或引用可以指向派生类对象。
- 如果定义的类将被用作基类, 则应将那些要在派生类中重新定义的类方法声明为虚的。
- 构造函数不能为虚函数
- 基类的析构函数应当是虚函数