[C++]16:多态

多态

  • 1.多态的定义和实现
    • 1.多态的概念:
    • 2.虚函数和虚函数的重写:
      • 1.虚函数的重写:
      • 2.协变:子类和父类中虚函数的返回值不同(意义不大)
      • 3.析构函数的重写:
      • 4.一个题目:
      • 5.普通调用 and 多态调用:
      • 6.C++11 新增语法 final 和 override
        • 1.final
        • 2.override:
      • 7.重载 && 重写 && 隐藏(重定义)
  • 2.抽象类:
    • 1.抽象类概念:
    • 2.接口继承和实现继承:
  • 3.多态的原理:
    • 1.虚函数表:
    • 2.虚函数的重写是一种==覆盖==:
    • 3.动态绑定+静态绑定
  • 4.单继承和多继承中的虚函数表:
    • 1.单继承---->虚函数表:
    • 2.多继承---->虚函数表:
    • 3.多继承---->菱形继承和菱形虚拟继承
      • 1.菱形继承
      • 2.菱形虚拟继承
  • 5.继承和多态常见的面试题目:

1.多态的定义和实现

1.多态的概念:

同一个行为不同类型的对象去做会产生不同的结果

比如下面的例子:正常人座公交车投币和刷卡,学生会刷学生卡,老人刷老年卡----->同一个买票乘车的行为–>不同对象(学生or普通人or老人)—>不同结果(滴 学生卡 / 投币 or 刷卡 / 滴 老年卡)

在这里插入图片描述
在这里插入图片描述

2.虚函数和虚函数的重写:

1.使用virtual修饰的类的成员函数就是虚函数。

观察主函数:
1.必须通过父类的指针和引用调用父类的这个虚函数。
2.调用的必须是一个虚函数,并且子类要对基类的虚函数进行重写。

1.虚函数的重写:

1.虚函数之间的重写需要满足三同:返回值 函数名称 参数。
2.重写:子类中去—>相同声明不同定义的虚函数.
3.不同对象可以通过同一个父类对象的指针或者引用调用重写完成的虚函数.
4.看似调用的是父类的成员函数但是实际上调用的是重写的子类的虚函数。

在这里插入图片描述

2.协变:子类和父类中虚函数的返回值不同(意义不大)

1,要求是父子类关系的指针或者引用类型的返回:
2.成员函数:调用对象不是自己或者自己的父类。
3.返回的是其他的父子类关系对象。

namespace sfpy {
	class A {};
	class B : public A {};
	class C : public A {};


	class preson_bus {
	public:
		virtual void ticket()
		{
			cout << "投币 or 刷卡" << endl;
		}

		//1.协变
		virtual A* ret_1() { return new A; }
		virtual A& ret_2(A& a) { return a; }
	};
	class student :public preson_bus {
	public:
		virtual void ticket()
		{
			cout << "滴 学生卡" << endl;
		}
		virtual B* ret_1(){ return new B; }
		virtual B& ret_2(B& b){ return b; }
	};
	class oldman :public preson_bus {
	public:
		virtual void ticket()
		{
			cout << "滴 老年卡" << endl;
		}
		virtual C* ret_1() { return new C; }
		virtual C& ret_2(C& c) { return c; }
	};
}

3.析构函数的重写:

1.重写需要满足三同,对于析构函数来说(~函数名称)。
2.父类的析构函数加上virtual对于子类函数只要有定义就已经完成了析构函数的重写.
3.这个时候就有一个问题了:子类和父类的析构函数的名称是不是不一样,为什么构成析构呢?因为析构函数的名称统一被修改为destructor,实际上还是满足虚函数重写的规则。

namespace sfpy {
	class preson_bus {
	public:
		virtual void ticket()
		{
			cout << "投币 or 刷卡" << endl;
		}

		preson_bus()
		{}

		//1.协变
		virtual A* ret_1() { return new A; }
		virtual A& ret_2(A& a) { return a; }

		//2.析构函数的重写:

		virtual ~preson_bus()
		{
			cout << "~preson_bus()" << endl;
		}
	};
	class student :public preson_bus {
	public:
		virtual void ticket()
		{
			cout << "滴 学生卡" << endl;
		}
		student()
			:_name("xxxxxx")
		{}

