多态【C++】机制详解

文章目录

  • 概念
  • 多态的定义和实现
    • 多态的构成
    • 虚函数
    • 虚函数的重写
      • 虚函数重写的两个例外
        • 协变(基类与派生类虚函数返回值类型不同) 注:很少遇到了解下就可以
        • 析构函数的重写(基类与派生类析构函数的名字不同)
      • 两个新的关键字(C++11)
      • 重载、覆盖(重写)、隐藏(重定义)的对比
    • 抽象类
      • 概念
      • 接口继承和实现继承(注意)
    • 多态的实现原理
      • 编译时多态(静态多态)
      • 运行时多态(动态多态)
      • 示例
    • 继承和多态常见的面试问题

概念

什么是多态?多态简单来说就是多种形态,同一个事情去完成某个行为,在面向不同的对象的时候会产生不同的状态。

就比方说:一些游戏中游戏会有用相同的接口不同的角色上调用的时候就会产生不同的行为(不同类型的角色会产生不同的行为如火元素,水元素,风场等)

多态的定义和实现

多态的构成

这个就是一个多态的简单例子:

#include <iostream>
using namespace std;

class Fyro
{
public:
	virtual void effect()
	{
		cout << "火球" << endl;
	}
};

class Water:public Fyro
{
public:
	virtual void effect()
	{
		cout << "水龙" << endl;
	}
};

void User(Fyro &person)
{
	person.effect();
}

int main()
{
	Fyro A;
	Water B;

	User(A);
	User(B);
	return 0;
}

当元素类型不同的A/B调用effect()这个函数会对应大新不同的状态
在这里插入图片描述

构成多态需要两个条件

  1. 必须通过基类的指针或者引用调用虚函数
  2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写
    在这里插入图片描述

虚函数

虚函数就是被virtual修饰的函数

class Fyro
{
public:
	virtual void effect()
	{
		cout << "火球" << endl;
	}
};

effect()就是一个虚函数

虚函数的重写

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

class Fyro
{
public:
	virtual void effect()
	{
		cout << "火球" << endl;
	}
};

class Water:public Fyro
{
public:
	virtual void effect()
	{
		cout << "水龙" << endl;
	}
};

:在派生类中不写virtual的时候虚函数也重写(因为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但是不写virtual容易产生误会并且书写不规范不建议省略virtual

虚函数重写的两个例外

协变(基类与派生类虚函数返回值类型不同) 注:很少遇到了解下就可以

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

class A{};
class B : public A {};
class Person {
public:
 virtual A* f() {return new A;}
};
class Student : public Person {
public:
 virtual B* f() {return new B;}
};
析构函数的重写(基类与派生类析构函数的名字不同)

如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,
都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同,
看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处
理,编译后析构函数的名称统一处理成destructor。

#include <iostream>
using namespace std;

class Fyro
{
public:
	virtual ~Fyro()
	{
		cout << "~Fyro" << endl;
	}
};

class Water :public Fyro
{
public:
	virtual ~Water()
	{
		cout << "~Water()" << endl;
	}
};

int main()
{
	Fyro *A = new Fyro;
	Fyro *B = new Water;

	delete A;
	delete B;
	return 0;
}

在这里插入图片描述
~Fyro析构两次的原因是:只有派生类Water的析构函数重写了Fyro的析构函数,下面的delete对象调用析构函数,才能构成多态,才能保证AB指向的对象正确的调用析构函数。

两个新的关键字(C++11)

从上面可以看出,C++对函数重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数
名字母次序写反而无法构成重载,而这种错误在编译期间是不会报出的,只有在程序运行时没有
得到预期结果才来debug会得不偿失,因此:C++11提供了override和final两个关键字,可以帮
助用户检测是否重写。

  1. final:修饰虚函数,表示该虚函数不能再被重写
class Car
{
public:
 virtual void Drive() final {}
};
class Benz :public Car
{
public:
 virtual void Drive() {cout << "Benz-舒适" << endl;}
};
  1. override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。
class Car{
public:
 virtual void Drive(){}
};
class Benz :public Car {
public:
 virtual void Drive() override {cout << "Benz-舒适" << endl;}
};

在这里插入图片描述

重载、覆盖(重写)、隐藏(重定义)的对比

在这里插入图片描述

抽象类

到这里不知道大家尝试写多态的时候有没有注意就是基类的函数往往不知道让其实现什么好,往往显得冗杂或者多余,那么就引出了抽象类

概念

在虚函数的后面写上 =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;
 }
};
void Test()
{
Car* pBenz = new Benz;
 pBenz->Drive();
 Car* pBMW = new BMW;
 pBMW->Drive();
}

