在 C++ 中,多态 是面向对象编程的三大核心特性之一(封装、继承、多态),其核心思想是:“同一接口,不同实现”——即通过基类的接口(成员函数),调用派生类的具体实现,实现代码的灵活扩展。

一、多态的核心机制:虚函数

多态的实现依赖于虚函数:在基类中用 virtual 关键字声明的成员函数,允许派生类重写 该函数(函数名、参数、返回值完全相同)。
当通过基类指针或引用调用该函数时,编译器会根据实际指向的对象类型,自动选择对应的派生类实现。

二、多态的实现条件

  1. 继承关系:必须存在基类和派生类的继承。
  2. 虚函数重写:基类中声明 virtual 函数,派生类重写该函数(函数签名完全一致)。
  3. 基类指针/引用:通过基类的指针或引用调用虚函数。

三、多态的示例:动物叫

假设我们有一个基类 Animal,派生类 DogCat,它们都有“叫”的行为,但叫声不同。通过多态,可以用统一的接口调用不同动物的叫声。

#include <iostream>
using namespace std;

// 基类:动物
class Animal {
public:
// 虚函数:动物叫(基类声明,用 virtual)
virtual void bark() {
cout << "动物叫" << endl;
}
};

// 派生类:狗
class Dog : public Animal {
public:
// 重写(Override)基类的虚函数
void bark() override { // override 关键字可选,用于检查重写是否正确
cout << "汪汪叫" << endl;
}
};

// 派生类:猫
class Cat : public Animal {
public:
// 重写基类的虚函数
void bark() override {
cout << "喵喵叫" << endl;
}
};

// 统一接口:通过基类指针调用 bark
void makeSound(Animal* animal) {
animal->bark(); // 调用的是实际对象(Dog/Cat)的 bark
}

int main() {
Animal* a1 = new Dog(); // 基类指针指向 Dog 对象
Animal* a2 = new Cat(); // 基类指针指向 Cat 对象

makeSound(a1); // 输出:汪汪叫(调用 Dog 的 bark)
makeSound(a2); // 输出:喵喵叫(调用 Cat 的 bark)

delete a1;
delete a2;
return 0;
}

关键分析

  • makeSound 函数的参数是 Animal*(基类指针),但调用 bark 时,实际执行的是派生类(DogCat)的 bark 函数——这就是多态:同一接口(bark),不同实现。

四、多态的底层原理:虚函数表(Vtable)

C++ 多态通过虚函数表(Virtual Table,简称 Vtable)实现:

  1. 每个包含虚函数的类(基类或派生类)会有一个虚函数表,表中存储该类所有虚函数的地址。
  2. 每个对象会包含一个虚表指针(vptr),指向所属类的虚函数表。
  3. 当通过基类指针调用虚函数时,编译器会通过对象的 vptr 找到对应类的虚函数表,再调用表中的函数地址——因此能根据实际对象类型动态绑定函数(动态绑定)。

五、纯虚函数与抽象类

有时基类的虚函数不需要具体实现(仅作为接口),可以声明为纯虚函数(在函数声明后加 =0)。包含纯虚函数的类称为抽象类,抽象类不能实例化对象,只能作为基类被继承。

// 抽象类:包含纯虚函数
class Shape {
public:
// 纯虚函数:计算面积(仅声明,无实现)
virtual double getArea() = 0;
};

// 派生类:圆形(必须实现纯虚函数,否则也是抽象类)
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
// 实现纯虚函数
double getArea() override {
return 3.14 * radius * radius;
}
};

// 派生类:矩形
class Rectangle : public Shape {
private:
double width, height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
// 实现纯虚函数
double getArea() override {
return width * height;
}
};

// 多态调用:统一接口计算面积
void printArea(Shape* shape) {
cout << "面积:" << shape->getArea() << endl;
}

int main() {
Shape* s1 = new Circle(5);
Shape* s2 = new Rectangle(3, 4);
printArea(s1); // 输出:78.5
printArea(s2); // 输出:12
return 0;
}

作用:抽象类强制派生类实现特定接口(如 getArea),保证了派生类的行为一致性,是多态的常用形式。

