第十一站:C++面向对象-多态

为什么要使用多态

当定义的子类继承父类并重写父类的方法后,

父类使用指针调用子类的同名方法,得到的却是父类同名方法的结果 

#include <iostream>
using namespace std;
class Father
{
public:void play() {cout << "一起去KTV唱歌吧" << endl;};
};class Son :public Father {
public:void play() {cout << "一起去打游戏吧!" << endl;};
};void testPlay(Father** man, int n) {for (int i = 0; i < n; i++) {man[i]->play();//man[i] = *man;}
}
int main(void) {Father father;Son son1, son2;Father* p[3] = { &father,&son1,&son2 };int len = sizeof(p) / sizeof(p[0]);testPlay(p, len);
}

 使用多态后(父类方法声明时关键字virtual :虚函数):

是在父类方法声明的时候添加virtual关键字,实现的时候不需要添加

子类重写该方法的时候也可以添加,

父类

 virtual void play() {
    cout << "一起去KTV唱歌吧" << endl;
};

子类

virtual void play() {
    cout << "一起打游戏吧!" << endl;
};

多态的本质 

形式上,使用 统一的父类指针做一般性处理,
但是实际执行时,这个 指针可以指向子类对象
形式上,原本调用父类的方法,但是 实际上会调用子类的同名方法。
注意:
程序执行时,父类指针指向父类对象,或子类对象时,在形式上是无法分辨的!
只有通过多态机制,才能执行真正对应的方法。

 虚函数表(32位下调试)

 

这里一个对象理应内存分布:一个虚函数指针+两个int成员数据(12个字节)

 //虚函数是存放在虚函数表里的
//虚函数表没有占用对象的内存空间,对象内存空间中只有一个虚函数指针
virtual void fun1() {cout << "virtual:fun1()" << endl;}
virtual void fun2() { cout << "virtual:fun2()" << endl; }
virtual void fun3() { cout << "virtual:fun3()" << endl; }

//普通成员函数在代码区
void fun4() { cout << "非虚函数:fun1()" << endl; }

public:
    int x = 200;
    int y = 300;

static int z;//静态成员数据在内存的全局数据区

 测试

 

#include <iostream>
using namespace std;class Father{
public://虚函数是存放在虚函数表里的//虚函数表没有占用对象的内存空间,对象内存空间中只有一个虚函数指针virtual void fun1() {cout  << "Father:fun1()" << endl; }virtual void fun2() { cout << "Father:fun2()" << endl; }virtual void fun3() { cout << "Father:fun3()" << endl; }//普通成员在代码区void fun4() { cout << "非虚函数:Father::fun4()" << endl; }int x = 200;int y = 300;static int z;//静态成员数据在内存的全局数据区
};
int Father::z = 0;
//定义一个void型函数指针
//virtual void fun1() {cout << "Father:fun1()" << endl;}
typedef void (*fun_t)(void);
int main(void) {Father father;cout << father.x << endl;//一个虚函数大小//虚函数表没有占用对象的内存空间,对象内存空间中只有一个虚函数指针,指向虚函数表//理应,这里只占12个字节cout << "sizeof(father)的大小:" << sizeof(father) << endl;cout << "对象的地址" << (int*)&father << endl;//先将father的值16进制的地址转换成int*的指针,在取值,然后再转换成int*的指针进行赋值int* vptr = (int*)(*(int*)&father);cout << "虚函数指针vptr的地址值" << vptr << endl;cout << "调用第一个虚函数:";((fun_t) * (vptr + 0))();//等同于调用void father::fun1(void);cout << "调用第二个虚函数:";((fun_t) * (vptr + 1))();//等同于调用void father::fun2(void);cout << "调用第三个虚函数:";((fun_t) * (vptr + 2))();//等同于调用void father::fun3(void);cout << "第一个数据成员的地址:" << endl;cout << &father.x << endl;cout << std::hex << (int) & father + 4 << endl;cout << "第一个数据成员的值:" << endl;cout <<std::dec<< father.x << endl;cout << *(int*)((int)&father + 4) << endl;cout << "第二个数据成员的地址:" << endl;cout << &father.y << endl;cout << std::hex << (int) & father + 8 << endl;cout << "第二个数据成员的值:" << endl;cout << std::dec <<father.y << endl;cout << *(int*)((int)&father + 8) << endl;return 0;
}

子类继承父类虚函数

 子类虚函数的三种状态

完全继承父类 

直接复制父类的虚函数表

子类重写父类的

 在父类虚函数的对应位置进行替换

当父类再次指向子类的时候,子类中的虚函数表已经发生改变,父类这时候调用虚函数得到的就是子类的虚函数表中对应的东西

子类自己的虚函数

将新增的虚函数添加到已有虚函数表尾部

 

 

#include <iostream>
using namespace std;class Father{
public://虚函数是存放在虚函数表里的//虚函数表没有占用对象的内存空间,对象内存空间中只有一个虚函数指针virtual void fun1() {cout  << "Father:fun1()" << endl; }virtual void fun2() { cout << "Father:fun2()" << endl; }virtual void fun3() { cout << "Father:fun3()" << endl; }//普通成员在代码区void fun4() { cout << "非虚函数:Father::fun4()" << endl; }int x = 200;int y = 300;static int z;//静态成员数据在内存的全局数据区
};
int Father::z = 0;class Son :public Father {
public://继承父类的虚函数virtual void fun1() { cout << "Son:fun1()" << endl; }//自己的虚函数virtual void fun4() { cout << "Son:fun4()" << endl; }
};//定义一个void型函数指针
//virtual void fun1() {cout << "Father:fun1()" << endl;}
typedef void (*fun_t)(void);int main(){Son son;int* vptr1 = (int*)*(int*)&son;cout << "son的地址:" << (int*) & son << endl;cout << "sizeof(son)的值:" << sizeof(son) << endl;for (int  i = 0; i < 4; i++){cout << "调用第" << i + 1 << "个虚函数:";((fun_t) * (vptr1 + i))();}for (int i = 1; i <= 2; i++){cout << "第" << i << "个成员的值"<<endl;cout << son.x << endl;cout << *(int*)((int) & son + 4) * i << endl;}return 0;
}

 使用多重继承的虚函数表

 

#include <iostream>
using namespace std;class Father{
public://虚函数是存放在虚函数表里的//虚函数表没有占用对象的内存空间,对象内存空间中只有一个虚函数指针virtual void fun1() {cout  << "Father:fun1()" << endl; }virtual void fun2() { cout << "Father:fun2()" << endl; }virtual void fun3() { cout << "Father:fun3()" << endl; }//普通成员在代码区void fun4() { cout << "非虚函数:Father::fun4()" << endl; }int x = 200;int y = 300;static int z;//静态成员数据在内存的全局数据区
};
int Father::z = 0;
//另外一个基类
class Monther {
public://虚函数是存放在虚函数表里的//虚函数表没有占用对象的内存空间,对象内存空间中只有一个虚函数指针virtual void handel() { cout << "Monther:handl()" << endl; }virtual void hande2() { cout << "Monther:handl2()" << endl; }//普通成员在代码区void fun() { cout << "非虚函数:Father::fun4()" << endl; }int m = 400;int n = 600;static int z;//静态成员数据在内存的全局数据区
};class Son :public Father ,public Monther{
public://继承父类的虚函数virtual void fun1() { cout << "Son:fun1()" << endl; }//自己的虚函数virtual void fun4() { cout << "Son:fun4()" << endl; }//继承另外一个基类的虚函数virtual void hande2() { cout << "Son:handl2()" << endl; }
};//定义一个void型函数指针
//virtual void fun1() {cout << "Father:fun1()" << endl;}
typedef void (*fun_t)(void);int main(){Son son;int* vptr1 = (int*)*(int*)&son;cout << "第一个虚函数表的指针" << vptr1 << endl;//第二个虚函数表// 一个虚函数指针,两个成员数据int* vptr2 = (int*)*((int*)&son+3);cout << "第二个虚函数表的指针" << vptr2 << endl;cout << "son的地址:" << (int*) & son << endl;cout << "sizeof(son)的值:" << sizeof(son) << endl;for (int  i = 0; i < 4; i++){cout << "调用第" << i + 1 << "个虚函数:";((fun_t) * (vptr1 + i))();}for (int i = 1; i <= 2; i++){cout << "第" << i << "个成员的值"<<endl;cout << *(int*)((int) & son + 4 * i) << endl;}for (int i = 1; i <= 2; i++) {cout << "继承母亲的第" << i << "个成员的值" << endl;cout << *(int*)((int)&son +12+ 4 * i) << endl;}for (int i = 0; i < 2; i++) {cout << "调用母亲的第" << i + 1 << "个虚函数:";((fun_t) * (vptr2 + i))();}return 0;
}

final关键字

设置该类不能被继承 

 

class i1 {

};
class i2 final :public i1{

};
class i3 :public i2{

};

设置该成员函数不能被重写(final只能用于虚函数中

 

 class i1 {
    virtual void chat()final {
        cout << "聊天一代" << endl;
    };
};
class i2  :public i1 {
    virtual void chat();
};

override仅能虚函数(仅限函数声明时可以写)

作用:
1. 提示程序的阅读者,这个函数是重写父类的功能。
2. 防止程序员在重写父类的函数时,把函数名写错。

class i1 {
public:
    virtual void chat() {
        cout << "聊天一代" << endl;
    };
};
class i2  :public i1 {
public:
    virtual void chat()override;
};

 消失的析构函(析构函数最好加上virtual)

#include <iostream>
#include <string>
using namespace std;class Father {
public:Father(const char* addr = "中国") {cout << __FUNCTION__ << endl;int len = strlen(addr) + 1;this->addr = new char[len];strcpy_s(this->addr,len, addr);};~Father() {cout << __FUNCTION__ << endl;if (addr) {delete[] addr;addr = NULL;}};
private:char* addr;
};
class Son:public Father {
public:Son(const char* addr = "中国",const char *game = "推箱子"):Father(addr) {cout << __FUNCTION__ << endl;int len = strlen(game) + 1;this->game = new char[len];strcpy_s(this->game, len, game);};~Son() {cout << __FUNCTION__ << endl;if (game) {delete[] game;game = NULL;}};
private:char* game;
};
int main(void) {string line(50, '-');Father* father = new Father();delete father;cout << line << endl;Son* son = new Son();delete son;cout << line << endl;Father* father1 = new Son();delete father1;cout << line << endl;}

 上述可以看出

父类的通过子类定义的对象,释放之后,只释放了父类的空间,子类并没有释放;

解决:将父类的析构函数定义为虚函数

作用:当对父类的指针进行delete的时候,就会对该指针使用"动态析构"[如果这个指针是指向子类对象,那么就会先调用该子类的析构函数,然后再调用父类的]

virtual ~Father() { 

 

 纯虚函数和抽象函数

纯虚数(仅提供接口,实际方法由子类实现)

抽象类(拥有纯虚数的的类就是抽象类),抽象类无法创建对象

 

//把eat方法定义为纯虚数
virtual void eat() = 0;

1:纯虚函数一定要被实现才会结束抽象这个类 ,

2:要么不对这个纯虚函数做任何处理,等效于下一种情况(该方式不推荐)

3:要么继续把这个纯虚函数声明为纯虚函数,这个子类也成为抽象类virtual void eat() = 0;

#include <iostream>
#include <string>
using namespace std;class Shape {
public:Shape(const string& color = "白色") { this->color = color; }string getColor()const {return color;}//定义一个纯虚数virtual float area() = 0;
private:string color;
};class Circle :public Shape {
public:Circle(float radios, const string& color) :Shape(color),r(radios){};//实现父类的纯虚数float area() { return 3.14 * r * r; }
private:float r;
};
int main(void) {//Shape shape;Circle cir(2,"白色");cout<<cir.getColor()<<endl;cout <<cir.area()<<endl;
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

主板电路学习; 华硕ASUS A43S笔记本安装win7X64(ventoy)

记录 老爷机 白色 华硕 A43S 笔记本 安装 win7X64 1. MBR样式常规安装win7X64Sp1 (华硕 A43S 安装 win7X64 ) 老爷机 白色 华硕 A43S 笔记本 安装 win7X64 &#xff08;常规安装&#xff09; 设置&#xff1a; 禁用UEFI 启用AHCI ventoy制作MBR&#xff08;非UEFI&#xff…

OpenAI的DALL·e2生成的AI图像有时会带有偏见或NSFW

专家警告说&#xff0c;OpenAI的图像生成模型DALLe2可能玩起来很有趣&#xff0c;但它生成的图片可能会带有刻板印象和偏见&#xff0c;甚至是生成NSFW图片&#xff0c;因此在现实世界中部署会有风险。 OpenAI公司承认“如果没有足够的防护措施&#xff0c;像DALLe2这样的模型…

【Linux 内核源码分析】堆内存管理

堆 堆是一种动态分配内存的数据结构&#xff0c;用于存储和管理动态分配的对象。它是一块连续的内存空间&#xff0c;用于存储程序运行时动态申请的内存。 堆可以被看作是一个由各个内存块组成的堆栈&#xff0c;其中每个内存块都有一个地址指针&#xff0c;指向下一个内存块…

Wpf 使用 Prism 实战开发Day13

配置 AutoMapper 关系映射 在上一节 ToDoController 控制器&#xff0c;或 IToDoService 服务接口中&#xff0c;方法的传参都是直接传的实体类。但在实际开发过程中&#xff0c;这样是不允许的。标准且规范的做法是&#xff0c;定义一个数据传输层&#xff0c;即Dto层。 一.在…

51单片机矩阵键盘

矩阵键盘 矩阵键盘是一种常用于电子设备中的输入设备&#xff0c;其原理是利用行和列的交叉点来识别按键输入。矩阵键盘通常由多个按键排列成行和列的形式组成&#xff0c;通过按下某个按键可以在对应的行和列交叉点上产生电路连接。 在矩阵键盘中&#xff0c;每个按键都被安排…

【Linux】常见指令解析下

目录 前言1. cp指令&#xff08;重要&#xff09;2. mv指令 &#xff08;重要&#xff09;3. cat指令4. more指令5. less指令 &#xff08;重要&#xff09;6. head指令7. tail指令8. 时间相关的指令8.1 data显示8.2 时间戳 9. cal指令10. find指令&#xff08;非常重要&#x…

每日一练【最大连续1的个数】

一、题目描述 给定一个二进制数组 nums 和一个整数 k&#xff0c;如果可以翻转最多 k 个 0 &#xff0c;则返回 数组中连续 1 的最大个数 。 二、题目解析 本题同样是利用滑动窗口的解法。 首先进入窗口&#xff0c;如果是1&#xff0c;就直接让right&#xff0c;但是如果是…

会话跟踪技术(cookiesession)

文章目录 1、什么是会话跟踪技术2、Cookie2.1、Cookie基本使用2.2、Cookie原理2.3、Cookie使用细节 3、Session3.1、Session基本使用3.2、Session原理3.3、Session使用细节 4、Cookie和Session的对比 1、什么是会话跟踪技术 会话 ​ 用户打开浏览器&#xff0c;访问web服务器的…

2024美赛数学建模思路 - 案例:感知机原理剖析及实现

文章目录 1 感知机的直观理解2 感知机的数学角度3 代码实现 4 建模资料 # 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 感知机的直观理解 感知机应该属于机器学习算法中最简单的一种算法&#xff0c;其…

cs231n assignment1——SVM

整体思路 加载CIFAR-10数据集并展示部分数据数据图像归一化&#xff0c;减去均值&#xff08;也可以再除以方差&#xff09;svm_loss_naive和svm_loss_vectorized计算hinge损失&#xff0c;用拉格朗日法列hinge损失函数利用随机梯度下降法优化SVM在训练集和验证集计算准确率&a…

产品经理 | 原型设计必须遵循的视觉设计规范(1)— 设计原则

前言&#xff1a;Hello大家好&#xff0c;我是小哥谈。本系列原型设计规范教程&#xff0c;主要用于规范系统的原型界面设计&#xff0c;使之具有良好的设计风格&#xff0c;帮助塑造品牌形象。通过定义原型的字体、图标、布局、颜色等信息&#xff0c;提供多样化的交互设计方案…

无法找到mfc100.dll的解决方法分享,如何快速修复mfc100.dll文件

在日常使用电脑时&#xff0c;我们可能会碰到一些系统错误提示&#xff0c;比如“无法找到mfc100.dll”的信息。这种错误通常会阻碍代码的执行或某些应用程序的启动。为了帮助您解决这一问题&#xff0c;本文将深入探讨其成因&#xff0c;并提供几种不同的mfc100.dll解决方案。…

1360. 卒的遍历-深度优先搜索-DFS

代码&#xff1a; #include<bits/stdc.h> using namespace std; int n,m; int r[25][3]; int fx[3]{0,1,0}; int fy[3]{0,0,1}; int a; void print(int k){a;cout<<a<<":";for(int i1;i<k;i){cout<<r[i][1]<<","<<…

[C#]winform部署yolov8图像分类的openvino格式的模型

【官方框架地址】 https://github.com/ultralytics/ultralytics 【openvino介绍】 OpenVINO是一个针对Intel硬件优化的开源工具包&#xff0c;用于优化和部署深度学习模型。以下是OpenVINO部署模型的主要优点&#xff1a; 高性能&#xff1a;OpenVINO提供了一系列性能优化工…

Flask 3.x log全域配置(包含pytest)

最近使用到flask3.x&#xff0c;配置了全域的log&#xff0c;这边记录下 首先需要创建logging的配置文件&#xff0c;我是放在项目根目录的&#xff0c; Logging 配置 logging.json {"version": 1, # 配置文件版本号"formatters": {"default&qu…

HTTP 协议和 TCP/IP 协议之间有什么区别?

HTTP&#xff08;超文本传输协议&#xff09;和TCP/IP&#xff08;传输控制协议/互联网协议&#xff09;是两种在互联网通信中广泛使用的协议&#xff0c;它们之间的区别和联系对许多人来说可能还不是很清晰&#xff0c;今天我们就带大家来一起了解一下HTTP和TCP/IP协议这2者之…

java数据结构与算法刷题-----LeetCode566. 重塑矩阵

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 文章目录 1. 法一&#xff0c;下标填充2. 法二&#xff1a;数学除法和取余…

MFC 序列化机制

目录 文件操作相关类 序列化机制相关类 序列化机制使用 序列化机制执行过程 序列化类对象 文件操作相关类 CFile&#xff1a;文件操作类&#xff0c;封装了关于文件读写等操作&#xff0c;常见的方法&#xff1a; CFile::Open&#xff1a;打开或者创建文件CFile::Write/…

Mongo集群入门

一、前言 MongoDB 有三种集群架构模式&#xff0c;分别为主从复制&#xff08;Master-Slaver&#xff09;、副本集&#xff08;Replica Set&#xff09;和分片&#xff08;Sharding&#xff09;模式。 Master-Slaver 是一种主从复制的模式&#xff0c;目前已经不推荐使用。 Re…

大模型:我也会自监督学习~

前言 当下大模型的能力已经很强了&#xff0c;但是将来我们想要的是能力更强的大模型&#xff0c;其最好能够处理各种复杂问题也即强对齐模型。 之前大模型训练的监督信号主要来源于人类反馈&#xff0c;但是如果想要训练一个强对齐模型必然就需要一个对应的强监督信号&#…