接口继承和实现继承(注意)

普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数

多态的实现原理

多态的实现原理是面向对象编程中的一个核心概念,它允许使用一个接口来定义多个类的共同行为,并通过这个接口来调用不同类中具体的实现。多态性可以分为编译时多态和运行时多态。

编译时多态(静态多态)

编译时多态是在编译阶段由编译器确定的多态性。它主要包括函数重载和模板。

  1. 函数重载:允许在同一个作用域内定义多个同名函数,但它们的参数列表(参数类型、数量或顺序)必须不同。编译器会根据参数列表的不同来选择正确的函数版本。
  2. 模板:模板是一种参数化类型,它允许编写与类型无关的代码。编译器在编译时会根据传入的参数类型来确定模板的具体实现。

运行时多态(动态多态)

运行时多态是在运行时由运行时系统确定的多态性。它主要通过虚函数来实现。

  1. 虚函数:在基类中声明为虚的成员函数,可以在派生类中被覆盖。通过基类指针或引用调用虚函数时,会根据对象的实际类型来决定调用哪个函数版本。
  2. 虚表:每个包含虚函数的类都有一个虚表(vtable),它是一个函数指针数组,存储了类中所有虚函数的地址。每个对象都有一个指向其类虚表的指针(vptr)。
  3. 动态绑定:当通过基类指针或引用调用虚函数时,会根据对象的 vptr 来查找虚表,并调用虚表中对应的函数地址。

示例

class Animal {
public:
    virtual void makeSound() {
        std::cout << "Unknown sound" << std::endl;
    }
};
class Dog : public Animal {
public:
    void makeSound() override {
        std::cout << "Woof!" << std::endl;
    }
};
class Cat : public Animal {
public:
    void makeSound() override {
        std::cout << "Meow!" << std::endl;
    }
};
int main() {
    Animal* animal = new Dog();
    animal->makeSound(); // 输出: Woof!
    animal = new Cat();
    animal->makeSound(); // 输出: Meow!
    return 0;
}

在这个例子中,Animal 类有一个虚函数 makeSoundDogCatAnimal 的派生类,它们覆盖了 makeSound 方法。通过基类指针 animal 调用 makeSound 函数时,会根据 animal 指向的实际对象类型来决定调用哪个版本的 makeSound 函数。
多态的实现原理使得程序能够以统一的方式处理不同类型的对象,提高了代码的灵活性和可扩展性。

继承和多态常见的面试问题

