C++ 虚函数与多态
C++与多态
多态性可以简单地概括为“一个接口,多种方法”,程序在运行时才决定调用的函数。C++多态是通过虚函数来实现的,虚函数允许派生类重新定义基类的成员函数。通过基类指针或引用指向任意一个派生类对象,调用相应的虚函数,可以根据指向的派生类的不同而实现不同的方法。
静态联编与动态联编
静态联编又称早绑定,即函数的调用,在编译器编译期间就可以确定函数的调用地址,并生产代码,是静态的。
动态联编又称晚绑定,即函数调用的地址不能在编译器期间确定,需要在运行时才确定。
虚函数
在基类成员函数前添加 virtual 来声明该成员函数为虚函数,而在派生类中该成员函数自动为虚函数(可以在派生类中的虚函数前添加 virtual 来显示的指明该函数为虚函数)。C++中普通函数,静态成员函数,内联成员函数,构造函数,友元函数不能被声明为虚函数。
#include <iostream>
using namespace std;
class Base
{
public:
virtual void f() { cout << "Base" << endl; } //声明该成员函数为虚函数
};
class Derived_1 :public Base
{
public:
virtual void f() { cout << "Derived_1" << endl; } //声明重写虚函数
};
class Derived_2 :public Base
{
public:
virtual void f() { cout << "Derived_2" << endl; } //声明重写虚函数
};
int main()
{
//通过基类指针指向派生类,实现多态
Base *b[2];
b[0] = new Derived_1;
b[1] = new Derived_2;
b[0]->f();
b[1]->f();
return 0;
}
虚函数的实现原理
每当创建一个包含有虚函数的类或从包含有虚函数的类派生一个类时,编译器就会为这个类创建一个虚函数表(VTABLE)保存该类所有虚函数的地址,其实这个虚函数表的作用就是保存自己类中所有虚函数的地址。在每个带有虚函数的类中,编译器会增加一个隐藏成员,即虚函数指针(VPTR),指向这个对象的虚函数表。如果派生类对虚函数进行重写,则虚函数中旧的虚函数地址被新的虚函数地址覆盖。如果派生类创建新的虚函数,则虚函数表增加新虚函数的地址。 当构造该派生类对象时,其虚函数指针被初始化指向该派生类的虚函数表。所以可以认为虚函数表是该类的所有对象共有的,在定义该类时被初始化;而虚函数指针则是每个类对象都有独立一份的,且在该类对象被构造时被初始化。调用虚函数时,程序将查找虚函数表,然后跳转到对于的函数地址。
隐藏与覆盖
隐藏
当基类和派生类中存在同名函数时,无论同名函数的形参个数或者类型是否相同,派生类中的同名函数都会将基类中的同名函数隐藏掉,而不会是重载关系。这时,当你通过派生类对象调用该同名函数时,只能访问派生类的该函数,如果需要访问基类的该函数,则需要在函数名前加上类作用域。
覆盖
当派生类和基类中的存在同名函数,且参数个数和参数类型完全相同,并且基类中的该函数有virtual修饰(派生类中的该函数可有可无),则派生类的该函数覆盖掉基类的该函数。
#include <iostream>
using namespace std;
class Base
{
public:
void f() { cout << "Base" << endl; }
virtual void g() { cout << "Base" << endl; }
};
class Derived:public Base
{
public:
void f() { cout << "Derived" << endl; } //隐藏基类同名函数
virtual void g() { cout << "Derived" << endl; } //覆盖基类同名函数
};
int main()
{
Derived d;
Base &b=d;
b.f(); //基类引用调用基类的f()
d.f(); //派生类调用自己的f(),隐藏了基类的f()
b.g(); //基类引用调用派生类的g(),虚函数表中基类g()的地址被覆盖为派生类g()的地址
d.g(); //派生类调用自己的g()
return 0;
}
纯虚函数与抽象类
纯虚函数是在声明虚函数的结尾添加 =0 。纯虚函在基类中不用被实现,需要在继承该类的派生类中进行实现。包含纯虚函数的类不能实例化对象。包含至少一个纯虚函数的类称为抽象类,因为包含了纯虚函数,所以抽象类不能实例化对象。
#include <iostream>
using namespace std;
class Abstract
{
public:
virtual void g() = 0; //定义纯虚函数
};
class Derived:public Abstract
{
public:
virtual void g() { cout << "Derived" << endl; } //在派生类中实现纯虚函数
};
int main()
{
Abstract *a=new Derived; //抽象类不能实例化,但可以指向或引用派生类,从而实现多态
a->g();
return 0;
}