C++_多态

C++_多态

多态的概念

通俗来讲,就是多种形态。多态分为编译时多态(静态多态)运行时多态(动态多态)

编译时多态主要就是函数重载函数模板,他们传不同类型的参数就可以调用不同的函数,通过参数不同达到多种形态,之所以叫编译时多态,是因为他们实参传给形参的参数匹配是在编译时完成的,编译时一般归为静态,运行时一般归为动态。

运行时多态,具体点就是去完成某个行为,可以传不同的对象就会完成不同的行为,就达到多种形态。比如买票这个行为,普通人是全价买票;学生是优惠买票;军人是优先买票。再比如动物叫,小猫是喵喵🐱,小狗是汪汪🐶。

多态的定义及实现

多态的构成条件

多态是一个继承关系下的类对象,去调用同一函数,产生了不同的行为。比如Student继承Person,前者优惠买票,后者全价买票。

实现多态的两个重要条件

  • 必须用指针或者引用调用函数
  • 被调用的函数必须是虚函数(virtual)

要实现多态效果,

  1. 必须是基类的指针或引用,因为只有基类的指针或引用才能既指向派生类对象,也指向基类对象。
  2. 派生类必须对基类的虚函数重写/覆盖,重写/覆盖了,派生类才能有不同的函数,多态的不同形态效果才能达到。

在这里插入图片描述

虚函数

类成员函数前面加virtual修饰,那么这个成员函数被称为虚函数。

非成员函数不能加virtual修饰。

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

虚函数的重写/覆盖

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

在重写虚函数时,派生类的虚函数可以不加virtual,但不建议。(继承后基类的虚函数被继承下来在派生类中依旧保持虚函数属性)

class Person {
public:virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:virtual void BuyTicket() { cout << "买票-打折" << endl; }
};
void Func(Person* ptr)
{// 这里可以看到虽然都是Person指针ptr在调用BuyTicket// 但是跟ptr没关系,而是由ptr指向的对象决定的ptr->BuyTicket();
} 
int main()
{Person ps;Student st;Func(&ps);Func(&st);return 0;
}
class Animal
{ 
public:virtual void talk() const{}
};
class Dog : public Animal
{ 
public:virtual void talk() const{std::cout << "汪汪" << std::endl;}
};
class Cat : public Animal
{ 
public:virtual void talk() const{std::cout << "(>^ω^<)喵" << std::endl;}
};
void letsHear(const Animal& animal)
{animal.talk();
} 
int main()
{Cat cat;Dog dog;letsHear(cat);letsHear(dog);return 0;
}

一个选择题

下面的程序输出结果是什么()

A: A->0 B: B->1 C: A->1 D: B->0

class A
{ 
public:virtual void func(int val = 1){ std::cout << "A->" << val << std::endl;}virtual void test(){ func();}
};
class B : public A
{
public:void func(int val = 0){ std::cout << "B->" << val << std::endl; }
};
int main(int argc ,char* argv[])
{B*p = new B;p->test();return 0;
}

虚函数重写的问题

  • 协变

派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或引用,派生类虚函数返回派生类对象的指针或引用时,称为协变。

class A {};
class B : public A {};class Person {
public:virtual A* BuyTicket(){cout << "买票-全价" << endl;return nullptr;}
};class Student : public Person {
public:virtual B* BuyTicket(){cout << "买票-打折" << endl;return nullptr;}
};void Func(Person* ptr)
{ptr->BuyTicket();
} int main()
{Person ps;Student st;Func(&ps);Func(&st);return 0;
}
  • 析构函数的重写

基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写。

虽然基类和派生类析构函数的名字不同,看起来不符合重写规则,但实际上编译器对析构函数的名称做了特殊化处理,编译后的析构函数名称统一处理成destructor,所以基类的析构函数加了virtual,派生类的析构函数就构成重写。

下面的代码我们看到,如果~A()不加virtual,那么delete p2时只调用了A的析构函数,没有调用B的析构函数,就会导致内存泄漏问题,因此在~B()中释放资源。

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];
}// 只有派生类Student的析构函数重写了Person的析构函数,下面的delete对象调用析构函数
// 才能构成多态,才能保证p1和p2指向的对象正确的调用析构函数
int main()
{A* p1 = new A;A* p2 = new B;delete p1;delete p2;return 0;
}

override 和 final 关键字

C++对函数重写的要求比较严格,但是有些情况下由于疏忽,比如函数名写错参数写错等导致无法构成重载,而这种错误在编译阶段是不会报错的,只有在程序运行时没有得到预期结果,再去debug就得不偿失了。