概念查考:

  1. 下面哪种面向对象的方法可以让你变得富有( )
    A: 继承 B: 封装 C: 多态 D: 抽象
  1. ( )是面向对象程序设计语言中的一种机制。这种机制实现了方法的定义与具体的对象无关,而对方法的调用则可以关联于具体的对象。
    A: 继承 B: 模板 C: 对象的自身引用 D: 动态绑定
  1. 面向对象设计中的继承和组合,下面说法错误的是?()
    A:继承允许我们覆盖重写父类的实现细节,父类的实现对于子类是可见的,是一种静态复用,也称为白盒复用
    B:组合的对象不需要关心各自的实现细节,之间的关系是在运行时候才确定的,是一种动态复用,也称为黑盒复用
    C:优先使用继承,而不是组合,是面向对象设计的第二原则
    D:继承可以使子类能自动继承父类的接口,但在设计模式中认为这是一种破坏了父类的封装性的表现
  1. 以下关于纯虚函数的说法,正确的是( )
    A:声明纯虚函数的类不能实例化对象
    B:声明纯虚函数的类是虚基类
    C:子类必须实现基类的纯虚函数
    D:纯虚函数必须是空函数
  1. 关于虚函数的描述正确的是( )
    A:派生类的虚函数与基类的虚函数具有不同的参数个数和类型
    B:内联函数不能是虚函数
    C:派生类必须重新定义基类的虚函数
    D:虚函数可以是一个static型的函数
  1. 关于虚表说法正确的是( )
    A:一个类只能有一张虚表
    B:基类中有虚函数,如果子类中没有重写基类的虚函数,此时子类与基类共用同一张虚表
    C:虚表是在运行期间动态生成的
    D:一个类的不同对象共享该类的虚表
  1. 假设A类中有虚函数,B继承自A,B重写A中的虚函数,也没有定义任何虚函数,则( )
    A:A类对象的前4个字节存储虚表地址,B类对象前4个字节不是虚表地址
    B:A类对象和B类对象前4个字节存储的都是虚基表的地址
    C:A类对象和B类对象前4个字节存储的虚表地址相同
    D:A类和B类虚表中虚函数个数相同,但A类和B类使用的不是同一张虚表
  1. 下面程序输出结果是什么? ()
    A:class A class B class C class D
    B:class D class B class C class A
    C:class D class C class B class A
    D:class A class C class B class D
#include<iostream>
using namespace std;
class A{
public:
 A(char *s) { cout<<s<<endl; }
 ~A(){}
};
class B:virtual public A
{
public:
 B(char *s1,char*s2):A(s1) { cout<<s2<<endl; }
};
class C:virtual public A
{
public:
 C(char *s1,char*s2):A(s1) { cout<<s2<<endl; }
};
class D:public B,public C
{
public:
 D(char *s1,char *s2,char *s3,char *s4):B(s1,s2),C(s1,s3),A(s1)
 { cout<<s4<<endl;}
};
int main() {
 D *p=new D("class A","class B","class C","class D");
 delete p;
 return 0;
}
  1. 多继承中指针偏移问题?下面说法正确的是( )
    A:p1 == p2 == p3
    B:p1 < p2 < p3
    C:p1 == p3 != p2
    D:p1 != p2 != p3
class Base1 {  public:  int _b1; };
class Base2 {  public:  int _b2; };
class Derive : public Base1, public Base2 { public: int _d; };
int main(){
 Derive d;
 Base1* p1 = &d;
 Base2* p2 = &d;
 Derive* p3 = &d;
 return 0;
}
  1. 以下程序输出结果是什么()
    A: A->0
    B: B->1
    C: A->1
    D: B->0
    E: 编译出错
    F: 以上都不正确
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;
   }

参考答案:

  1. A
  2. D
  3. C
  4. A
  5. B
  6. D
  7. D
  8. A
  9. C
  10. B

点个赞嘿嘿
在这里插入图片描述

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

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

相关文章

通过 PPPOE 将 linux 服务器作为本地局域网 IPv4 外网网关

将 linux 服务器作为本地外网网关&#xff0c;方便利用 Linux 生态中的各种网络工具&#xff0c;对流量进行自定义、精细化管理… 环境说明 拨号主机&#xff1a;CentOS 7.9, Linux Kernel 5.4.257 拨号软件: rp-pppoe-3.11-7.el7.x86_64初始化 1、升级系统到新的稳定内核&a…

【代码随想录】【算法训练营】【第65天】 [卡码94]城市间货物运输I

前言 思路及算法思维&#xff0c;指路 代码随想录。 题目来自 卡码网。 day 65&#xff0c;周四&#xff0c;继续ding~ [卡码94] 城市间货物运输I 题目描述 卡码94 城市间货物运输I 解题思路 前提&#xff1a; 思路&#xff1a; 重点&#xff1a; 代码实现 C语言 Be…

