C++之多态

C++之多态

多态

在这里插入图片描述

什么是多态?

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

核心概念

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

多态的定义及实现

多态的构成条件

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

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

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

class A { public: virtual void(){}};
虚函数的重写/覆盖

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

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

关键技术原理
  1. 虚函数表(vtable)
    • 每个包含虚函数的类自动生成虚函数表
    • 存储该类所有虚函数的地址
    • 创建对象时隐式添加vptr指针指向vtable
  2. 动态绑定过程基类指针查询对象vptr访问vtable调用实际函数地址
最佳实践指南
  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];};intmain(){ A* p1 = new A; A* p2 = new B; delete p1; delete p2;return0;}

如果~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;
  • 抽象类:包含纯虚函数的类称为抽象类,不能实例化对象,只能作为基类被派生。
    • Shape是一个抽象类,Circle是派生类,重写了draw()函数。

多态的使用:

Shape* shape =newCircle(); shape->draw();// 输出:Drawing Circle

示例:

classShape{public:virtualvoiddraw()=0;// 纯虚函数};classCircle:publicShape{public:voiddraw()override{ cout <<"Drawing Circle"<< endl;}};

多态的原理

多态是如何实现的

在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 voidfunc1(){} protected:int a =1;}; class Derive : public Base { public: virtual voidfunc1(){} protected:int b =2;};intmain(){int i =0;staticint j =1;int* p1 = new int;constchar* 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);return0;}//通过比较虚表地址,找到其最近的内存片段来确定

2. 虚函数的声明

    • Base类的show()函数被声明为虚函数,Derived类重写了这个虚函数。

在类中使用virtual关键字声明函数为虚函数。
Base为基类,Derived为继承类。

classBase{public:virtualvoidshow(){ cout <<"Base show()"<< endl;}};classDerived:publicBase{public:voidshow()override{// override表示重写基类的虚函数 cout <<"Derived show()"<< endl;}};

3. 多态的实现过程

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

通过基类指针或引用调用虚函数

Base* ptr =&d; ptr->show();

创建对象

Derived d;
动态绑定与静态绑定

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

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

Read more

C++:模板的幻觉 —— 实例化、重定义与隐藏依赖势中

C++:模板的幻觉 —— 实例化、重定义与隐藏依赖势中

一、表象之下:模板真的“生成代码”吗? 很多人第一次学 C++ 模板时,会这样理解: “模板是一种代码生成机制,编译器在编译时会根据不同类型生成不同版本的函数或类。” 乍一看没错,比如: template<typename T> void print(T x) { std::cout << x << std::endl; } int main() { print(42); print("Hello"); } 似乎编译器确实“生成了两份函数”: print<int>(int) 与 print<const

By Ne0inhk
【C++贪心】P8769 [蓝桥杯 2021 国 C] 巧克力|普及+

【C++贪心】P8769 [蓝桥杯 2021 国 C] 巧克力|普及+

本文涉及知识点 C++贪心 [蓝桥杯 2021 国 C] 巧克力 题目描述 小蓝很喜欢吃巧克力,他每天都要吃一块巧克力。 一天小蓝到超市想买一些巧克力。超市的货架上有很多种巧克力,每种巧克力有自己的价格、数量和剩余的保质期天数,小蓝只吃没过保质期的巧克力,请问小蓝最少花多少钱能买到让自己吃 x x x 天的巧克力。 输入格式 输入的第一行包含两个整数 x x x, n n n,分别表示需要吃巧克力的天数和巧克力的种类数。 接下来 n n n 行描述货架上的巧克力,其中第 i i i 行包含三个整数 a i a_i ai , b i b_i bi

By Ne0inhk

揭秘C++26新特性:CPU亲和性控制如何让多线程性能飙升(专家级指南)

第一章:C++26 CPU亲和性与性能优化概述 在高性能计算和实时系统开发中,CPU亲和性控制成为提升程序执行效率的关键技术之一。C++26标准正在积极引入对硬件资源调度的底层支持,允许开发者通过标准化接口绑定线程到特定CPU核心,从而减少上下文切换开销、提高缓存命中率,并优化多核并行任务的执行性能。 为何关注CPU亲和性 * 降低线程迁移带来的缓存失效问题 * 增强实时应用的可预测性与响应速度 * 配合NUMA架构实现内存访问局部性优化 标准库中的预期接口设计 虽然C++26尚未最终定稿,但委员会提案P2173R4建议引入std::execution_context与std::set_affinity等设施。未来可能的用法如下: #include <thread> #include <execution> int main() { std::jthread worker([](std::stop_token st) { // 将当前线程绑定到CPU核心0 std::set_affinity(std::this_thread::get_

By Ne0inhk
C++杂说——命名空间,输入与输出,缺省参数,make/makefile

C++杂说——命名空间,输入与输出,缺省参数,make/makefile

希望你开心,希望你健康,希望你幸福,希望你点赞! 最后的最后,关注喵,关注喵,关注喵,大大会看到更多有趣的博客哦!!! 喵喵喵,你对我真的很重要! 命名空间 命名空间编译默认查找顺序: a、当前局部域 : 自留地 b、全局域找 : 村子野地 c, 不会到其他命名空间中去找 : 隔壁张大爷自留地 命名空间展开三种 1、指定访问 2、全展开 //// 展开命名空间 //using namespace bit; //using namespace xjh; 3、指定展开某一个(经常使用,可以展开它一个) // 指定展开某一个 //using bit::x; 命名空间可以为: // 局部域 // 全局域 // 命名空间域 // 不同域可以定义同名的变量/函数/类型 两个私有的命名空间最好不要同时展开,

By Ne0inhk