深入理解 C++ 多态:从概念到实现的完整解析

多态是 C++ 面向对象编程的核心特性之一,它允许不同对象对同一行为做出不同响应,实现 "一个接口,多种实现" 的编程思想。无论是日常开发中的代码复用,还是面试中的高频考点,多态都占据着举足轻重的地位。本文将从概念入手,逐步拆解多态的实现条件、核心机制及实际应用,帮助读者构建完整的知识体系。


目录

一、多态的核心概念与生活实例

[1. 什么是多态?](#1. 什么是多态?)

[2. 生活中的多态场景](#2. 生活中的多态场景)

二、多态的实现条件

[1. 核心前提:虚函数与重写](#1. 核心前提:虚函数与重写)

[2. 调用方式:基类指针或引用](#2. 调用方式:基类指针或引用)

三、虚函数重写的特殊情况

[1. 协变规则](#1. 协变规则)

[2. 析构函数的重写](#2. 析构函数的重写)

(1)未使用虚函数的析构函数:

(2)重写的析构函数:

四、多态与其他函数关系的辨析

五、总结


一、多态的核心概念与生活实例

1. 什么是多态?

通俗来讲,多态就是 "同一行为,多种形态"。在编程中,它特指当使用基类的指针或引用调用虚函数时,实际执行的函数版本由指针或引用所指向的对象实际类型决定,而非声明类型。

2. 生活中的多态场景

  • 买票行为:普通人买全价票、学生买半价票、军人优先购票,同样是 "买票" 动作,不同身份的人执行结果不同。
  • 支付宝红包:新用户领取大额红包,老用户领取小额红包,同一 "领红包" 功能根据用户类型动态调整结果。
  • 动物叫声:猫叫 "喵喵",狗叫 "汪汪",同样是 "发声" 行为,不同动物表现出不同特征。

这些场景的共性的是:行为接口统一,但具体实现因主体不同而存在差异,这正是多态的核心思想。

二、多态的实现条件

要在 C++ 中实现多态,必须同时满足以下两个条件:

1. 核心前提:虚函数与重写

  • 虚函数 :在基类成员函数声明前添加virtual关键字,如virtual void BuyTicket() {},它是多态的基础。
  • 重写(覆盖) :派生类必须实现与基类虚函数 "三同" 的函数 ------ 函数名相同、参数列表相同、返回值类型相同(协变除外)。例:基类PersonBuyTicket()被派生类Student重写,实现半价购票逻辑。
cpp 复制代码
class Person
{
public:
	virtual void BuyTicket(){cout << "买票---全价" << endl;}
private:
	int _a;
};

class Student : public Person
{
public:
	virtual void BuyTicket() { cout << "买票---半价" << endl; }
};

2. 调用方式:基类指针或引用

必须通过基类的指针或引用调用虚函数,直接通过对象调用无法触发多态。

cpp 复制代码
class Person
{
public:
	virtual void BuyTicket(){cout << "买票---全价" << endl;}
private:
	int _a;
};
class Student : public Person
{
public:
	virtual void BuyTicket() { cout << "买票---半价" << endl; }
};
//满足多态的条件时 跟对象有关 指向那个对象就调用那个对象的虚函数
//不满足多态的条件时 跟类型有关 调用的类型是什么,调用的就是谁的虚函数
void Func(Person& p)
{
p.BuyTicket();
}
void Func(Person* p)
{
	p->BuyTicket();
}
int main()
{
	//cout << sizeof(Person) << endl;
	//cout << sizeof(Student) << endl;
	Person p;
	Student s;
	Func(p);
	Func(s);
	Func(&p);
	Func(&s);

	return 0;
}

三、虚函数重写的特殊情况

1. 协变规则

允许基类虚函数返回基类指针 / 引用,派生类虚函数返回派生类指针 / 引用,仍视为重写。示例:

cpp 复制代码
class A
{
};
class B :public A
{
};

class Person
{
public:
	virtual A* BuyTicket() { cout << "买票---全价" << endl; }
private:
	int _a;
};

class Student : public Person
{
public:
	virtual B* BuyTicket() { cout << "买票---半价" << endl; }
};

注意:只要基类虚函数的返回值与派生类的虚函数的返回值满足父子关系即可(即父类返回父类的子类返回子类的)

2. 析构函数的重写

编译器会将所有析构函数统一处理为destructor,因此只要基类析构函数是虚函数,派生类析构函数无需显式加virtual,也视为重写。这一特性至关重要,可避免通过基类指针删除派生类对象时的内存泄漏:

这一特性至关重要,可避免通过基类指针删除派生类对象时的内存泄漏:

(1)未使用虚函数的析构函数:

s实际为Student类型的变量,只是切割后赋值给Person指针,所以再使用delete时候,应该先析构子类再析构父类(因为开辟空间时 先构造了父类再构造的子类),但是delete s 的时候只析构了父类Person,因为该父子类的析构没有构成虚函数重新。

(2)重写的析构函数:

编译器会将所有析构函数统一处理为destructor,因此只要基类析构函数是虚函数,派生类析构函数无需显式加virtual,也视为重写。这一特性至关重要,可避免通过基类指针删除派生类对象时的内存泄漏:

四、多态与其他函数关系的辨析

很多开发者容易混淆重载、重写(覆盖)和隐藏,三者的核心区别如下:

特性 重载(overload) 重写(override) 隐藏(redefine)
作用域 同一类内部 基类与派生类之间 基类与派生类之间
核心条件 函数名相同,参数不同 三同 + 虚函数 函数名相同,不满足重写条件
调用规则 编译时根据参数匹配 运行时根据对象类型匹配 编译时根据声明类型匹配

示例对比:

cpp 复制代码
class Base {
public:
    virtual void Func(int x) { cout << "Base::Func(int)" << endl; } // 虚函数
    void Show() { cout << "Base::Show()" << endl; } // 普通函数
};

class Derived : public Base {
public:
    virtual void Func(int x) { cout << "Derived::Func(int)" << endl; } // 重写
    void Func(double x) { cout << "Derived::Func(double)" << endl; } // 重载(自身作用域)
    void Show() { cout << "Derived::Show()" << endl; } // 隐藏(非虚函数同名)
};

五、总结

多态的核心价值在于提高代码的灵活性和可扩展性,它通过虚函数表机制实现运行时动态绑定。理解多态的关键在于:明确重写的条件、掌握虚函数的特殊规则、区分易混淆的函数关系。下一篇文章将深入剖析多态的底层实现原理 ------ 虚函数表,带你从内存角度看透多态的本质。

希望这篇文章对你有帮助,如果你有任何问题或建议,欢迎在评论区留言。谢谢阅读!

相关推荐
じ☆冷颜〃2 小时前
分布式系统中网络技术的演进与异构融合架构(HFNA)
笔记·python·物联网·设计模式·架构·云计算
散峰而望4 小时前
【算法竞赛】C++函数详解:从定义、调用到高级用法
c语言·开发语言·数据结构·c++·算法·github
冷凝雨5 小时前
复数乘法(C & Simulink)
c语言·开发语言·信号处理·simulink·dsp
CoderCodingNo5 小时前
【GESP】C++五级真题(贪心思想考点) luogu-B4071 [GESP202412 五级] 武器强化
开发语言·c++·算法
我有一些感想……5 小时前
An abstract way to solve Luogu P1001
c++·算法·ai·洛谷·mlp
郭涤生5 小时前
第十章_信号_《UNIX环境高级编程(第三版)》_笔记
服务器·笔记·unix
0和1的舞者5 小时前
Spring AOP详解(一)
java·开发语言·前端·spring·aop·面向切面
MoonBit月兔5 小时前
年终 Meetup:走进腾讯|AI 原生编程与 Code Agent 实战交流会
大数据·开发语言·人工智能·腾讯云·moonbit
QT 小鲜肉5 小时前
【Linux命令大全】001.文件管理之which命令(实操篇)
linux·运维·服务器·前端·chrome·笔记
智航GIS5 小时前
8.2 面向对象
开发语言·python