【C++】十分钟掌握多态(1)

news2024/12/27 16:30:36

目录

  • 前言
  • 1. 多态的概念
  • 2. 多态的定义及实现
    • 2.1多态的继承条件
    • 2.2虚函数#
    • 2.3虚函数的重写
    • 2.4虚函数重写的两个例外
    • 2.5 C++11 override 和 final
    • 2.6 重载、覆盖(重写)、隐藏(重定义)的对比
  • 3. 抽象类
    • 3.1概念
    • 3.2接口继承和实现继承
  • 4. 多态的原理
    • 4.1虚函数表

前言

这篇文章的代码及解释都是在vs下的x86程序中,涉及的指针都是4bytes

1. 多态的概念

多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。

2. 多态的定义及实现

2.1多态的继承条件

多态:不同继承关系的类对象,去调用同一函数,产生了不同的行为。比如Student继承了Person。Person对象买票函数输出全价,Student对象买票函数输出半价。

那么在继承中要构成多态还有两个条件:

  1. 必须通过基类的指针或者引用调用虚函数
  2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写
class people
{
public:
	virtual void BuyTicket()
	{
		cout << "买票全价" << endl;
	}
};

class student : public people
{
public:
	virtual void BuyTicket()
	{
		cout << "买票半价" << endl;
	}
};

void func(people* p)
{
	p->BuyTicket();
}

int main()
{
	people mike;
	func(&mike);
	
	student joe;
	func(&joe);
	return 0;
}

在这里插入图片描述

2.2虚函数#

虚函数:被virtual修饰的类成员函数称为虚函数

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

2.3虚函数的重写

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

2.4虚函数重写的两个例外

  1. 协变(父类和子类虚函数返回值类型不同)
    派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。(了解即可)
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;}
};
  1. 析构函数的重写(基类与派生类析构函数的名字不同)
    如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同,看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor。
class Person 
{
public:
	virtual ~Person() { cout << "~Person()" << endl; }
};

class Student : public Person 
{
public:
	~Student() { cout << "~Student()" << endl; }
};

int main()
{
	Person* p1 = new Person;
	Person* p2 = new Student;
	delete p1;
	delete p2;
	return 0;
}

2.5 C++11 override 和 final

从上面可以看出,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:
	void Drive(){}
};

class Benz :public Car {
public:
	virtual void Drive() override {cout << "Benz-舒适" << endl;}
};

在这里插入图片描述

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

在这里插入图片描述

3. 抽象类

3.1概念

在虚函数的后面写上 = 0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承

3.2接口继承和实现继承

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

4. 多态的原理

4.1虚函数表

// 这里常考一道笔试题:sizeof(Base)是多少?
class Base
{
public:
virtual void Func1()
{
cout << "Func1()" << endl;
}
private:
int _b = 1;
};

通过观察测试我们发现b对象是8bytes,除了_b成员,还多一个__vfptr放在对象的前面(注意有些平台可能会放到对象的最后面,这个跟平台有关),对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代表function,按理说还应该有个table代表表)。

一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表,。那么派生类中这个表放了些什么呢?

  1. 虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个nullptr。
  2. 总结一下派生类的虚表生成:
    a.先将基类中的虚表内容拷贝一份到派生类虚表中
    b.如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数
    c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。
  3. 这里还有一个童鞋们很容易混淆的问题:虚函数存在哪的?虚表存在哪的? 答:注意虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是他的指针又存到了虚表中。另外对象中存的不是虚表,存的是虚表指针。那么虚表存在哪的呢?实际我们去验证一下会发现vs下是存在代码段的

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

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

相关文章

这3个网站能够让你白嫖GPT4

1、perplexity&#xff08;https://www.perplexity.ai/&#xff09; 特点&#xff1a; 1&#xff09;保存试用上限5次GPT4&#xff0c;每4小时恢复1次 2&#xff09;试用需连接外网谷歌账号 3&#xff09;可以查看其他用户的提出的热门问题 4&#xff09;可以开启对话线程。在…

安装驱动的时候传递参数 导出符号表

安装驱动的时候传递参数 导出符号表 安装驱动传递参数 #include <linux/module.h> #include <linux/init.h>/* module_param(name, type, perm) 功能&#xff1a;接收安装驱动的时候传递的参数 参数name:变量名type:变量的类型/ * Standard types are:* byte, h…

水题杂谈222222

Trie字符串统计 思路&#xff1a; Trie字符串就是把字符串像树一样存储下来 例子&#xff1a; 将如下字符串用trie存储 然后在查找字符串的时候就顺着树查找&#xff0c;但是要在每个字符串的结尾位置打上标记 #include<iostream> #include<cmath> #include<…

23款奔驰S450 4MATIC更换原厂流星雨智能数字大灯,让智能照亮您前行的路

凭借智能数字大灯 (DIGITAL LIGHT)&#xff0c;您可体验根据其他道路使用者和周围环境进行优化调节的理想照明条件。这款包含130万像素模块大灯&#xff0c;进一步扩展了几何多光束 LED 大灯的功能。其高分辨率的照明可有针对性地点亮各个区域。解锁车辆时&#xff0c;大灯将通…

小驰私房菜_16_高通设备开机模式

[Android基础] [qcom开机模式] 用的比较多的可能是 adb reboot bootloader 和 adb reboot edl。 这2个命令都是刷机的时候会用到。 adb reboot bootloader 是进入bootloader模式&#xff0c;通过fastboot 命令来刷img文件。 adb reboot edl 是进入紧急下载模式&#xff0c;进而…

Java POI (3)—— Excel单元格复制过程中公式不生效的问题