六.不恰当的虚函数

1. 虚函数中参数类型不同 (重载而非覆盖)

虚函数的精髓在于动态绑定,要求基类指针或引用调用的函数签名(函数名、参数列表)与派生类中的函数签名完全匹配。如果参数列表不同,编译器会将派生类中的函数视为一个全新的函数,与基类的虚函数重载,而不是覆盖

示例 : 参数列表不同

##include <iostream>
#include <string>

class BaseTask {
public:
// 核心虚函数:签名 (int)
virtual void do_work(int count) {
std::cout << "Base::do_work(int) -> Processing " << count << " items." << std::endl;
}

virtual ~BaseTask() = default;
};

class HeavyTask : public BaseTask {
public:
// 错误重载 (Overloading) 而非覆盖 (Overriding)
// 函数名相同,但参数类型不同 (std::string),导致它是一个全新的函数。
void do_work(const std::string& description) {
std::cout << "HeavyTask::do_work(string) -> Executing: " << description << std::endl;
}

// 正确覆盖的函数 (用于对比)
virtual void do_work(int count) override {
std::cout << "HeavyTask::do_work(int) -> Efficiently processing " << count << " items." << std::endl;
}
};

void execute_task(BaseTask* task_ptr) {
std::cout << "\n--- Function Call Execution ---\n";

// 1. 签名匹配:调用 Base::do_work(int)
// 多态生效:调用 HeavyTask::do_work(int)
task_ptr->do_work(5);

// 2. 签名不匹配:尝试调用 do_work(string)
// 编译错误!基类 BaseTask 中没有 do_work(string) 这个函数。
// 虽然 HeavyTask 对象里有这个函数,但基类指针看不到它。
// task_ptr->do_work("Complex Setup"); // <-- 如果取消注释,会导致编译错误
}

int main() {
HeavyTask heavy_task;

// 调用正确签名的函数 (多态)
execute_task(&heavy_task);

// 直接通过派生类对象调用它的两个重载函数
std::cout << "\n--- Direct Derived Calls ---\n";
heavy_task.do_work(100); // 调用 do_work(int)
heavy_task.do_work("Final Report Generation"); // 调用 do_work(string)

return 0;
}

2. 虚函数中返回类型不同 (协变返回类型)

通常情况下,如果返回类型不同,虚函数机制也会失效。但 C++ 引入了一个例外规则:协变返回类型 (Covariant Return Types)

规则:协变返回类型
如果满足以下所有条件,返回类型可以不同,但函数仍构成覆盖:

  1. 基类虚函数返回类型是 BaseType*BaseType& (指针或引用)。
  2. 派生类虚函数返回类型是 DerivedType*DerivedType& (也是指针或引用)。
  3. DerivedType 必须是 BaseType 的派生类。

示例 : 返回类型协变

#include <iostream>

// 基类返回类型
class BaseResult {
public:
virtual void print() { std::cout << "BaseResult\n"; }
};

// 派生类返回类型
class DerivedResult : public BaseResult {
public:
void print() override { std::cout << "DerivedResult\n"; }
};

class BaseFactory {
public:
// 虚函数返回 BaseResult*
virtual BaseResult* createResult() {
std::cout << "Base Factory creating BaseResult...\n";
return new BaseResult();
}
};

class DerivedFactory : public BaseFactory {
public:
// 覆盖:返回类型是协变的 DerivedResult*
// DerivedResult 是 BaseResult 的派生类
DerivedResult* createResult() override { // 必须有 override
std::cout << "Derived Factory creating DerivedResult...\n";
return new DerivedResult();
}
};

void demo_return_type() {
std::cout << "--- 返回类型协变演示 ---\n";
DerivedFactory df;
BaseFactory* ptr = &df;

// 编译器知道 ptr 指向的是 DerivedFactory,会调用 DerivedFactory::createResult()
BaseResult* result = ptr->createResult();

// 实际得到的是 DerivedResult*,但通过 BaseResult* 引用
result->print();
// 输出: DerivedResult (多态生效)

delete result;
}