news 2026/4/18 0:06:46

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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 关键字(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)
性能快(直接调用)稍慢(通过虚表)
多态支持不支持支持

三、使用建议

  1. 基类析构函数必须设为虚函数(如果类会被继承)

  2. 重写虚函数时使用override关键字(C++11)

  3. 不需要被继承的类可以使用final

  4. 不要重新定义虚函数的默认参数

  5. 通过基类指针/引用调用虚函数才能实现多态

虚函数是实现运行时多态的基础。理解虚函数的规则,是掌握C++面向对象编程的关键一步。

核心记忆:

  • virtual告诉编译器:这个函数需要动态绑定

  • override告诉编译器:我要重写基类的虚函数

  • final告诉编译器:到此为止,不能再重写/继承

下一节,我们将深入讲解虚函数的底层实现原理——虚函数表(vtable)和虚函数指针(vptr),敬请期待!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/17 23:58:11

解密GodMode9权限系统:从绿色到红色的安全操作指南

解密GodMode9权限系统&#xff1a;从绿色到红色的安全操作指南 【免费下载链接】GodMode9 GodMode9 Explorer - A full access file browser for the Nintendo 3DS console :godmode: 项目地址: https://gitcode.com/gh_mirrors/go/GodMode9 GodMode9是一款为任天堂3DS主…

作者头像 李华
网站建设 2026/4/17 23:55:58

从泊松分布到正态分布:用Box-Cox转换驯服‘方差不稳定’的计数型特征

泊松分布到正态分布&#xff1a;Box-Cox变换如何重塑计数数据的建模潜力 当你在分析网站每日访问量、餐厅订单数或社交媒体互动次数时&#xff0c;是否遇到过模型效果总是不尽如人意的困扰&#xff1f;这些计数型数据背后隐藏着一个统计学秘密——它们往往服从泊松分布&#xf…

作者头像 李华
网站建设 2026/4/17 23:55:00

题解:洛谷 P9752 [CSP-S 2023] 密码锁

本文分享的必刷题目是从蓝桥云课、洛谷、AcWing等知名刷题平台精心挑选而来,并结合各平台提供的算法标签和难度等级进行了系统分类。题目涵盖了从基础到进阶的多种算法和数据结构,旨在为不同阶段的编程学习者提供一条清晰、平稳的学习提升路径。 欢迎大家订阅我的专栏:算法…

作者头像 李华
网站建设 2026/4/17 23:53:24

5G NR功率控制实战:手把手教你用Wireshark和UE Log分析PRACH与PUSCH发射功率

5G NR功率控制实战&#xff1a;从信令解析到问题定位的全流程指南 当基站侧监控到某小区边缘用户上行速率骤降50%时&#xff0c;我们首先在网管系统发现该区域UE的PUSCH发射功率普遍达到23dBm上限。这种典型的上行受限场景&#xff0c;往往需要网络优化工程师同时分析空口信令和…

作者头像 李华