
多态是面向对象编程里很常用的一个概念,简单讲就是'同一个接口,不同的行为'。它让代码复用性更好,也更容易扩展。多态分两种:编译时多态(静态多态)和运行时多态(动态多态)。函数重载、模板这些都是在编译期间就确定具体调用的,属于静态多态;而今天我们主要聊的是运行时多态——通过基类指针或引用调用虚函数,程序在运行中才决定执行哪个函数,真正的'同一接口,多种形态'。
要实现运行时多态,必须同时具备两个条件。一是继承关系,必须用基类的指针或引用来操作派生类对象;二是虚函数重写,被调用的函数得是虚函数,并且在派生类里做了重写(override)。如果只满足其中一个,比如直接通过对象调用而不是用指针/引用,或者函数没加上 virtual,那么编译器会直接静态绑定,体现不出多态。
虚函数很简单,成员函数前面加 virtual 关键字就行,非成员函数不行。下面这个例子,Person 和 Student 都有 BuyTicket 虚函数:
class Person {
public:
virtual void BuyTicket() {
cout << "买票全价" << endl;
}
};
class Student : public Person {
public:
virtual void BuyTicket() {
cout << "买票半价" << endl;
}
};
当你用基类指针去调 BuyTicket,实际执行哪个版本取决于指针指向的对象,这就是多态的核心。
重写的规则要注意一下:派生类的虚函数与基类的虚函数,返回值、函数名、参数列表必须一模一样。派生类重写时可以不写 virtual,因为一旦在基类定义为虚函数,派生类继承后它依然是虚函数。不过按照规范,建议还是加上 virtual 或者 C++11 的 override,这样意图更清晰。
关于虚函数有个容易踩坑的地方,就是默认参数。默认参数是编译期绑定的,重写只覆盖函数体,不会改变默认值。看下面这段代码:
class A {
public:
virtual void func(int val = 1) {
std::cout << << val << std::endl;
}
{
();
}
};
: A {
:
{
std::cout << << val << std::endl;
}
};
{
B* p = B;
p->();
;
}


