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

A / B测试太慢?AI帮你实时优化实验策略

A / B测试太慢?AI帮你实时优化实验策略

👋 大家好,欢迎来到我的技术博客! 📚 在这里,我会分享学习笔记、实战经验与技术思考,力求用简单的方式讲清楚复杂的问题。 🎯 本文将围绕AI这个话题展开,希望能为你带来一些启发或实用的参考。 🌱 无论你是刚入门的新手,还是正在进阶的开发者,希望你都能有所收获! 文章目录 * A/B测试太慢?AI帮你实时优化实验策略 🚀 * 为什么传统A/B测试成了效率黑洞? * AI驱动的实时优化:从“被动等待”到“主动决策” * 贝叶斯优化:AI决策的数学引擎 * 代理模型:预测点击率 * 采集函数:决定下一步策略 * 代码实战:用Python实现AI优化A/B测试 * 代码执行结果示例 * 实时决策流程:AI如何动态调整实验? * 实际业务场景:电商大促的AI优化案例 * 贝叶斯优化 vs 其他AI方法 * 如何在你的系统中落地AI优化? * 步骤1:构建基础数据层 * 步骤2:集成AI优化引擎 * 步骤3:设置停止条件 * 为什么AI优化能避免“实验陷阱”?

By Ne0inhk
人工智能:自然语言处理在教育领域的应用与实战

人工智能:自然语言处理在教育领域的应用与实战

人工智能:自然语言处理在教育领域的应用与实战 学习目标 💡 理解自然语言处理(NLP)在教育领域的应用场景和重要性 💡 掌握教育领域NLP应用的核心技术(如智能问答、作业批改、个性化学习) 💡 学会使用前沿模型(如BERT、GPT-3)进行教育文本分析 💡 理解教育领域的特殊挑战(如多学科知识、学生认知差异、数据隐私) 💡 通过实战项目,开发一个智能问答系统应用 重点内容 * 教育领域NLP应用的主要场景 * 核心技术(智能问答、作业批改、个性化学习) * 前沿模型(BERT、GPT-3)在教育领域的使用 * 教育领域的特殊挑战 * 实战项目:智能问答系统应用开发 一、教育领域NLP应用的主要场景 1.1 智能问答 1.1.1 智能问答的基本概念 智能问答是通过自然语言与用户进行交互,回答用户问题的程序。在教育领域,智能问答的主要应用场景包括: * 课程问答:回答课程相关的问题(如“什么是机器学习”

By Ne0inhk
AI工具泛滥时代,为什么“能力“越来越不值钱?

AI工具泛滥时代,为什么“能力“越来越不值钱?

文章目录 * 一、一个荒诞的现象:工具民主化与机会不平等 * 二、三个被误读的AI创业神话 * 三、AI创作者的真正壁垒:从"工具使用者"到"商业闭环构建者" * 四、给新手的实战建议:从0到1的行动清单 * 五、关于《脉向AI》栏目 * 六、适合谁看? 一、一个荒诞的现象:工具民主化与机会不平等 2025被称为"AI应用元年",但一个诡异的分化正在发生。 一方面,AI工具从未如此普及。ChatGPT、Midjourney、Claude、Sora、可灵、即梦……每个月都有新的"生产力神器"登上热搜。知识付费市场上,“AI副业课”" prompt工程&

By Ne0inhk
人工智能:自然语言处理在法律领域的应用与实战

人工智能:自然语言处理在法律领域的应用与实战

自然语言处理在法律领域的应用与实战 学习目标 💡 理解自然语言处理(NLP)在法律领域的应用场景和重要性 💡 掌握法律领域NLP应用的核心技术(如法律文本分类、实体识别、合同分析) 💡 学会使用前沿模型(如LegalBERT、LexGLUE)进行法律文本分析 💡 理解法律领域的特殊挑战(如专业术语、法律规范、数据稀缺) 💡 通过实战项目,开发一个合同分析应用 重点内容 * 法律领域NLP应用的主要场景 * 核心技术(法律文本分类、实体识别、合同分析) * 前沿模型(LegalBERT、LexGLUE)在法律领域的使用 * 法律领域的特殊挑战 * 实战项目:合同分析应用开发 一、法律领域NLP应用的主要场景 1.1 法律文本分类 1.1.1 法律文本分类的基本概念 法律文本分类是将法律文本划分到预定义类别的过程。在法律领域,法律文本分类的主要应用场景包括: * 判例分类:将判例分为不同的类别(如民事、刑事、行政) * 法律文件分类:

By Ne0inhk