C++多态
在 C++ 中,多态 是面向对象编程的三大核心特性之一(封装、继承、多态),其核心思想是:“同一接口,不同实现”——即通过基类的接口(成员函数),调用派生类的具体实现,实现代码的灵活扩展。
一、多态的核心机制:虚函数
多态的实现依赖于虚函数:在基类中用 virtual 关键字声明的成员函数,允许派生类重写 该函数(函数名、参数、返回值完全相同)。
当通过基类指针或引用调用该函数时,编译器会根据实际指向的对象类型,自动选择对应的派生类实现。
二、多态的实现条件
- 继承关系:必须存在基类和派生类的继承。
- 虚函数重写:基类中声明
virtual函数,派生类重写该函数(函数签名完全一致)。 - 基类指针/引用:通过基类的指针或引用调用虚函数。
三、多态的示例:动物叫
假设我们有一个基类 Animal,派生类 Dog 和 Cat,它们都有“叫”的行为,但叫声不同。通过多态,可以用统一的接口调用不同动物的叫声。
|
关键分析:
makeSound函数的参数是Animal*(基类指针),但调用bark时,实际执行的是派生类(Dog或Cat)的bark函数——这就是多态:同一接口(bark),不同实现。
四、多态的底层原理:虚函数表(Vtable)
C++ 多态通过虚函数表(Virtual Table,简称 Vtable)实现:
- 每个包含虚函数的类(基类或派生类)会有一个虚函数表,表中存储该类所有虚函数的地址。
- 每个对象会包含一个虚表指针(vptr),指向所属类的虚函数表。
- 当通过基类指针调用虚函数时,编译器会通过对象的
vptr找到对应类的虚函数表,再调用表中的函数地址——因此能根据实际对象类型动态绑定函数(动态绑定)。
五、纯虚函数与抽象类
有时基类的虚函数不需要具体实现(仅作为接口),可以声明为纯虚函数(在函数声明后加 =0)。包含纯虚函数的类称为抽象类,抽象类不能实例化对象,只能作为基类被继承。
// 抽象类:包含纯虚函数 |
作用:抽象类强制派生类实现特定接口(如 getArea),保证了派生类的行为一致性,是多态的常用形式。
六.不恰当的虚函数
1. 虚函数中参数类型不同 (重载而非覆盖)
虚函数的精髓在于动态绑定,要求基类指针或引用调用的函数签名(函数名、参数列表)与派生类中的函数签名完全匹配。如果参数列表不同,编译器会将派生类中的函数视为一个全新的函数,与基类的虚函数重载,而不是覆盖。
示例 : 参数列表不同#
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)。
规则:协变返回类型
如果满足以下所有条件,返回类型可以不同,但函数仍构成覆盖:
- 基类虚函数返回类型是
BaseType*或BaseType&(指针或引用)。 - 派生类虚函数返回类型是
DerivedType*或DerivedType&(也是指针或引用)。 DerivedType必须是BaseType的派生类。
示例 : 返回类型协变
// 基类返回类型
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;
}