因此C++11提供了关键字override,可以帮助用户检查是否重写,如果我们不想让派生类去重写这个虚函数,则需要final关键字。

// error C3668: “Benz::Drive”: 包含重写说明符"override"的方法没有重写任何基类方法
class Car {
public:virtual void Dirve(){}
};
class Benz :public Car {
public:virtual void Drive() override{ cout << "Benz-舒适" << endl;}
};
int main()
{return 0;
}
// error C3248: “Car::Drive”: 声明为"final"的函数无法被"Benz::Drive"重写
class Car
{ 
public:virtual void Drive() final {}
};
class Benz :public Car
{ 
public:virtual void Drive() {cout << "Benz-舒适" << endl; }
};
int main()
{return 0;
}

重载/重写/隐藏 的对比

在这里插入图片描述

纯虚函数和抽象类

在虚函数的后面写上=0,则这个函数为纯虚函数,纯虚函数不需要定义实现(实现没啥意义,因为要被派生类重写,但是语法上可以实现),只要声明即可。

包含纯虚函数的类为抽象类,抽象类不能实例化出对象,如果派生类继承后不重写纯虚函数,则派生类也为抽象类。

纯虚函数某种程度上强制了派生类重写虚函数,因为不重写则无法实例化出对象。

class Car
{ 
public:virtual void Drive() = 0;
};class Benz :public Car
{ 
public:virtual void Drive(){cout << "Benz-舒适" << endl;}
}class BMW :public Car
{ 
public:virtual void Drive(){cout << "BMW-操控" << endl;}
};
int main()
{// 编译报错:error C2259: “Car”: 无法实例化抽象类Car car;Car* pBenz = new Benz;pBenz->Drive();Car* pBMW = new BMW;pBMW->Drive();return 0;
}

多态的原理

虚函数表指针

下面程序在32位环境下运行结果是什么()

A.编译报错 B.运行报错 C.8 D.12

class Base
{ 
public:virtual void Func1(){cout << "Func1()" << endl;}
protected:int _b = 1;char _ch = 'x';
};
int main()
{Base b;cout << sizeof(b) << endl;return 0;
}

答案为12bytes,除了_b_ch成员还多了一个__vfptr放在对象前(也可能放在对象后,与编译器有关)。

对象中的这个指针我们叫做虚函数表指针,一个含有虚函数的类中都至少含有一个虚函数表指针,⼀个类所有虚函数的地址都要被放到这个类对象的虚函数表中,虚函数表也简称虚表。

在这里插入图片描述

多态是如何实现的

从底层的角度Func函数中ptr->BuyTicket(),是如何作为ptr指向Person对象调用Person::BuyTicketptr指向Student对象调用Student::BuyTicket的呢?通过下图我们可以看到,满足多态条件后,底层不再是编译时通过调用对象确定函数的地址,而是运行时到指向的对象的虚表中确定对应的虚函数的地址,这样就实现了指针或引用指向基类就调用基类的虚函数,指向派⽣类就调用派生类对应的虚函数。第⼀张图,ptr指向的Person对象,调用的是Person的虚函数;第⼆张图,ptr指向的tudent对象,调用的是Student的虚函数。

在这里插入图片描述

class Person {
public:virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:virtual void BuyTicket() { cout << "买票-打折" << endl; }
};
class Soldier: public Person {
public:virtual void BuyTicket() { cout << "买票-优先" << endl; }
};
void Func(Person* ptr)
{// 这里可以看到虽然都是Person指针ptr在调用BuyTicket// 但是跟ptr没关系,而是由ptr指向的对象决定的ptr->BuyTicket();
} 
int main()
{// 其实多态不仅仅发生在派生类对象之间,多个派生类继承基类,重写虚函数后// 多态也会发生在多个派生类之间Person ps;Student st;Soldier sr;Func(&ps);Func(&st);Func(&sr);return 0;
}

动态绑定和静态绑定

  • 对不满足多态条件的函数调用是在编译时绑定,也就是编译时确定调用函数的地址,叫做静态绑定
  • 满足多态条件的函数调用是在运行时绑定,也就是在运行时到指向对象的虚函数表中找到调用函数的地址,也就是动态绑定
	// ptr是指针+BuyTicket是虚函数 满足多态条件// 这里就是动态绑定,编译在运行时到ptr指向对象的虚函数表中确定调用函数地址ptr->BuyTicket();