浅学三次握手

数据要完成传输&#xff0c;必须要建立连接。由于建立TCP连接的过程需要来回3次&#xff0c;所以&#xff0c;将这个过程形象的叫做三次握手。 结合上面的图来看更清楚。 先说三次握手吧&#xff0c;连接是后续数据传输的基础。就像我们打电话一样&#xff0c;必须保证我和对方…

DHCP原理及配置

目录 一、DHCP原理 DHCP介绍 DHCP工作原理 DHCP分配方式 工作原理 DHCP重新登录 DHCP优点 二、DHCP配置 一、DHCP原理 1 DHCP介绍 大家都知道&#xff0c;现在出门很多地方基本上都有WIFI&#xff0c;那么有没有想过这样一个问题&#xff0c;平时在家里都是“固定”的…

多媒体软件开发选择Animate软件还是Unity3D软件?

以下内容可能有一些片面&#xff0c;因为多媒体软件开发平台有很多&#xff0c;因为接触Animate和Unity3D比较多&#xff0c;所以这里仅对这两款进行分析&#xff01; Animate软件与Unity3D软件都是经常在多媒体展馆中用来制作互动展示内容的&#xff0c;对于这两种开发平台&a…

SpringBoot整合RedisTemplate的使用方法

文章目录 0.启动 Redis服务器1. 添加依赖2.配置 Redis 相关信息3. 创建配置类 RedisTemplate4. 使用 RedisTemplate5. 测试 0.启动 Redis服务器 打开 cmd &#xff0c; 输入 redis-server &#xff0c;启动 Redis 服务器。 1. 添加依赖 首先&#xff0c;在 pom.xml 中添加 Sp…

(word原件)软件系统详细需求设计书参考文档及软件文档大全

1引言 1.1编写目的 1.2项目背景 1.3参考材料 2系统总体设计 2.1整体架构 2.2整体功能架构 2.3整体技术架构 2.4设计目标 2.5.1总体原则 2.5.2实用性和先进性 2.5.3标准化、开放性、兼容性 2.5.4高可靠性、稳定性 2.5.5易用性 2.5.6灵活性和可扩展性 2.5.7经济性和投资保护 3系统…

秒懂设计模式--学习笔记(9)【结构型-装饰器模式】

目录 8、装饰器模式8.1 装饰器模式&#xff08;Decorator&#xff09;8.2 装修&#xff08;举例&#xff09;8.3 化妆&#xff08;示例&#xff09;8.4 化妆品的多样化8.5 装饰器8.6 自由嵌套8.7 装饰器模式的各角色定义8.8 装饰器模式 8、装饰器模式 8.1 装饰器模式&#xff…

GUI界面开发之tkinter(一)

Tkinter是一个内置的Python库&#xff0c;用于创建图形用户界面&#xff08;GUI&#xff09;。它提供了一组工具和小部件&#xff0c;用于创建窗口、对话框、按钮、菜单和其他GUI元素。 在本篇文章中&#xff0c;主要介绍了窗口等知识点。 大家好&#xff01;我是码银&#x1…

Nginx实现服务器端集群搭建/Nginx实现动静分离/Nginx高可用解决方案/Nginx与Tomcat部署

Nginx实现服务器端集群搭建 Nginx与Tomcat部署 前面课程已经将Nginx的大部分内容进行了讲解,我们都知道了Nginx在高并发场景和处理静态资源是非常高性能的,但是在实际项目中除了静态资源还有就是后台业务代码模块,一般后台业务都会被部署在Tomcat,weblogic或者是webspher…

把当前img作为到爷爷的背景图

&#xff08;忽略图大小不一致&#xff0c;一般UI给的图会刚好适合页面大小&#xff0c;我这网上找的图&#xff0c;难调大小&#xff0c;我行内的就自己随便写的宽高&#xff09;&#xff0c;另外悄悄告诉你最后有简单方法&#xff5e;&#xff5e; 先来看看初始DOM结构代码 …

