【C++】多态

2023-11-04


在这里插入图片描述

1.多态的基本概念

多态性提法接口和具体实现之间的另一层隔离,多态改善了代码的可读性和组织性,同时也使创建的程序具有可扩展性。

C++支持编译时多态(静态多态)和运行时多态(动态多态),运算符重载和函数重载就是编译时多态,而派生类和虚函数实现运行时多态

静态多态和动态多态的区别就是函数地址是早绑定(静态联编)还是晚绑定(动态联编)。如果函数调用,在编译阶段就可以确定函数调用的地址,并产生代码就是静态多态(编译时多态)。而如果函数的调用地址不能在编译期间确定,需要在运行时才能确定,这就是晚绑定(动态多态,运行时多态)。

2.动态联编和静态联编

静态联编

class animal {
public:
	void speak()
	{
		cout << "动物发出了叫声" << endl;
	}
};
class dog:public animal 
{
public:
	void speak() {
		cout << "狗发出了叫声" << endl;
	}
};
void dospeak(animal&an) {
    //参数的类型已经确定是animal类型
    //speak在编译的过程中就已经确定好了函数的地址
	an.speak();
}
void test() {
	dog dg;
    //这里传入dg类型的时候编译器并不会报错
    //原因是当发生继承关系的时候,编译器会发生类型的转换
	dospeak(dg);
}
int main() {
	test();
	return 0;
}
//输出:动物发出了叫声

程序调用的是animal对应的成员函数,因为在编译的过程中函数dospea()就已经确定了参数的类似是animal,所以调用时也是调用的animal类的成员函数

动态联编

将dospeak()函数改为虚函数,在父类上定义虚函数,发生多态。

多态的核心思想:父类的引用或者指针指向子类对象

  • 父类的引用指向子类对象
  • 父类的指针指向子类对象
class animal {
public:
    //使用关键字virtual改为虚函数
	virtual void speak()
	{
		cout << "动物发出了叫声" << endl;
	}
};
class dog:public animal 
{
public:
	virtual void speak() {
		cout << "狗发出了叫声" << endl;
	}
};
//方法:父类的引用指向子类
void dospeak(animal&an) {
	an.speak();
}
void test() {
	dog dg;
	dospeak(dg);
}
//输出:狗发出了叫声

2.多态的原理剖析

class animal {
public:
	void speak()
	{
		cout << "动物发出了叫声" << endl;
	}
};
void test() {
	animal an;
	cout << sizeof(an) << endl;
};
//输出的结果为:1
class animal {
public:
	virtual void speak()
	{
		cout << "动物发出了叫声" << endl;
	}
};
void test() {
	animal an;
	cout << sizeof(an) << endl;
};
//输出的结果为:4

结果变为4的原因是此时存放的是一个虚函数指针。

class animal {
public:
	 virtual void speak()
	{
		cout << "动物发出了叫声" << endl;
	}
};
class dog:public animal 
{
public:
};

image-20220606015107268

image-20220606015739576

当发生多态时

class animal {
public:
	 virtual void speak()
	{
		cout << "动物发出了叫声" << endl;
	}
};
class dog:public animal 
{
public:
   	virtual void speak()
	{
		cout << "狗发出了叫声" << endl;
	}
};

image-20220606020209797

image-20220606020441989

子类写speak()父类的虚函数,这种写法叫重写;

重写必须返回值,参数个数,类型,顺序都相同。

//发生了函数重写
//f
animal* an = new dog;
an->speak();
//输出:狗发出了叫声
//通过找函数的地址直接调用
void test() {
	dog dg;
	//*(int*)*(int*)&dg函数地址
	//强转为int*的原因是指针的大小为4字节
    //先拿到vfptr指针,通过解引用访问虚函数表
	((void(*)()) * (int*)*(int*)&dg)();
	((void(*)())(*((int*)*(int*)&dg + 1)))();
}

输出:

狗在吃东西
狗发出了叫声

在这里插入图片描述

3.计算器案例

class abstractCalculator
{
public:
	virtual int getResult()
    {
        return 0;
    }
	void setv1(int v)
	{
		this->val1 = v;
	}
	void setv2(int v)
	{
		this->val2 = v;
	}
public:
	int val1;
	int val2;
};
//加法计算器
class PlusCalculator :public abstractCalculator
{
public:
	virtual int getResult()
	{ 
		return val1 + val2;
	};
};
class SubCalculator : public abstractCalculator
{
public:
	virtual int getResult()
	{
		return val1 - val2;
	};
};
class ChengCalculator :public abstractCalculator
{
public:
	virtual int getResult()
	{
		return val1 * val2;
	};

};

