深入理解 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; } // 隐藏(非虚函数同名)
};

五、总结

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

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

相关推荐
csbysj20202 小时前
Ruby 字符串(String)
开发语言
Highcharts.js2 小时前
学习 Highcharts 可视化开发的有效途径
学习·数据可视化·highcharts·图表开发·可视化开发
胡童嘉3 小时前
长沙烈焰鸟网络科技有限公司实习day12+软件测试学习day3日记
学习
基哥的奋斗历程3 小时前
Kotlin_Flow_完整使用指南
android·开发语言·kotlin
心之伊始3 小时前
Java synchronized 锁升级全过程深度解析:从 Mark Word 到偏向锁、轻量级锁与重量级锁的 HotSpot 实现
java·开发语言·word
谅望者3 小时前
数据分析笔记08:Python编程基础-数据类型与变量
数据库·笔记·python·数据分析·概率论
q***06293 小时前
搭建Golang gRPC环境:protoc、protoc-gen-go 和 protoc-gen-go-grpc 工具安装教程
开发语言·后端·golang
布丁写代码4 小时前
GESP C++ 一级 2025年09月真题解析
开发语言·c++·程序人生·学习方法
iiiiii114 小时前
【论文阅读笔记】多实例学习方法 Diverse Density(DD):在特征空间中寻找正概念的坐标
论文阅读·人工智能·笔记·机器学习·ai·学习方法·多实例学习