解锁 EasyV「万能子组件」,你的业务展示形式由你定义!

随着可视化业务表达方式的不断拓深&#xff0c;展示形式也越来越丰富多样&#xff0c;充满更多的“个性化”以及“专属定制”的诉求场景。而现在的子组件样式&#xff0c;仅支持固定格式的内容表达&#xff0c;难以满足易知微用户的定制化需求。 比如以下场景 想在地图上展示图…

破解快消行业营销费用管理难题,引领财务费控数字化转型

众所周知&#xff0c;快消品行业消费群体较为广泛&#xff0c;涉及渠道复杂&#xff0c;产品周转期短、可替代性强&#xff0c;特别是面对竞争激烈的市场环境&#xff0c;大力投入营销、渠道费用对快消行业的企业来说十分普遍&#xff0c;而这其中&#xff0c;由于渠道多、业态…

13.FreeRTOS_定时器

定时器概述 定时器运行过程 定时器就像一个闹钟&#xff0c;它有超时时间、函数、是否为周期性这三个部分。 超时时间&#xff1a;什么时候到时间&#xff0c;就像闹钟响起函数&#xff1a;闹钟响起&#xff0c;要干什么是否为周期性&#xff1a;这个闹钟只响一次&#xff0…

MySQL下载安装使用教程图文教程(超详细)

「作者简介」&#xff1a;冬奥会网络安全中国代表队&#xff0c;CSDN Top100&#xff0c;就职奇安信多年&#xff0c;以实战工作为基础著作 《网络安全自学教程》&#xff0c;适合基础薄弱的同学系统化的学习网络安全&#xff0c;用最短的时间掌握最核心的技术。 这一章节我们使…

指针!!C语言(第一篇)

指针1 指针变量和地址1.取地址操作符(&)2.指针变量和解引用操作符(*) 指针变量的大小和类型指针的运算特殊指针1.viod*指针2.const修饰指针3.野指针 assert断言指针的使用和传址调用1.strlen的模拟实现2.传值调用和传址调用 指针变量和地址 在认识指针之前&#xff0c;我们…

算法力扣刷题记录 四十二【101. 对称二叉树、100.相同的树、572.另一个树的子树】

前言 二叉树篇&#xff0c;开始对二叉树操作练习。 记录 四十二【101. 对称二叉树】。 继续。 一、题目阅读 给你一个二叉树的根节点 root &#xff0c; 检查它是否轴对称。 示例 1&#xff1a; 输入&#xff1a;root [1,2,2,3,4,4,3] 输出&#xff1a;true示例 2&#x…

香橙派AIpro部署YOLOv5:探索强悍开发板的高效目标检测能力

香橙派AIpro部署YOLOv5&#xff1a;探索强悍开发板的高效目标检测能力 一、香橙派AIpro开箱使用体验 1.1香橙派AIpro开箱 拿到板子后第一件事情就是开箱&#xff1a; 开箱后可以看见一个橘子的标识&#xff0c;也就是香橙派了&#xff0c;并且还有四个大字&#xff1a;为AI…

微信小游戏 彩色试管 倒水游戏 逻辑 (二)

最近开始研究微信小游戏&#xff0c;有兴趣的 可以关注一下 公众号&#xff0c; 记录一些心路历程和源代码。 定义一个 Water class 1. **定义接口和枚举**&#xff1a; - WaterInfo 接口定义了水的颜色、高度等信息。 - PourAction 枚举定义了水的倒动状态&#xff0c;…

基于5个K7的多FPGA PCIE总线架构的高性能数据预处理平台

板载FPGA实时处理器&#xff1a;XCKU060-2FFVA15172个QSFP光纤接口&#xff0c;最大支持10Gbps/lane板载DMA控制器&#xff0c;能实现双向DMA高速传输支持x8 PCIE主机接口&#xff0c;系统带宽5GByte/s1个R45自适应千兆以太网口1个FMC子卡扩展接口 基于PCIE总线架构的高性能数据…