00EF2001 mov 	eax,dword ptr [ptr]
00EF2004 mov 	edx,dword ptr [eax]
00EF2006 mov 	esi,esp
00EF2008 mov 	ecx,dword ptr [ptr]
00EF200B mov 	eax,dword ptr [edx]
00EF200D call 	eax// BuyTicket不是虚函数 不满足多态条件// 这里就是静态绑定,编译器直接确定调用函数地址ptr->BuyTicket();
00EA2C91 mov 	ecx,dword ptr [ptr]
00EA2C94 call 	Student::Student (0EA153Ch)

虚函数表

  • 基类对象的虚函数表中存放着基类所有虚函数的地址
  • 派生类由两部分构成,继承下来的基类和自己的成员。一般情况下,继承下来的基类中有虚函数表指针,自己就不会再生成虚函数表指针。但是要注意,这里继承下来的虚函数表指针和基类对象的虚函数表指针并非同一个,就像基类对象的成员和派生类对象中的基类对象成员都独立存在一样
  • 派生类中重写的基类的虚函数,派生类的虚函数表指针中的虚函数地址就会被覆盖成重写后的虚函数地址
  • 派生类的虚函数表中包括:基类的虚函数地址,派生类重写的虚函数地址,派生类自己的虚函数地址
  • 虚函数表本质是一个存放虚函数指针的指针数组,一般情况下这个数组最后放了一个0x00000000的标志。(由编译器决定)
  • 虚函数也存在代码段,只是地址被放到了虚函数表中
  • 虚函数表存在的位置C++并没有规定,但在vs中存放于代码段

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/146761.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

简单有效关于msvcp140.dll丢失的解决方法,msvcp140.dll

这篇文章将和大家分享几种msvcp140.dll丢失的解决方法&#xff0c;分析解决方法为什么能够通过这种方法进行修复成功&#xff0c;有效的将丢失的msvcp140.dll文件进行修复完成。 msvcp140.dll丢失&#xff1f;简单有效的解决途径 一、重新安装相关软件 原理 许多应用程序在安…

研究生数学建模竞赛E题思路模型参考文献高速公路应急车道紧急启用模型——高速公路饱和路段动态应急车道开放决策模型研究

1 概述 动态应急车道 ( Hard Shoulder Running ) 作为 调整路段交通流运行现状的重要管理手段&#xff0c; 可以在短时间内提供道路供给&#xff0c; 有效提升瓶颈路段的通行 能力。 早在 21 世纪初 &#xff0c; 欧美国家就已经通过开放 应急车道缓解的方式来解决路段的交…

2024最受人追捧的电脑远程控制软件推荐!首选这五款!好用、连接稳定、安全性高!

在2024年&#xff0c;电脑远程控制软件市场上涌现了众多受欢迎且功能强大的选择。 以下是根据最新信息推荐的五款好用、连接稳定、安全性高的电脑远程控制软件&#xff1a; 1. 安企神 特点&#xff1a;它是全球知名的远程控制软件&#xff0c;以其稳定性和可靠性著称。 它支…

C++——初步认识C++和namespace的用法

1.编程语言排行榜 我们通过排行可以看出 C在变成语言中还是占据着重要的地位 2.C在工作领域中的应用 1.PC客户端开发。⼀般是开发Windows上的桌面软件&#xff0c;比如WPS之类的&#xff0c;技术栈的话⼀般是C和 QT&#xff0c;QT 是⼀个跨平台的 C图形用户界面&#xff08;G…

sourceTree使用脚本一键push代码到gerrit

问题 在gerrit,我们无法直接把代码push到对应的分支。需要把代码push到 HEAD:refs/for/branch,review通过后再submit到分支。所以无法直接使用sourceTree上的推送按钮来push代码。但是可以通过自定义操作和脚本来实现这一功能。 脚本编写 新建文本文档写入以下内容&#xff…

fmql之ubuntu添加dhcp服务

按照官方指示&#xff1a;【在文末】 2024-08-22 buildroot linux 使用wpa_supplicant -B -i wlan0 切换WIFI 设备之后无法上网的问题。解决方法&#xff0c;使用udhcpc -i wlan0 命令-CSDN博客 网口连接路由器&#xff0c;然后发现路由器分配了ip&#xff0c;但是板卡没有配置…

VulnHub-Narak靶机笔记

Narak靶机笔记 概述 Narak是一台Vulnhub的靶机&#xff0c;其中有简单的tftp和webdav的利用&#xff0c;以及motd文件的一些知识 靶机地址&#xff1a; https://pan.baidu.com/s/1PbPrGJQHxsvGYrAN1k1New?pwda7kv 提取码: a7kv 当然你也可以去Vulnhub官网下载 一、nmap扫…

写作练习(一)

一、reply Z-Library The aim is to express gratitude and practice writing, and as a record. 二、Original letter As a college student of computer and a blogger, Z-Library is always a part of my study, which provide many books that I need pay a lost of time …

