引言
在面向对象编程中,多态是三大特性(封装、继承、多态)中最精髓的一个。它字面意思是“多种形态”,在C++中,多态允许我们通过基类指针或引用调用派生类的重写函数,从而实现“一个接口,多种实现”。
简单来说:同一个函数名,在不同的对象上执行不同的行为。
在我学习C++的过程中,多态曾让我既兴奋又困惑——兴奋的是它让代码如此灵活,困惑的是虚函数、虚表、重写这些概念交织在一起。今天,我们先从基础开始,理解多态的概念和虚函数的规则。
第一部分:什么是多态?
一、多态的分类
| 类型 | 别名 | 发生时机 | 实现方式 |
|---|---|---|---|
| 静态多态 | 编译时多态 | 编译阶段 | 函数重载、模板 |
| 动态多态 | 运行时多态 | 运行阶段 | 虚函数、继承 |
// 静态多态:函数重载 class Calculator { public: int add(int a, int b) { return a + b; } // 编译时确定 double add(double a, double b) { return a + b; } // 编译时确定 }; // 动态多态:虚函数(本节重点) class Animal { public: virtual void speak() { cout << "动物叫" << endl; } // 运行时确定 }; class Dog : public Animal { public: void speak() override { cout << "汪汪" << endl; } };二、多态的核心思想***
第二部分:虚函数的基本概念
一、什么是虚函数?
虚函数是在基类中使用关键字virtual声明的成员函数,它可以在派生类中被重写(override)。
#include <iostream> using namespace std; class Shape { public: // 虚函数 virtual void draw() { cout << "绘制图形" << endl; } // 普通函数(非虚) void info() { cout << "这是一个图形" << endl; } }; class Circle : public Shape { public: // 重写虚函数 void draw() override { cout << "绘制圆形" << endl; } // 隐藏普通函数(不推荐) void info() { cout << "这是一个圆形" << endl; } }; int main() { Circle c; Shape* p = &c; // 基类指针指向派生类对象 p->draw(); // 输出:绘制圆形(虚函数:调用派生类版本) p->info(); // 输出:这是一个图形(普通函数:调用基类版本) return 0; }二、虚函数的作用
// 没有虚函数:无法实现多态 class Bird { public: void fly() { cout << "鸟在飞" << endl; } }; class Penguin : public Bird { public: void fly() { cout << "企鹅不会飞" << endl; } }; void makeFly(Bird* b) { b->fly(); // 永远调用 Bird::fly() } // 有虚函数:实现多态 class BirdV { public: virtual void fly() { cout << "鸟在飞" << endl; } }; class PenguinV : public BirdV { public: void fly() override { cout << "企鹅不会飞" endl; } }; void makeFlyV(BirdV* b) { b->fly(); // 根据实际对象类型调用 } int main() { Penguin p; makeFly(&p); // 输出:鸟在飞(不是期望的结果) PenguinV pv; makeFlyV(&pv); // 输出:企鹅不会飞(正确的多态行为) return 0; }第三部分:虚函数的规则
一、虚函数的基本规则
class Base { public: // 规则1:虚函数用 virtual 关键字声明 virtual void func1() { cout << "Base::func1" << endl; } // 规则2:虚函数可以有默认实现 virtual void func2() { cout << "Base::func2" << endl; } // 规则3:虚函数可以是纯虚函数(下节讲解) // virtual void func3() = 0; // 纯虚函数 // 规则4:析构函数通常应该是虚函数 virtual ~Base() { cout << "Base析构" << endl; } }; class Derived : public Base { public: // 规则5:重写虚函数时,函数签名必须完全相同 // 返回类型、函数名、参数列表都要一致 void func1() override { cout << "Derived::func1" << endl; } // 规则6:可以使用 override 关键字(C++11)明确表示重写 void func2() override { cout << "Derived::func2" endl; } // 错误示例:参数不同,这是重载/隐藏,不是重写 // void func1(int x) { } // 这会隐藏基类的 func1 ~Derived() { cout << "Derived析构" << endl; } };二、虚函数规则详细说明
规则1:虚函数必须通过指针或引用调用才能实现多态
class Base { public: virtual void show() { cout << "Base" << endl; } }; class Derived : public Base { public: void show() override { cout << "Derived" << endl; } }; int main() { Derived d; Base b; Base* p1 = &d; p1->show(); // ✅ 多态:输出 Derived Base& r1 = d; r1.show(); // ✅ 多态:输出 Derived Base b1 = d; // 对象切片 b1.show(); // ❌ 不是多态:输出 Base(切片丢失了派生类信息) return 0; }规则2:虚函数不能是静态函数
class Test { public: // static virtual void func(); // 错误!虚函数不能是静态的 // 静态函数属于类,不属于对象,无法实现多态 };规则3:虚函数不能是内联函数(但编译器可能忽略)
class Test { public: // virtual inline void func(); // 不推荐,虚函数通常不内联 // 因为虚函数的调用需要在运行时确定,内联在编译时展开 };规则4:构造函数不能是虚函数
class Test { public: // virtual Test() { } // 错误!构造函数不能是虚函数 // 构造对象时需要知道确切类型,无法动态决定 };规则5:虚函数的重写要求函数签名完全一致
class Base { public: virtual void func() { } virtual void func(int x) { } }; class Derived : public Base { public: // ✅ 正确:签名完全一致 void func() override { } // ✅ 正确:重写另一个虚函数 void func(int x) override { } // ❌ 错误:返回值类型不同(特殊情况除外:协变返回类型) // int func() override { return 0; } // ❌ 错误:参数不同,这是隐藏,不是重写 // void func(double x) { } };规则6:协变返回类型(特殊情况)
class Base { public: virtual Base* clone() const { return new Base(*this); } }; class Derived : public Base { public: // 允许:返回类型是基类返回类型的派生类指针 virtual Derived* clone() const override { return new Derived(*this); } };规则7:虚函数的默认参数不会动态绑定
class Base { public: virtual void func(int x = 10) { cout << "Base::func: " << x << endl; } }; class Derived : public Base { public: void func(int x = 20) override { cout << "Derived::func: " << x << endl; } }; int main() { Base* p = new Derived(); p->func(); // 输出:Derived::func: 10 // 函数体是 Derived 的,但默认参数使用的是 Base 的! // 警告:不要重新定义虚函数的默认参数 delete p; return 0; }第四部分:虚函数与析构函数
一、为什么基类析构函数应该是虚函数?
// 错误示例:基类析构函数不是虚函数 class BaseWrong { public: ~BaseWrong() { cout << "BaseWrong析构" << endl; } }; class DerivedWrong : public BaseWrong { private: int* data; public: DerivedWrong() : data(new int[100]) { } ~DerivedWrong() { delete[] data; cout << "DerivedWrong析构" << endl; } }; int main() { BaseWrong* p = new DerivedWrong(); delete p; // 只调用 BaseWrong 的析构函数! // 问题:DerivedWrong 的析构函数没有被调用,data 内存泄漏! return 0; } // 正确示例:基类析构函数是虚函数 class BaseCorrect { public: virtual ~BaseCorrect() { cout << "BaseCorrect析构" << endl; } }; class DerivedCorrect : public BaseCorrect { private: int* data; public: DerivedCorrect() : data(new int[100]) { } ~DerivedCorrect() override { delete[] data; cout << "DerivedCorrect析构" << endl; } }; int main() { BaseCorrect* p = new DerivedCorrect(); delete p; // 先调用 DerivedCorrect 析构,再调用 BaseCorrect 析构 // ✅ 正确释放资源 return 0; }二、规则总结:只要类会被继承,析构函数就应该是虚函数
class Interface { public: virtual ~Interface() = default; // 虚析构函数 virtual void doSomething() = 0; };第五部分:override 和 final 关键字(C++11)
一、override:显式声明重写
class Base { public: virtual void func1() { } virtual void func2(int x) { } virtual void func3() const { } }; class Derived : public Base { public: // ✅ 明确表示要重写基类的虚函数 void func1() override { } // ❌ 编译错误:参数不匹配,override 会检查 // void func2(double x) override { } // ❌ 编译错误:const 不匹配 // void func3() override { } // 建议:只要重写虚函数,就加上 override };二、final:禁止重写或禁止继承
class Base { public: virtual void func() final { // final:派生类不能重写这个函数 cout << "Base::func" << endl; } }; class Derived : public Base { public: // void func() override { } // 错误!func 被 final 禁止重写 }; class FinalClass final { // final:不能被继承 // ... }; // class Bad : public FinalClass { }; // 错误!FinalClass 被 final 禁止继承第六部分:完整示例——动物叫声系统
#include <iostream> #include <string> #include <vector> using namespace std; // 基类:动物 class Animal { protected: string name; public: Animal(const string& n) : name(n) { } // 虚函数:发出声音 virtual void speak() const { cout << name << "发出声音" << endl; } // 虚函数:获取类型 virtual string getType() const { return "动物"; } // 虚析构函数:确保派生类正确析构 virtual ~Animal() { cout << name << "被销毁" << endl; } }; // 派生类:狗 class Dog : public Animal { public: Dog(const string& n) : Animal(n) { } void speak() const override { cout << name << "汪汪叫" << endl; } string getType() const override { return "狗"; } }; // 派生类:猫 class Cat : public Animal { public: Cat(const string& n) : Animal(n) { } void speak() const override { cout << name << "喵喵叫" << endl; } string getType() const override { return "猫"; } }; // 派生类:鸟 class Bird : public Animal { public: Bird(const string& n) : Animal(n) { } void speak() const override { cout << name << "叽叽喳喳" << endl; } string getType() const override { return "鸟"; } }; // 多态演示函数 void makeSound(const Animal* animal) { cout << "这是一只" << animal->getType(); cout << ",它在"; animal->speak(); } int main() { cout << "=== 多态演示 ===" << endl; // 基类指针数组 vector<Animal*> animals; animals.push_back(new Dog("旺财")); animals.push_back(new Cat("咪咪")); animals.push_back(new Bird("啾啾")); // 统一调用,行为不同 for (Animal* a : animals) { makeSound(a); } // 清理内存 for (Animal* a : animals) { delete a; // 虚析构函数确保正确释放 } return 0; } /* 输出: === 多态演示 === 这是一只狗,它在旺财汪汪叫 这是一只猫,它在咪咪喵喵叫 这是一只鸟,它在啾啾叽叽喳喳 旺财被销毁 咪咪被销毁 啾啾被销毁 */总结
一、虚函数核心规则速查表
| 规则 | 说明 |
|---|---|
| 声明方式 | 使用virtual关键字 |
| 调用方式 | 通过指针或引用调用才能实现多态 |
| 重写要求 | 函数签名必须完全相同(返回值、函数名、参数) |
| 构造函数 | 不能是虚函数 |
| 析构函数 | 基类析构函数应该是虚函数 |
| 静态函数 | 不能是虚函数 |
| 默认参数 | 不会动态绑定,不要重新定义 |
| override | 推荐使用,编译器会检查重写是否正确 |
| final | 禁止重写或禁止继承 |
二、虚函数 vs 普通函数
| 特性 | 普通函数 | 虚函数 |
|---|---|---|
| 绑定时机 | 编译时 | 运行时 |
| 调用方式 | 通过对象或指针 | 通常通过指针/引用 |
| 派生类重写 | 隐藏(不推荐) | 重写(override) |
| 性能 | 快(直接调用) | 稍慢(通过虚表) |
| 多态支持 | 不支持 | 支持 |
三、使用建议
基类析构函数必须设为虚函数(如果类会被继承)
重写虚函数时使用
override关键字(C++11)不需要被继承的类可以使用
final不要重新定义虚函数的默认参数
通过基类指针/引用调用虚函数才能实现多态
虚函数是实现运行时多态的基础。理解虚函数的规则,是掌握C++面向对象编程的关键一步。
核心记忆:
virtual告诉编译器:这个函数需要动态绑定override告诉编译器:我要重写基类的虚函数final告诉编译器:到此为止,不能再重写/继承
下一节,我们将深入讲解虚函数的底层实现原理——虚函数表(vtable)和虚函数指针(vptr),敬请期待!