一、出现问题的原因 在实现Excel中单元格的复制功能实现上&#xff0c;之前的代码是这样写的 /*** copyCellValue() 方法用于将源单元格的值复制到目标单元格中。* param sourceCell 是源单元格对象* param destCell 是目标单元格对象*/public static void copyCellValue(Cel…

JVM堆参数调优

1、java7 2、java8之后jvm变化 3、默认内存大小 public static void main(String[] args){ long maxMemory Runtime.getRuntime().maxMemory() ;//返回 Java 虚拟机试图使用的最大内存量。 long totalMemory Runtime.getRuntime().totalMemory() ;//返回 Java 虚拟机中的内存…

docker Alpine镜像介绍(基础镜像)

文章目录 Alpine镜像特点开发/维护者使用案例dockerfile 使用方法 https://hub.docker.com/_/alpine https://github.com/alpinejs/alpine Alpine镜像 Alpine镜像是一个基于Alpine Linux发行版构建的Docker镜像。Alpine Linux是一个轻量级的Linux发行版&#xff0c;它的目标…

H3C交换机在地址池下如何进行IP和MAC地址绑定

环境&#xff1a; H3C S6520-26Q-SI version 7.1.070, Release 6326 问题描述&#xff1a; H3C交换机在地址池下如何进行IP和MAC地址绑定 将MAC地址为0000-e03f-0305的PC机与IP地址10.1.1.1绑定&#xff0c;掩码为255.255.255.0 解决方案&#xff1a; 1.进入地址池视图 …

【Java可执行命令】(一)编译工具javac:从源代码到字节码,深入解析Java编译工具 javac ~

Java可执行命令详解之javac 1️⃣ 概念2️⃣ 优势和缺点3️⃣ 使用3.1 语法格式3.1.1 可选参数&#xff1a;-d3.1.2 可选参数&#xff1a;-classpath3.1.3 可选参数&#xff1a;-sourcepath3.1.4 可选参数&#xff1a;-target3.1.5 可选参数&#xff1a;-g 4️⃣ 应用场景5️⃣…

网络原理之传输层与网络层重点协议

目录 传输层重点协议 TCP协议 TCP协议段格式 TCP原理 确认应答机制&#xff08;安全机制&#xff09; 超时重传机制&#xff08;安全机制&#xff09; 连接管理机制&#xff08;安全机制&#xff09; 滑动窗口&#xff08;效率机制&#xff09; 流量控制&#xff08;安…

力扣 257. 二叉树的所有路径

题目来源&#xff1a;https://leetcode.cn/problems/binary-tree-paths/description/ C题解1&#xff1a;使用递归&#xff0c;声明了全局变量result&#xff0c;遇到叶子节点就将字符串添加到result中。 递归三步法&#xff1a; 1. 确认传入参数&#xff1a;当前节点已有路径…

Java 泛型进阶

目录 一、什么是泛型 二、引出泛型 1、语法 四、泛型类的使用 1、语法 2、示例 3、类型推导(Type Inference) 4、裸类型(Raw Type) &#xff08;了解&#xff09; &#xff08;1&#xff09;说明 五、泛型如何编译的 1、擦除机制 2、为什么不能实例化泛型类型数组 …

Nginx Rewrite的应用

目录 一、Nginx Rewrite 二、Rewrite的功能 1.Rewrite 跳转场景 2.Rewrite 跳转实现 3.Rewrite 实际场景 4.Rewrite 正则表达式 5.Rewrite 命令/语法格式 6.location 分类 7.location 优先级 8.Rewrite和location比较 9.根据以上了解&#xff0c;小案例来操…

【STM32】F103(64K/128K Flash)外设概述

本文介绍的是STM32F103 中等容量产品&#xff08;STM32F103x8xx和STM32F103xBxx&#xff09;的硬件数据&#xff0c;即64KB或128KB Flash&#xff0c;20KB SRAM。 ST官网资料&#xff1a;https://www.st.com/zh/microcontrollers-microprocessors/stm32f103.html ST官方的中等…

Scala中的隐式参数、隐式函数和隐式类

使用 implicit 修饰的内容是隐式内容, 隐式的特点就是遇到适应的类型会自动的应用。隐式可以使得静态类型动态化&#xff0c;为现有类库添加功能&#xff0c;隐式的代理增强一个类或者一个方法。 隐式转化的时机 当方法中的参数的类型与目标类型不一致时当对象调用所在类中不…

HOT18-矩阵置零

leetcode原题链接: 矩阵置零 题目描述 给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,1,1],[1,0,1],[1,1,1]] 输出&#xff1a;[[1,0,1],[0,…

【Flutter】Flutter Redux 入门:解决状态管理的问题

文章目录 一、 前言二、 Flutter Redux 简介1. 什么是 Redux2. 为什么需要 Redux3. Flutter Redux 的作用 三、 Flutter Redux 的基本使用1. 安装和配置2. 创建 Store3. 使用 StoreProvider 四、 Flutter Redux 的基础示例1. 创建一个简单的计数器应用2. 解析代码和说明 五、 版…

解决npm WARN deprecated uuid@3.4.0: Please upgrade to version 7 or higher

一、问题 环境 系统&#xff1a;centos 7 node &#xff1a;v18.16.1 npm&#xff1a;9.5.1 安装pm2 npm install -g pm2提示报错&#xff1a; npm WARN deprecated uuid3.4.0: Please upgrade to version 7 or higher. Older versions may use Math.random() in certai…

记录react 视频和 预览拖动

一、react 视频 ##1、循环播放 import React, { useEffect, useState, useRef } from "react"; const videoRef useRef(null); const showVideoClass { display: "block", width: "100%", height: "100%" } const hindVideoClass …