		virtual B* ret_1(){ return new B; }
		virtual B& ret_2(B& b){ return b; }

		virtual ~student()
		{
			cout << "~student()" << endl;
		}
	protected:
		string _name;
	};
	class oldman :public preson_bus {
	public:
		virtual void ticket()
		{
			cout << "滴 老年卡" << endl;
		}

		oldman() 
			:_name("xxxxxx")
		{}
		virtual C* ret_1() { return new C; }
		virtual C& ret_2(C& c) { return c; }

		virtual ~oldman()
		{
			cout << "~oldman()" << endl;
		}
	protected:
		string _name;
	};
}

内容补充:
1.父类中的析构函数前面加了virtual后面子类中的析构函数前面可以不需要加virtual(建议virtual都去加上)。
2.不需要显示的去使用析构函数,在函数结束后自动的调用析构先父后子。

在这里插入图片描述

4.一个题目:

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;
}

在这里插入图片描述

5.普通调用 and 多态调用:

1.普通调用:对象,对象指针,对象引用,都可以去调用相关成员的函数和变量。
2.多态调用:子类对象的(指针或者引用)赋值给父类对象通过父类对象(指针或者引用)调用对应已经重写好的虚函数。

6.C++11 新增语法 final 和 override

1.final

实现一个类不可以被继承:

方法一:
1.父类构造私有化,子类实例化不了对象。

方法二:
1.使用final修饰类,如果类要继承在编译的时候就报错。

在这里插入图片描述
2.final修饰父类的virtual成员函数可以让它不可以被重写:

在这里插入图片描述

2.override:

1.override加在子类中的虚函数(){} 中可以检查当前虚函数是否重写,如果没有重写就编译报错。

namespace sfpy {
	class preson_bus{
	public:
		virtual void ticket ()
		{
			cout << "投币 or 刷卡" << endl;
		}

		preson_bus()
		{}


		//2.析构函数的重写:

		virtual ~preson_bus()
		{
			cout << "~preson_bus()" << endl;
		}
	};
	class student :public preson_bus {
	public:
		virtual void ticket() override
		{
			cout << "滴 学生卡" << endl;
		}
		student()
			:_name("xxxxxx")
		{}


		virtual ~student()
		{
			cout << "~student()" << endl;
		}
	protected:
		string _name;
	};
	class oldman :public preson_bus {
	public:
		virtual void ticket() override
		{
			cout << "滴 老年卡" << endl;
		}

		oldman()
			:_name("xxxxxx")
		{}

		virtual ~oldman()
		{
			cout << "~oldman()" << endl;
		}
	protected:
		string _name;
	};
}

7.重载 && 重写 && 隐藏(重定义)

1,重载:两个函数在同一个作用域 && 函数名相同参数不同。
2.重写:
2-1:两个函数分别在父类和子类中。
2-2:三同(协变除外)
2-3:两个函数必须是虚函数。
3.隐藏(重定义)
3-1:两个函数分别在父类和子类中。
3-2:函数名相同。
3-3:子类和父类的同名函数不是重写就是隐藏!

2.抽象类:

1.抽象类概念:

1.给虚函数后面加上一个=0,虚函数变成了纯虚函数。
2.包涵纯虚函数的类被称为抽象类,抽象类不可以实例化出对象。
3.继承抽象类,子类要完成一个对纯虚函数的一个重写,完成重写后才可以实例化子类对象。
4.规定类派生类必须进行重写。

namespace sfpy {
	class preson_bus {
	public:
		//纯虚函数:
		virtual void ticket()=0
		{}
		//2.析构函数的重写:
		virtual ~preson_bus()
		{
			cout << "~preson_bus()" << endl;
		}
	};

	class student :public preson_bus {
	public:
		virtual void ticket() override
		{
			cout << "滴 学生卡" << endl;
		}
		student()
			:_name("xxxxxx")
		{}


		virtual ~student()
		{
			cout << "~student()" << endl;
		}
	protected:
		string _name;
	};
	class oldman :public preson_bus {
	public:
		virtual void ticket() override
		{
			cout << "滴 老年卡" << endl;
		}

		oldman()
			:_name("xxxxxx")
		{}

