C++ 多态与虚函数入门:从概念到规则

张开发
2026/4/18 0:06:50 15 分钟阅读

分享文章

C++ 多态与虚函数入门:从概念到规则
引言在面向对象编程中多态是三大特性封装、继承、多态中最精髓的一个。它字面意思是“多种形态”在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 关键字C11明确表示重写 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 关键字C11一、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; // 基类指针数组 vectorAnimal* 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关键字C11不需要被继承的类可以使用final不要重新定义虚函数的默认参数通过基类指针/引用调用虚函数才能实现多态虚函数是实现运行时多态的基础。理解虚函数的规则是掌握C面向对象编程的关键一步。核心记忆virtual告诉编译器这个函数需要动态绑定override告诉编译器我要重写基类的虚函数final告诉编译器到此为止不能再重写/继承下一节我们将深入讲解虚函数的底层实现原理——虚函数表vtable和虚函数指针vptr敬请期待

更多文章