安捷伦Agilent/keysight 53220A参数资料 通用频率计 计数器

Agilent 53220A&#xff0c;Keysight 53220A&#xff0c;通用频率计数器/计时器&#xff0c;350 MHz&#xff0c;12 位&#xff0c;100 ps 53220A 350 MHz 通用频率计数器/计时器是一款双通道频率计数器&#xff0c;能够执行所需的全部频率和时间间隔测量。它可以添加可选的射…

突破常规:如何利用动态系统思维彻底变革你的团队!

引言 在现代社会中&#xff0c;变化是唯一不变的。面对快速发展的科技和瞬息万变的市场环境&#xff0c;企业和开发团队必须具备适应性和灵活性。动态系统思维作为一种理解和应对复杂系统中变化的方法&#xff0c;提供了有效的解决方案。本文将探讨动态系统思维在敏捷方法中的应…

load jsonl File with OpenAI API request results to pandas data.frame

题意&#xff1a;将包含 OpenAI API 请求结果的 jsonl 文件加载到 pandas DataFrame 中 问题背景&#xff1a; I have a large data set containing around 500k observation. It has a string variable that I want to create an embedding for. I used the OpenAI API to cr…

AI入门系列 | 如何优雅地下载最前沿的模型?

​简介 一片白云横谷口&#xff0c;几多归鸟尽迷巢。 小伙伴们好&#xff0c;我是微信公众号《小窗幽记机器学习》的小编&#xff1a;卖铁观音的小男孩。本系列主要基于过往经历&#xff0c;总结当时自身环境中实操经验。倘若能够顺便帮到他人&#xff0c;也是善莫大焉。 本文…

[已更新]2024数学建模研赛华为杯E题详细思路代码成品文章研究生数学建模数模辅导

截止2024.8.21 12点 已更新e全部小问的建模和问题一的代码 ####https://docs.qq.com/doc/DVU9YYUFLWlNOY3pyE题: 问题1&#xff1a;统计四个观测点的交通流参数随时间的变化规律 为了统计交通流参数&#xff08;如车流密度、流量和速度&#xff09;&#xff0c;首先需要从视…

猫咪检测系统源码分享

猫咪检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vision …

Easypoi模板导出

Easypoi模板导出 优点 快速导出&#xff1a;使用预定义的Excel模板&#xff0c;可以快速导出数据&#xff0c;特别是在数据量大的情况下。简化代码&#xff1a;通过模板导出&#xff0c;减少了编写大量POI代码的需要&#xff0c;使得代码更简洁易懂。灵活性&#xff1a;模板可…

Camunda如何共享流程定义但不共享流程实例?

文章目录 一、项目场景二、问题描述三、解决方案方案一&#xff1a;官方实例化共享定义的方法1. 部署共享定义2. 在查询中包含共享的定义3. 实例化共享定义 方案二&#xff1a;自定义TenantIdProvider方法&#xff0c;将租户id作为变量添加到启动的实例中1. 采用自定义的Tenant…

PHP API 框架:构建高效API的利器

在当今快速发展的互联网时代&#xff0c;API&#xff08;应用程序编程接口&#xff09;已成为连接不同应用程序和服务的关键。PHP&#xff0c;作为一种流行的服务器端脚本语言&#xff0c;提供了多种强大的框架来简化API的开发。本文将介绍PHP API框架的重要性&#xff0c;以及…

Linux笔记---简单指令

1. 使用的环境 博主使用的是华为云服务器xshell终端的方式学习的&#xff0c;因为据说这样的方式比较接近以后的工作环境。 其中云服务器安装的是Ubuntu操作系统(以Linux为内核&#xff0c;适合新手学习Linux的一个版本) 这里的云服务器不一定使用华为的&#xff0c;但是我在…

论文推荐——犹豫直觉模糊偏好关系积性一致性及其在群决策中的应用

犹豫直觉模糊偏好关系积性一致性及其在群决策中的应用 论文全文 论文全文 论文最巧妙的地方就是修正了积性一致性的条件&#xff0c;使得修复方法完全满足互补条件&#xff0c;也算对大佬的工作做了一个很好的修补。

PVE8最新安装使用指南、优化Proxmox VE 8.1

PVE镜像下载 1、在浏览器输入以下网址下载pve的iso安装镜像。 Download Proxmox software, datasheets, agreements 2、制作系统引导盘 这里使用rufus工具将ISO文件刻录进U盘 rufus下载地址:https://wwf.lanzoul.com/i72bm24j105c 密码:5k8t 下载完成后直接双击运行&…