		virtual ~oldman()
		{
			cout << "~oldman()" << endl;
		}
	protected:
		string _name;
	};
}

2.接口继承和实现继承:

1.普通函数的继承是实现继承,继承父类的实现,继承的是整体的内容。
2.虚函数的继承是接口继承,继承父类的接口,重写父类的内容。不进行多态调用,没有必要定义虚函数,没有必要进行重写。

3.多态的原理:

1.虚函数表:

namespace sfpy {
	class Base{
	public:
		virtual void fun1()
		{
			cout << "fun1()" << endl;
		}
	protected:
		int _a;
	};
}

在这里插入图片描述
在这里插入图片描述

1.存在一个vfptr的函数指针数组的指针。
2.vfptr就指向一个虚函数表。

namespace sfpy {
	class Base{
	public:
		virtual void func1()
		{
			cout << "Base_1()" << endl;
		}
		virtual void func2()
		{
			cout << "Base_2()" << endl;
		}
	protected:
		int _a;
	};
	class Base_1 : public Base {
	public:
		virtual void func1()
		{
			cout << "Base_1_1()" << endl;
		}
		virtual void func2()
		{
			cout << "Base_1_2()" << endl;
		}
	protected:
		int _a;
	};

}

在这里插入图片描述

2.虚函数的重写是一种覆盖

在这里插入图片描述

3.动态绑定+静态绑定

1.静态绑定:在编译的时候函数就已经编译完成,确定了函数调用的行为。
2.动态绑定:在程序运行的时候提供对象的类型去确定具体调用的行为同时调用对应的函数的过程。

4.单继承和多继承中的虚函数表:

1.单继承---->虚函数表:

补充:
1.虚函数表存在常量区。
2.相同类型的对象他们的虚表是相同的。
3.虚表在编译的时候就产生了,虚表指针是在初始化列表之前产生的。

2.多继承---->虚函数表:


namespace sfpy {
	class B {
	public:
		virtual void func1()
		{
			cout << "B::func1()" << endl;
		}
	protected:
		int _b = 1;
	};
	class C {
	public:
		virtual void func1()
		{
			cout << "C::func1()" << endl;
		}
	protected:
		int _c = 2;
	};
	class D : public B , public C{
		virtual void func1()
		{
			cout << "D::func1()" << endl;
		}
	protected:
		int _d = 3;
	};
}


请添加图片描述

在这里插入图片描述

3.多继承---->菱形继承和菱形虚拟继承

1.菱形继承

1.A作为公共的父类存在数据冗余和二义性的问题!
2.这个时候就需要菱形虚拟继承。

namespace sfpy {
	class A {
	public:
		virtual void func1()
		{
			cout << "B::func1()" << endl;
		}
	protected:
		int _a = 1;
	};
	class B : public A{
	public:
		virtual void func1()
		{
			cout << "B::func1()" << endl;
		}
	protected:
		int _b = 2;
	};
	class C : public A {
	public:
		virtual void func1()
		{
			cout << "C::func1()" << endl;
		}
	protected:
		int _c = 3;
	};
	class D : public B , public C{
		virtual void func1()
		{
			cout << "D::func1()" << endl;
		}
	protected:
		int _d = 4;
	};
}

在这里插入图片描述

2.菱形虚拟继承

namespace sfpy {
	class A {
	public:
		virtual void func1()
		{
			cout << "B::func1()" << endl;
		}
	protected:
		int _a = 1;
	};
	class B : virtual public A {
	public:
		virtual void func1()
		{
			cout << "B::func1()" << endl;
		}
	protected:
		int _b = 2;
	};
	class C : virtual public A {
	public:
		virtual void func1()
		{
			cout << "C::func1()" << endl;
		}
	protected:
		int _c = 3;
	};
	class D : public B, public C {
		virtual void func1()
		{
			cout << "D::func1()" << endl;
		}
	protected:
		int _d = 4;
	};
}

在这里插入图片描述

