C++三大核心特性之多态详解

 更新时间:2025年11月07日 08:25:53   作者:zzh_zao  
多态是⼀个继承关系的下的类对象,去调⽤同⼀函数,产⽣了不同的⾏为,这篇文章主要介绍了C++三大核心特性之多态的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下

什么是多态?

多态(Polymorphism)是面向对象编程的三大核心特性之一(封装、继承、多态),源于希腊语"多种形态"。在C++中,它允许我们使用统一的接口处理不同类型的对象,显著提高了代码的灵活性和可扩展性。

核心概念

  1. 同一接口,多种形态
    不同的对象可以通过相同的方法名调用,但实际执行的逻辑由对象自身的类决定。
  2. 解耦调用与实现
    调用者只需关注接口(方法名和参数),无需关心具体实现,提高代码的可扩展性和可维护性。

多态的定义及实现

多态的构成条件

多态是⼀个继承关系的下的类对象,去调⽤同⼀函数,产⽣了不同的⾏为。
实现多态还有两个必须重要条件:

  1. 必须是基类的指针或者引⽤调⽤虚函数

  2. 被调⽤的函数必须是虚函数,并且完成了虚函数重写/覆盖。说明:要实现多态效果,第⼀必须是基类的指针或引⽤,因为只有基类的指针或引⽤才能既指向基类 对象⼜指向派⽣类对象;第⼆派⽣类必须对基类的虚函数完成重写/覆盖,重写或者覆盖了,基类和派 ⽣类之间才能有不同的函数,多态的不同形态效果才能达到。

虚函数

类成员函数前⾯加virtual修饰,那么这个成员函数被称为虚函数。注意⾮成员函数不能加virtual修饰。

class A
{

public:
 virtual void() {}
};

虚函数的重写/覆盖