void test02()
{
	abstractCalculator * abc ; 
	//加法计算器
	abc =  new PlusCalculator;
	abc->setv1(10);
	abc->setv2(20);
	cout << abc->getResult() << endl;
	delete abc;
	abc = new SubCalculator;
	abc->setv1(10);
	abc->setv2(20);
	cout << abc->getResult() << endl;
	delete abc;
	abc = new ChengCalculator;
	abc->setv1(10);
	abc->setv2(20);
	cout << abc->getResult() << endl;
}

4.抽象类与纯虚函数

//纯虚函数的写法
virtual int getResult()=0;
class abstractCalculator
{
public:
    //告诉编译器为函数保留一个位置,但是这个位置不存放地址
	virtual int getResult()=0;
}
  • 如果父类有纯虚函数,那么子类继承后就必须实现纯虚函数。
  • 如果父类中有了纯虚函数,这个父类就无法实例化对象,相当于父类只是进行了声明。
  • 如果父类有了纯虚函数,这个类就被称为抽象类
  • 因此抽象类无法实例化

image-20220606140941777

在这里插入图片描述

5.虚析构和纯虚析构函数

class animal
{
public:
	virtual void speak() = 0;
	~animal()
	{
		cout << "animal的析构函数" << endl;
	}
};
class dog :public animal {
public:
	virtual void speak() {
		cout << "狗在叫" << endl;
	}
	dog(const char* name) {
		//开空间
		m_name = new char[strlen(name) + 1];
		strcpy(m_name, name);
	}
	~dog() {
		delete[] m_name;
		cout << "dog的析构函数" << endl;
	}
	char * m_name;
};
void test() {
	animal* an;
	an = new dog("TOM");
	delete an;
}
//输出:调用的是animal的析构函数

调用的是父类的析构函数,那么m_name的空间就没有释放,出现内存泄漏。

解决方案:虚析构/纯虚析构

virtual ~animal()
{
	cout << "animal的析构函数" << endl;
}
virtual ~dog() {
	delete[] m_name;
	cout << "dog的析构函数" << endl;
}

输出:

调用dog的析构函数

调用aniaml的析构函数

使用纯虚析构

class animal
{
public:
	animal() {
	}
	virtual void speak()
{
	}
	//纯虚函数,需要类内声明,内外实现。
	virtual ~animal() = 0;
};
animal::~animal() {
	cout << "animal的纯虚析构" << endl;
}

为什么虚析构需要写定义?因为子类无法继承父类的构造函数和析构函数,所以无法对析构和构造进行重写;在删除子类对象时需要调用父类的析构函数,所以父类的纯虚析构函数需要定义。

6.向上类型转换和向下类型转换

不发生多态

向下类型转换(基类转换为派生类):不安全,访问的范围变大

向上类型转换(派生类转换为基类):安全,访问的范围变小

发生多态

发生了多态,类型转换总是安全的

在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【C++】多态 的相关文章