5.继承和多态常见的面试题目:

  1. inline函数可以是虚函数吗?
    答:可以,不过编译器就忽略inline属性,这个函数就不再是inline,因为虚函数要放到虚表中去。

  2. 静态成员可以是虚函数吗?
    答:不能,因为静态成员函数没有this指针,使用类型::成员函数的调用方式无法访问虚函数表,所以静态成员函数无法放进虚函数表。

  3. 构造函数可以是虚函数吗?
    答:不能,因为对象中的虚函数表指针是在构造函数初始化列表
    阶段才初始化的。

  4. 析构函数可以是虚函数吗?什么场景下析构函数是虚函数?
    答:可以,并且最好把基类的析构函数定义成虚函数。

  5. 对象访问普通函数快还是虚函数更快?
    答:首先如果是普通对象,是一样快的。如果是指针对象或者是引用对象,则调用的普通函数快,因为构成多态,运行时调用虚函数需要到虚函数表中去查找。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

算法沉淀——字符串(leetcode真题剖析)

算法沉淀——字符串 01.最长公共前缀02.最长回文子串03.二进制求和04.字符串相乘 01.最长公共前缀 题目链接&#xff1a;https://leetcode.cn/problems/longest-common-prefix/ 编写一个函数来查找字符串数组中的最长公共前缀。 如果不存在公共前缀&#xff0c;返回空字符串…

【原创 附源码】Flutter集成谷歌支付详细流程(附源码)

最近有时间&#xff0c;特意整理了一下之前使用过的Flutter平台的海外支付&#xff0c;附源码及demo可供参考 这篇文章只记录Google支付的详细流程&#xff0c;相关Flutter文章链接如下&#xff1a; 【原创 附源码】Flutter集成Apple支付详细流程(附源码) 【原创 附源码】Flu…

关闭Windows 10自动更新方法

1. 关闭WindowsUpdate服务 如果你想要完全关闭Win10的自动更新功能&#xff0c;你可以在Windows服务中的WindowsUpdate选项里进行禁用设置。按照以下步骤&#xff0c;你就能完成操作。 按下“WinR”键&#xff0c;来启动“运行”&#xff0c;在运行中输入“services.msc”&…

力扣题目训练(9)

2024年2月2日力扣题目训练 2024年2月2日力扣题目训练412. Fizz Buzz414. 第三大的数415. 字符串相加129. 求根节点到叶节点数字之和131. 分割回文串65. 有效数字 2024年2月2日力扣题目训练 2024年2月2日第九天编程训练&#xff0c;今天主要是进行一些题训练&#xff0c;包括简…

Linux_进程

进程创建 进程退出码 进程等待 程序替换 Shell作为命令行解释器是一个进程&#xff0c;它也有自己的数据结构task_struct和代码和数据。为了防止用户输入的指令造成Shell崩溃&#xff0c;所以Shell执行用户输入的指令是通过创建一个子进程来执行的。例如lspwd等等。 一.进程…

单html页面使用Vue3和Element-Plus

一、快速入门 使用CDN方式引入Vue3使用CDN方式引入Element-Plus的样式文件和组件库 案例 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, ini…

Unity类银河恶魔城学习记录7-4 P70 Improving sword‘s behaviour源代码

Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释&#xff0c;可供学习Alex教程的人参考 此代码仅为较上一P有所改变的代码 【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili Sword_Skill_Controller.cs using System.Collections; using System.Colle…

前端秘法引言(配置vscode, 以及html的基础)

目录 一.配置环境vscode 二.配置插件 三.vscode的实用小技巧 四.标题段落换行标签 五.格式化标签 一.配置环境vscode vscode官网https://code.visualstudio.com/ 点击右上角的download 根据不同的操作系统进行下载安装,我这里选的是Windows x64 安装好后打开,点击左上角的…

前端学习的笔记第二篇

vscode如何快速生成代码 ! Tab 效果&#xff1a; 解析&#xff1a; <!DOCTYPE html>: 指定当前html版本5。 <html lang"en">: lang > language&#xff0c;en > english。指定当前页面内容是英文的。 <meta charset"UTF-8">:…

搭建网站的步骤和顺序?搭建一个网站的基本流程是什么?

搭建网站的步骤和顺序&#xff1f;搭建一个网站的基本流程是什么&#xff1f; 一.领取一个免费域名和SSL证书&#xff0c;和CDN 1.打开网站链接&#xff1a;https://www.rainyun.com/z22_ 2.在网站主页上&#xff0c;您会看到一个"登陆/注册"的选项。 3.点击"…