虚函数的重写/覆盖:派⽣类中有⼀个跟基类完全相同的虚函数(即派⽣类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称派⽣类的虚函数重写了基类的虚函数。

注意:在重写基类虚函数时,派⽣类的虚函数在不加virtual关键字时,也可以构成重写(因为继承后基类的虚函数被继承下来了在派⽣类依旧保持虚函数属性)

关键技术原理

  1. 虚函数表(vtable)

    • 每个包含虚函数的类自动生成虚函数表
    • 存储该类所有虚函数的地址
    • 创建对象时隐式添加vptr指针指向vtable
  2. 动态绑定过程

最佳实践指南

  1. 使用override关键字明确重写意图

  2. 基类析构函数必须声明为virtual

  3. 接口类使用纯虚函数

  4. 性能考量:虚函数调用比普通函数多一次指针解引用

虚函数重写

协变

派⽣类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引⽤,派⽣类虚函数返回派⽣类对象的指针或者引⽤时,称为协变。核心概念

  1. 基类虚函数返回基类类型指针 / 引用。
  2. 派生类重写函数返回派生类类型指针 / 引用。
  3. 编译器会自动处理类型转换,确保调用的一致性。

协变的条件

  1. 基类函数必须为虚函数。
  2. 返回类型必须是指针或引用(不能是值类型)。
  3. 派生类返回类型必须是基类返回类型的公有派生类。
  4. 函数签名的其他部分(参数、常量性)必须完全相同。

析构函数的重写

基类的析构函数为虚函数,此时派⽣类析构函数只要定义,⽆论是否加virtual关键字,都与基类的析构函数构成重写,虽然基类与派⽣类析构函数名字不同看起来不符合重写的规则,实际上编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统⼀处理成destructor,所以基类的析构函数加了vialtual修饰,派⽣类的析构函数就构成重写。

class A

{

public:
 virtual ~A()
 {
 cout << "~A()" << endl;
 }
};

class B : public A {

public:
 ~B()
 { 
 cout << "~B()->delete:"<<_p<< endl;
 delete _p;
 }

protected:
 int* _p = new int[10];
};

int main()
{
 A* p1 = new A;
 A* p2 = new B;
 delete p1;
 delete p2;
 return 0;
}

如果~A(),不加virtual,那么delete p2时只调⽤的A的析构函数,没有调⽤B的析构函数,就会导致内存泄漏问题,因为 ~B()中在释放资源。

只有派⽣类Student的析构函数重写了Person的析构函数,下⾯的delete对象调⽤析构函数,才能构成多态,才能保证p1和p2指向的对象正确的调⽤析构函数。

override和final关键字

  1. override 关键字
    作用
    明确标记一个函数是重写基类的虚函数,编译器会检查以下条件:
    基类中是否存在同名虚函数。
    基类和派生类的函数签名(参数、常量性、引用限定符)是否严格一致。
    返回类型是否符合协变规则(如果适用)。
  2. final 关键字
    作用
    final 有两种用法:
    修饰虚函数:禁止派生类重写该虚函数。
    修饰类:禁止该类被继承。

纯虚函数和抽象类

在虚函数的后⾯写上=0,则这个函数为纯虚函数,纯虚函数不需要定义实现(实现没啥意义因为要被派⽣类重写,但是语法上可以实现),只要声明即可。包含纯虚函数的类叫做抽象类,抽象类不能实例化出对象,如果派⽣类继承后不重写纯虚函数,那么派⽣类也是抽象类。纯虚函数某种程度上强制了派⽣类重写虚函数,因为不重写实例化不出对象

  • 纯虚函数:在基类中声明为virtual,并且没有实现的函数,形式为virtual void func() = 0;
  • 抽象类:包含纯虚函数的类称为抽象类,不能实例化对象,只能作为基类被派生。
  • 示例:
    class Shape {
    public:
        virtual void draw() = 0; // 纯虚函数
    };
    
    class Circle : public Shape {
    public:
        void draw() override {
            cout << "Drawing Circle" << endl;
        }
    };
    
    • Shape是一个抽象类,Circle是派生类,重写了draw()函数。
    • 多态的使用:
      Shape* shape = new Circle();
      shape->draw(); // 输出:Drawing Circle
      

多态的原理

多态是如何实现的

在C++中,多态主要通过虚函数(virtual functions)和继承机制来实现。以下是多态实现的详细过程和原理:

1. 虚函数表(vtable)

  • 概念:每个包含虚函数的类都会有一个虚函数表(vtable),它是一个存储虚函数地址的数组。当创建一个类的对象时,对象会包含一个指向其类的虚函数表的指针(vptr)。
  • 作用:vtable是实现多态的关键。当通过基类指针或引用调用虚函数时,程序会通过vptr找到对应的vtable,然后通过vtable找到实际的虚函数地址。

虚函数表知识要点

  1. 基类对象的虚函数表中存放基类所有虚函数的地址。同类型的对象共⽤同⼀张虚表,不同类型的对象各⾃有独⽴的虚表,所以基类和派⽣类有各⾃独⽴的虚表。
  2. 派⽣类由两部分构成,继承下来的基类和⾃⼰的成员,⼀般情况下,继承下来的基类中有虚函数表指针,⾃⼰就不会再⽣成虚函数表指针。但是要注意的这⾥继承下来的基类部分虚函数表指针和基类对象的虚函数表指针不是同⼀个,就像基类对象的成员和派⽣对象中的基类对象成员也独⽴的。
  3. 派⽣类中重写的基类的虚函数,派⽣类的虚函数表中对应的虚函数就会被覆盖成派⽣类重写的虚函数地址。
  4. 派⽣类的虚函数表中包含,(1)基类的虚函数地址,(2)派⽣类重写的虚函数地址完成覆盖,派⽣类⾃⼰的虚函数地址三个部分。
  5. 虚函数表本质是⼀个存虚函数指针的指针数组,⼀般情况这个数组最后⾯放了⼀个0x00000000标记。(此条取决于编译器)
  6. 虚函数存储的位置(vs下为常量区)
    可以自行验证:
class Base {

public:
 virtual void func1() {}

protected:
 int a = 1;
};

class Derive : public Base
{
public:
 virtual void func1() {}
protected:
 int b = 2;
};

int main()
{
 int i = 0;
 static int j = 1;
 int* p1 = new int;
 const char* p2 = "xxxxxxxx";
 printf("栈:%p\n", &i);
 printf("静态区:%p\n", &j);
 printf("堆:%p\n", p1);
 printf("常量区:%p\n", p2);
 Base b;
 Base* p3 = &b;
 printf("虚表地址:%p\n", *(int*)p3);
 return 0;
}
//通过比较虚表地址,找到其最近的内存片段来确定

2. 虚函数的声明

  • 在类中使用virtual关键字声明函数为虚函数。
    Base为基类,Derived为继承类。
    class Base {
    public:
        virtual void show() {
            cout << "Base show()" << endl;
        }
    };
    
    class Derived : public Base {
    public:
        void show() override { // override表示重写基类的虚函数
            cout << "Derived show()" << endl;
        }
    };
    
    • Base类的show()函数被声明为虚函数,Derived类重写了这个虚函数。

3. 多态的实现过程

  • 创建对象
    Derived d;
    
    • 当创建Derived类的对象d时,对象中会包含一个vptr,指向Derived类的vtable。
    • Derived类的vtable中存储了Derived类重写的虚函数show()的地址。
  • 通过基类指针或引用调用虚函数
    Base* ptr = &d;
    ptr->show();
    
    • ptr是一个指向Base类的指针,但它指向的是Derived类的对象。
    • 当调用ptr->show()时,程序会通过ptr找到对象的vptr,然后通过vptr找到Derived类的vtable,最终调用Derived类的show()函数。
    • 这就是多态的运行时绑定(动态绑定)过程。

动态绑定与静态绑定

• 对不满⾜多态条件(指针或者引⽤+调⽤虚函数)的函数调⽤是在编译时绑定,也就是编译时确定调⽤函数的地址,叫做静态绑定。

• 满⾜多态条件的函数调⽤是在运⾏时绑定,也就是在运⾏时到指向对象的虚函数表中找到调⽤函数的地址,也就做动态绑定。

总结

到此这篇关于C++三大核心特性之多态详解的文章就介绍到这了,更多相关C++多态详解内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C++实现统计代码运行时间的示例详解

    C++实现统计代码运行时间的示例详解

    这篇文章主要为大家详细介绍了C++一个有趣的小项目——统计代码运行时间,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2023-05-05
  • C/C++实现对STORM运行信息查看及控制的方法

    C/C++实现对STORM运行信息查看及控制的方法

    这篇文章主要介绍了C/C++实现对STORM运行信息查看及控制的方法,需要的朋友可以参考下
    2014-07-07
  • 手把手带你了解C++最小栈

    手把手带你了解C++最小栈

    这篇文章主要介绍了C++的最小栈,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-08-08
  • c++中log4cplus日志库使用的基本步骤和示例代码

    c++中log4cplus日志库使用的基本步骤和示例代码

    这篇文章主要给大家介绍了关于c++中log4cplus日志库使用的相关资料,log4cplus是一款开源的c++日志库,具有线程安全,灵活,以及多粒度控制的特点,log4cplus可以将日志按照优先级进行划分,使其可以面向程序的调试,运行,测试,后期维护等软件全生命周期,需要的朋友可以参考下
    2024-06-06
  • C语言实现顺序循环队列实例

    C语言实现顺序循环队列实例

    大家好,本篇文章主要讲的是C语言实现顺序循环队列实例,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下
    2022-02-02
  • C语言实现简单的聊天室功能

    C语言实现简单的聊天室功能

    这篇文章主要为大家详细介绍了C语言实现简单的聊天室功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-06-06
  • c++中关系运算符重载的实现示例

    c++中关系运算符重载的实现示例

    C++中关系运算符重载允许自定义类型使用关系运算符,重载规则包括可重载的运算符、重载方式、返回值类型、对称性、const修饰等,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2026-02-02
  • C++高性能服务器框架之线程模块

    C++高性能服务器框架之线程模块

    该模块基于pthread实现,sylar说,由于c++11中的thread也是由pthread封装实现的,并且没有提供读写互斥量,读写锁,自旋锁等,所以自己封装了pthread,本文主要详细介绍了C++高性能服务器框架中的线程模块,需要的朋友可以参考下
    2023-06-06
  • C++中set/multiset容器详解(附测试用例与结果图)

    C++中set/multiset容器详解(附测试用例与结果图)

    set/multiset属于关联式容器,底层结构是用二叉树实现,下面这篇文章主要给大家介绍了关于C++中set/multiset容器的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-02-02
  • wchar_t,char,string,wstring之间的相互转换

    wchar_t,char,string,wstring之间的相互转换

    以下是对wchar_t,char,string,wstring之间的相互转换进行了详细的分析介绍,需要的朋友可以过来参考下,希望对大家有所帮助
    2013-09-09

最新评论