随机推荐

  • MySQL可见_MySQL 8.0新特性 -- 不可见索引

    MySQL支持不可见索引 即优化器不会使用该索引 不可见索引特性不可以用于主键 默认索引是可见的 可以在create table create index alter table操作中使用关键字visible invisible来指定索引是
  • Python之整型

    1 python中一切都是对象 因此python中其实根本不存在int float这些类型 2 int其实是一个python对象 3 整数类型 int 在python中实际是上长整型 理论是可以存储无限大小的整型数 正数负数和0 一 int
  • linux杀死进程的五种方法

    方法一 Terminal终端输入 gnome system monitor 就可以打开system monitor 如图 然后找到相应进程 右击选择kill process就可以了 方法二 通过kill 进程id的方式可以实现 首先需要知道
  • 目标跟踪序列化测试以及搜参

    1 序列化测试 对于一些跟踪算法 特别是siamese系列 一般进行20epochs的训练 对应20个训练模型 特别是backbone解冻的后10个模型 均有可能出现最好的结果 got 10k与lasot的结果一般容易出现在10 15 ot
  • 第三章:primitive主数据类型和引用-认识变量

    该系列文章系个人读书笔记及总结性内容 任何组织和个人不得转载进行商业活动 第三章 primitive主数据类型和引用 认识变量 认识变量 变量有两种 primitive 简单的 主数据类型和引用 目前我们已经使用过变量的地方 对象的状态 i
  • 没有用到React,为什么我需要import引入React?

    没有用到React 为什么我需要import引入React 本质上来说JSX是React createElement component props children 方法的语法糖 所以我们如果使用了JSX 我们其实就是在使用React 所
  • 【华为OD机试真题 python】快递运输

    题目描述 运送的快递放在大小不等的长方体快递盒中 为了能够装载更多的快递同时不能让货车超载 需要计算最多能装多少个快递 注 快递的体积不受限制 快递数最多1000个 货车载重最大50000 输入描述 第一行输入每个快递的重量 用英文逗号隔开
  • 小爱控制HA上的开关(红外线)

    小爱同学控制homeassistant in 树莓派 by 红外线 前言 租了房子以后一直想搞智能家居自动化各种事情 最近终于腾出空可以搞辣 研究了一圈感觉拆开关太麻烦了 零火线还要撬开关 租的房子不敢瞎搞 想了一下可以用arduino 树
  • VMware Workstation 入门使用

    文章目录 名词解释 事先准备 安装 VMware Tools 将鼠标焦点从虚拟机中退出 共享剪切板 共享文件 夹 虚拟机为 Windows 虚拟机为 Linux 虚拟机快照的创建与加载 创建虚拟机快照 加载虚拟机快照 克隆虚拟机 构建宿主机
  • 【ztree应用】基于jquery实现带检索功能的ztree文件夹折叠效果(附源码下载)

    文章目录 写在前面 涉及知识 效果展示 1 搭建dom 2 引入ztree和jquery 3 实现搜索功能及调用 4 源码分享 1 百度网盘 2 123云盘 3 邮箱留言 总结 写在前面 前些日子 领导要求做一个关于数据库管理的工具 主要想
  • 循环点击链接selenium模拟

    https blog csdn net qq 43251443 article details 82819887转载
  • 私有地址和保留地址

    A类 10 X X X是私有地址 私有地址就是在互联网上不使用 而被用在局域网络中的地址 127 X X X是保留地址 用做循环测试用的 B类 172 16 0 0 172 31 255 255是私有地址 169 254 X X是保留地址
  • 深度学习进阶线路图

    研究动态 深度学习进阶线路图 一 在应用机器学习的时候 最耗时和重要的阶段是对原始数据进行特征提取 深度学习是一个新的机器学习的分支 他要做的就是跨过整个特征设计阶段 而是直接从数据中学习得到 大部分的深度学习方法都是基于神经网络的 在这些
  • Git命令大全

    Git命令大全 一 git config 二 git clone 三 git init 四 git status 五 git remote 六 git branch 七 git checkout 八 git add 九 git commit
  • 区块链技术对金融行业有什么冲击?

    区块链技术在经过了长达十年的发展 被越来越多的行业关注 特别是一些大型企业 对区块链技术还进行了深入的研究 区块链技术也在更多的领域被应用 区块链技术的热度虽然很高 但目前的发展还处在初级阶段 其过多的应用场景也是没有得到更大的发展 区块链
  • 蓝桥杯练习系统入门水题

    好几天没写代码了 上蓝桥杯的练习系统看了一下 做了四道巨水题之后发现有些题还要vip 无语 问题描述 Fibonacci数列的递推公式为 Fn Fn 1 Fn 2 其中F1 F2 1 当n比较大时 F
  • (计算机组成原理)指令的寻址方式

    指令寻址方式是指指令或者操作数有效地址的寻找方式 主要分为数据寻址和指令寻址 指令的地址码字段往往并不是操作数的真实地址 而是形式地址 用A表示 A 即操作数形式地址所指向的存储介质的数值 用形式地址结合指令的寻址方式可以计算出操作数的真实
  • 快速调整毕业论文格式:调整参考文献的引用样式和段落格式

    在撰写毕业论文的过程 我们需要参考并引用大量的参考文献 之前有介绍了如何在Word中使用Endnote插入参考文献 但是从Endnote样式网站下载的样式可能和学校要求的参考文献的引用格式和段落样式有些出入 我们需要根据需求在下载样式上进行
  • 二叉树 Binary Tree

    二叉树 二叉树的基本概念 1 什么是二叉树 2 二叉树的优点和缺点 3 二叉树的基本名词 4 二叉树的性质 5 特别的二叉树 满二叉树 Full Binary Tree 完全二叉树 Complete Binary Tree 平衡二叉树 Ba
  • 【C++】多态

    文章目录 1 多态的基本概念 2 动态联编和静态联编 2 多态的原理剖析 3 计算器案例 4 抽象类与纯虚函数 5 虚析构和纯虚析构函数 6 向上类型转换和向下类型转换 1 多态的基本概念 多态性提法接口和具体实现之间的另一层隔离 多态改善