使用二分查找优化时间复杂度

二分查找&#xff0c;也称为折半查找&#xff0c;是一种效率较高的查找方法。但是&#xff0c;折半查找要求线性表必须采用顺序存储结构&#xff0c;而且表中元素按关键字有序排列。我们应该如何用在具体问题中呢&#xff1f; 题目链接&#xff08;力扣&#xff08;LeetCode&am…

Linux操作系统基础(十二):yum软件包管理器

文章目录 yum软件包管理器 一、yum常用命令 二、yum在线安装软件案例 三、yum在线删除软件案例 yum软件包管理器 yum&#xff08; Yellow dog Updater, Modified&#xff09;是一个在 Fedora 和 RedHat中的 Shell 前端软件包管理器。基于RPM包管理&#xff0c;能够从指定的…

高效的工作学习方法

1.康奈尔笔记法 在这里插入图片描述 2. 5W2H法 3. 鱼骨图分析法 4.麦肯锡7步分析法 5.使用TODOLIST 6.使用计划模板&#xff08;年月周&#xff09; 7. 高效的学习方法 成年人的学习特点&#xff1a; 快速了解一个领域方法 沉浸式学习方法&#xff1a; 沉浸学习的判据&am…

matplotlib从起点出发(13)_Tutorial_13_Autoscaling

0 自动放缩 轴上的限制可以手动设置&#xff08;例如ax.set_xlim(xmin, xmax))&#xff0c;或者Matplotlib可以根据Axes上已有的数据自动设置它们。此种放缩行为有许多选项&#xff0c;如下所述。 我们将从一个简单的折线图开始&#xff0c;显示自动缩放将轴限制扩展到数据的…

如何生成生成一个修仙世界的狗血短剧剧本

如何生成生成一个修仙世界的狗血短剧剧本 生成一个修仙世界的狗血短剧剧本将上述剧本转为对话 生成一个修仙世界的狗血短剧剧本 剧本名称&#xff1a;《仙途情缘》 角色&#xff1a; 易天行&#xff1a;男主角&#xff0c;天赋异禀的修仙者&#xff0c;性格坚毅&#xff0c;正…

鸿蒙开发系列教程(十九)--页面内动画(2)

组件内转场动画 组件的插入、删除过程即为组件本身的转场过程&#xff0c;组件的插入、删除动画称为组件内转场动画。通过组件内转场动画&#xff0c;可定义组件出现、消失的效果。 transition(value: TransitionOptions) 参数可以定义平移、透明度、旋转、缩放这几种转场样…

rabbitmq自用记录

参考博客RabbitMq安装与使用&#xff08;mac&#xff09;高效总结&#xff08;亲测&#xff09;_mac 安装rabbitmq 服务端口-CSDN博客 启动服务 这里提前把redis服务也启动了 这里看到前端更改数据,后端进行日志打印 登录后访问rabbitmq网址

docker数据科学与spark镜像源与使用常见问题疑难解答

以下是一些与数据挖掘和数据科学相关的 Docker 镜像源&#xff1a; jupyter/all-spark-notebook: 此镜像包含 Jupyter Notebook 和 Spark 的完整环境&#xff0c;用于 Spark 开发和学习。 rocker/tidyverse: 此镜像包含用于 R 语言的 tidyverse 数据科学包。 jupyter/scipy-n…

代码随想录算法训练营Day25|回溯算法·组合总和III,电话号码的字母组合

组合总和III 题目&#xff1a;找出所有相加之和为n的k个数的组合。组合中只允许含有1-9的正整数&#xff0c;并且每种组合中不存在重复的数字。 组合变量个数为k个&#xff0c;和为n。简单思路是使用k重循环&#xff0c;一层层找出来&#xff0c;然后把每一层的数相加&#x…

单链表的介绍

一.单链表的概念及结构 概念&#xff1a;链表是⼀种物理存储结构上⾮连续、⾮顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表 中的指针链接次序实现的 。 结构&#xff1a;根据个人理解&#xff0c;链表的结构就像火车厢一样&#xff0c;一节一节连在一起的&#x…