类 —— 多态、抽象类

news2025/1/13 17:45:01

多态

通常说的多态,是指发生在类之间的多态。即相同的代码,实现不同的功能。
函数重载 —— 静态多态/编译时多态。
类之间的多态 —— 动态多态/运行时多态。

前提

继承、虚函数、函数重写。

函数重写(override)

在子类中重写父类的虚函数就是函数重写的过程,可以实现多态。
1、必须有继承关系;
2、父类中必须有虚函数。

虚函数(virtual)

只要基类中的某函数是虚函数,则所有继承自此基类的子类中该函数都是虚函数。
只有 普通成员函数 与 析构函数 可以声明为虚函数。

常规来说,给父类中的函数加上 virtual 关键字,定义成一个虚函数,那么在子类中,可以对父类的虚函数进行函数重写(override)。在 C++11中,可以在派生类的新覆盖的函数上使用一个关键字 override 来验证覆盖是否成功。

只要有虚函数的类,都会有一个虚函数表和一个虚(函数表)指针。
虚指针:指向虚函数表的指针;
虚函数表:存储所有的虚函数的信息(每个有虚函数的类都有专属的虚函数表)

虚函数表:保存所有虚函数的入口地址,每一个包含虚函数的类都会有一张虚函数表。
如果发生继承关系,子类会先复制父类的虚函数表,如果子类对某个虚函数重写,就会更新虚函数表中该函数的入口地址。未被重写的虚函数,保持原样(和父类的虚函数一样)。
虚函数表指针:指向虚函数表的指针,父类中有一个虚函数表指针,子类中的虚函数表指针是从父类中继承下来的虚函数表指针,指向子类的虚函数表(虚函数表指针存在类中的第一个位置,占 4 字节)。

#include <iostream>
using namespace std;

class Father
{
public:
    string secret;
    virtual void act_1()
    {
        cout << "Earn money. " << endl;
    }
};

class Son:public Father
{
public:
    string secret;
    void act_1() override		// 覆盖基类中的虚函数,virtual 可写可不写,override 验证覆盖是否成功
    {
        cout << "Save money. " << endl;
    }
};

int main()
{
    Father f;
    cout << &f << endl;				// 类中存在虚函数时,起始位置存放虚指针
    cout << &f.secret << endl;		// 类中第一个成员的地址,偏移首地址 4 个字节

    Son s;
    cout << &s << endl;				// 子类继承父类的虚指针(1 个)
    cout << &s.secret << endl;		// 类中第一个成员的地址,偏移首地址 8 个字节

    return 0;
}

在这里插入图片描述

虚析构函数

问题:由于实现多态 需要使用父类的指针 指向 子类的空间,父类指针可以操作的空间只能是父类自己的那部分,所以,在 delete 父类指针时,并不会释放掉子类的空间。
解决方法:给基类(父类)的析构函数前面加上 virtual 关键字,只要基类是虚析构函数,后面继承的所有子类都是虚析构函数,虚析构函数会引导父类的指针释放掉子类的空间。

#include <iostream>
using namespace std;

class Father
{
    string secret;
public:
    virtual void act_1();	// 虚函数声明
    
    virtual ~Father()		
    {
        cout << "Destructor of father class. " << endl;
    }
};

void Father::act_1()		// 虚函数声明与定义分离时,virtual 只需要修饰在声明处
{
    cout << "Earn money. " << endl;
}

class Son:public Father
{
    string secret;

public:
    void act_1()			// 前面可以加 virtual,后面可以加 override
    {
        cout << "Save money. " << endl;
    }
    void act_2()			// 这里不能加 virtual 和 override,此函数非虚函数
    {
        cout << "Inherit money. " << endl;
    }
    virtual ~Son()			// 此 virtual 可以不加
    {
        cout << "Destructor of son class. " << endl;
        // 父类的析构前,不加 virtual,则 delete p 时,此函数不执行
    }
};

int main()
{
    Father *p = new Son;
    p->act_1();
    p->Father::act_1();
    delete p;

    return 0;
}

在这里插入图片描述

多态的实现

多态可以理解为”一种接口,多种状态“。只需要编写一个函数接口,根据传入的参数类型,执行不同的策略代码。

多态的使用时需要具有三个前提条件:
● 公有继承
● 函数覆盖
● 基类的 引用/指针 指向派生类对象

在代码运行时,通过对象的虚函数指针找到虚函数表,在表中定位到虚函数的调用地址,从而执行对应的虚函数内容。
使用时会产生一些额外的开销,优点是代码的编写更加灵活高效,缺点是会降低代码的执行速度。

#include <iostream>
using namespace std;

class Animal
{
public:
    virtual void eat();
};

// 声明与定义分离。virtual 只需要修饰声明处
void Animal::eat()
{
    cout << "动物爱吃坤柳。" << endl;
}

class Dog:public Animal
{
public:
    // 覆盖基类中虚函数,派生类中 virtual 关键字可写可不写
    void eat() override 		// 验证虚函数覆盖是否成功
    {
        cout << "狗吃骨头。" << endl;
    }
};

class Cat:public Animal
{
public:
    void eat() // override
    {
        cout << "猫吃鱼。" << endl;
    }
};

void test_eat1(Animal &al)			// 基类的引用指向派生类对象
{
    al.eat();		
}

void test_eat2(Animal *al)			// 基类的指针指向派生类对象
{
    al->eat();		
}	

int main()
{
    Animal a1;
    Dog d1;
    Cat c1;
    test_eat1(a1);  		// 动物爱吃坤柳,基类的引用指向了基类的对象
    test_eat1(d1);  		// 狗吃骨头,基类的引用指向了 Dog类 的对象
    test_eat1(c1);  		// 猫吃鱼,基类的引用指向了 Cat类 的对象

    Animal *a2 = new Animal;    // 基类的指针指向了基类的对象
    Dog *d2 = new Dog;
    Cat *c2 = new Cat;
    test_eat2(a2);  // 动物爱吃坤柳
    test_eat2(d2);  // 狗吃骨头
    test_eat2(c2);  // 猫吃鱼

    return 0;
}

在这里插入图片描述

💡 练习

全局变量:int monster = 10000;
定义英雄类 hero,受保护的属性:string name,int hp,int attck;
公有的无参构造,有参构造,虚成员函数 void Atk(){blood-=0;};
法师类继承自英雄类,私有属性 int ap_atk=50;重写虚成员函数 void Atk(){blood-=(attck+ap_atk);};射手类继承自英雄类,私有属性 int ac_atk = 100;重写虚成员函数 void Atk(){blood-=(attck+ac_atk);}实例化类对象,判断怪物何时被杀死。

#include <iostream>
using namespace std;

int He_Yanwei = 10000;

class Hero
{
protected:
    string name;
    int hp;
    int attack;
public:
    Hero(string name, int hp, int attack):name(name), hp(hp), attack(attack) { }
    virtual void atk()
    {
        He_Yanwei -= 0;
    }
};

class Mages:public Hero
{
    int ap_atk = 50;
public:
    Mages():Hero("You_Changzhi", 10000000000, 1000) { }
    Mages(string name, int hp, int attack, int ap_atk):Hero(name, hp, attack), ap_atk(ap_atk) { }
    void atk()
    {
        He_Yanwei -= (attack + ap_atk);
        cout << "-" << (attack + ap_atk) << endl;
    }
};

class Shooters:public Hero
{
    int ac_atk = 100;
public:
    Shooters():Hero("Zou_Jinqi", 100000000, 1000) { }
    Shooters(string name, int hp, int attack, int ac_atk):Hero(name, hp, attack), ac_atk(ac_atk) {}
    void atk()
    {
        He_Yanwei -= (attack + ac_atk);
        cout << "-" << (attack + ac_atk) << endl;
    }
};

int main()
{
    Mages mage("You_Changzhi", 10000000000, 1000, 50);
    Shooters shooter("Zou_Jinqi", 100000000, 1000, 100);
    int i = 1;
    cout << "He_Yanwei: " << He_Yanwei << endl;
    cout << "-----------------------" << endl;

    while (He_Yanwei >= 0)
    {
        cout << "Round " << i++ << ": " << endl;

        shooter.atk();
        if (He_Yanwei <= 0)
            break;
        cout << "He_Yanwei: " << He_Yanwei << endl;

        mage.atk();
        if (He_Yanwei <= 0)
            break;
        cout << "He_Yanwei: " << He_Yanwei << endl;

        shooter.atk();
        if (He_Yanwei <= 0)
            break;
        cout << "He_Yanwei: " << He_Yanwei << endl;

        cout << "-----------------------" << endl;
    }
//    cout << "-----------------------" << endl;			// 加上就有问题,离谱
    cout << "You killed He Yanwei! " << endl;

    return 0;
}

在这里插入图片描述

纯虚函数和抽象类

纯虚函数

virtual 返回值 函数名(参数列表) = 0;

抽象类

包含纯虚函数的类就是抽象类,抽象类不允许实例化类对象。抽象类的析构函数必须是虚析构函数。
抽象类通常作为一个类型的模板(作为基类存在),不能实例化类对象(没有意义)。在继承后的子类中,加上具体的属性后,重写父类中的纯虚函数就可以实例化类对象了;如果子类中不重写父类的纯虚函数,那么子类也不能实例化类对象。
抽象类的子类重写纯虚函数:该子类不再是抽象类。(水果 ——> 苹果、香蕉、草莓…)
抽象类的子类不重写纯虚函数:该子类依然是抽象类。(水果 ——> 热带水果、温带水果…)
● 抽象类支持多态,可以存在引用或指针的声明格式。
● 因为抽象类的作用是指定算法框架,因此在一个继承体系中,抽象类的内容相对丰富且重要。

#include <iostream>
using namespace std;

class Test
{
public:
    virtual void show() = 0;     		// show 函数是一个纯虚函数
};

class T:public Test
{
public:
    void show()
    {
        cout << "T" << endl;
    }
};

int main()
{
    T t1;			// 可以定义了;若不对 父类的纯虚函数 重写,则此行报错
    
    return 0;
}

💡 练习

定义抽象类 Animal,私有成员 string name,string colour,公有纯虚函数 void sound(),
定义 Cat 类,继承自 Animal 类,重写 sound 函数,
定义 Dog 类,继承自 Animal 类,重写 sound 函数,实现多态现象的测试。
(定义一个全局函数,可以实现两个子类 sound 功能的测试,要求:全局函数只有一个参数)

#include <iostream>
using namespace std;

class Animal
{
    string species;
    string color;
public:
    virtual void sound() = 0;
};

class Cat:public Animal
{
public:
    void sound()
    {
        cout << "喵~喵~" << endl;
    }
};

class Dog:public Animal
{
public:
    void sound()
    {
        cout << "汪~汪~" << endl;
    }
};

class Sheep:public Animal				// 1、公有继承
{
public:
    void sound()						// 2、重写父类的虚函数
    {
        cout << "咩~咩~" << endl;
    }
};

void choice(Animal *ani)				
{
    ani->sound();						// 3、基类的 指针或引用 指向 派生类对象
}

int main()
{
    Cat c;
    choice(&c);
    Dog d;
    choice(&d);
    Sheep sh;
    choice(&sh);

    return 0;
}

在这里插入图片描述

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

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

相关文章

正则表达式从放弃到入门(2):grep命令详解

正则表达式从放弃到入门&#xff08;2&#xff09;&#xff1a;grep命令详解 总结 本博文转载自 这是一篇”正则表达式”扫盲贴&#xff0c;如果你还不理解什么是正则表达式&#xff0c;看这篇文章就对了。 如果你是一个新手&#xff0c;请从头阅读这篇文章&#xff0c;如果你…

设计一门编程语言:你认为最重要的一定要有的特性会是哪些?

当我们站在软件工程师的角度来考虑设计一门编程语言时&#xff0c;我们需要关注那些能够提升代码质量、开发效率和程序可维护性的特性。 以下是我认为最重要的几个特性&#xff1a; 1、简洁而明确的语法&#xff1a;语法是程序员与编程语言交互的基础。简洁明了的语法可以降低…

Pandas实战:电商平台用户分析

数据分析 1.行为概况 首先&#xff0c;我们要对用户的行为类型有一定的理解&#xff0c;了解每个行为所代表的含义。 浏览&#xff1a;作为用户与商品接触的第一个行为&#xff0c;它的数量级与其他行为类型相比而言是非常庞大的&#xff0c;因为&#xff1a; 用户购买之前需…

JavaSE基础50题:7. 写一个方法返回参数二进制中1的个数(3种方法!)

文章目录 概述方法1方法2方法3 概述 返回参数中二进制中1的个数。 如&#xff1a; 15(十进制) —— 0000 1111(二进制) —— 4个1 ①我们把二进制的数字的每一位都&1&#xff0c;其中&#xff1a;1&11 、0&10 ②用无符号右移&#xff08;>>>&#xff09;来…

第一百八十八回 分享三个使用TextField的细节

文章目录 1. 概念介绍2. 使用方法2.1 修改组件的填充颜色2.2 修改组件的高度2.3 给组件添加圆角3. 示例代码4. 内容总结我们在上一章回中介绍了"DropdownButton组件"相关的内容,本章回中将介绍**TextField组件的细节.**闲话休提,让我们一起Talk Flutter吧。 1. 概念…

栈的链式存储(详解)

栈的链式存储 栈的链式存储是通过链表来实现的&#xff0c;每个节点包含一个元素和一个指向下一个节点的指针。链式存储的栈不需要提前分配内存空间&#xff0c;可以动态地增加或减少元素。 在链式存储中&#xff0c;栈顶元素通常是链表的头节点&#xff0c;栈底元素是链表的…

Flume 安装部署

文章目录 Flume 概述Flume 安装部署官方网址下载安装配置文件启动 Flume 进程启动报错输出文件乱码问题 Flume 概述 Flume&#xff08;Apache Flume&#xff09;是一个开源的分布式日志收集、聚合和传输系统&#xff0c;属于 Apache 软件基金会的项目之一。其主要目标是简化大…

使用OpenMVS重建模型

1、数据格式转换 首先将生成的稠密点云以及图片信息转换成openmvs支持的.mvs文件。在openmvs_sample中的bin文件内打开终端 作者&#xff1a;舞曲的小水瓶 https://www.bilibili.com/read/cv25019877/ 出处&#xff1a;bilibili interfaceCOLMAP.exe -i D:\desktop\test\toy\…

Proteus8.16仿真软件安装图文教程(Proteus 8 Professional)

Proteus8.16 &#x1f527;软件安装包下载链接&#xff1a;&#x1f527;视频教程&#x1f527;1 安装软件解压&#x1f527;2 安装&#x1f527;3 破解&#x1f527;4 汉化 &#x1f527;软件安装包下载链接&#xff1a; Proteus8.16软件下载链接 1、本文关于Proteus8.16 SP…

网工学习5 交换机端口相关配置

交换机的接口属性默认支待一般网络环境&#xff0c;一般情况下是不需要对其接口进行设置的。在某些情况下需 要对其端口属性进行配置时&#xff0c;配置的对象主要有接口隔离、速率、双工等信息。 5.1 接口隔离设置 > 配置接口 GE0/0/1 和 GE0/0/2 的接口隔离功能&#xf…

dart语言多线程遇到的问题:Isolate.spawnUri(),在真机调试中无法生成隔离

报错原因 [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: IsolateSpawnException: Unable to spawn isolate: Unsupported isolate URI: 未处理的异常&#xff1a;IsolateSpawnException&#xff1a;无法生成隔离&#xff1a;不支持隔离 URI&…

如何捕捉股票短线机会

一、个股相关新闻 1、盈利变化 当公司的盈利能力提升时&#xff0c;投资者就会积极地买入该股&#xff0c;股价短期内会上升。尤其是财报即将发布的阶段&#xff0c;那些能够盈利预增的股票往往会受到投资者青睐&#xff0c;使股价在短时间内大幅上涨。 比如&#xff0c;2022年…

leetcode - 矩阵区域和

1314. 矩阵区域和 - 力扣&#xff08;LeetCode&#xff09; 给你一个 m x n 的矩阵 mat 和一个整数 k &#xff0c;请你返回一个矩阵 answer &#xff0c;其中每个 answer[i][j] 是所有满足下述条件的元素 mat[r][c] 的和&#xff1a; i - k < r < i k, j - k < c …

java源码-Java方法的定义和使用详解

1、 方法定义 如果我们想定义一个方法&#xff0c;基本语法如下&#xff1a; 修饰符&#xff1a;方法的修饰符是可选的&#xff0c;用于定义该方法的访问类型&#xff0c;可用的修饰符包括public/private/protected/默认的。 返回值&#xff1a;方法可以有返回值&#xff0c;…

RK3568平台开发系列讲解(Linux系统篇)通过OF函数获取属性

🚀返回专栏总目录 沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇将介绍通过OF函数获取属性。 一、获取获取属性 ① of_find_property 函数 of_find_property 函数用于在设备树中查找节点 下具有指定名称的属性。 如果找到了该属性, 可以通过返回的属性结构体…

机械专业个人简历17篇

以下简历内容以机械专业相关岗位招聘需求为背景&#xff0c;我们整理了17篇且具有参考价值的简历案例&#xff0c;大家可以灵活借鉴&#xff0c;助理大家在众多候选人中脱颖而出。 机械专业简历模板下载&#xff08;可在线编辑制作&#xff09;&#xff1a;来幻主简历&#xf…

共识问题:区块链如何确认记账权?

区块链可以说是最近几年最热的技术领域之一&#xff0c;区块链起源于中本聪的比特币&#xff0c;作为比特币的底层技术&#xff0c;本质上是一个去中心化的数据库&#xff0c;其特点是去中心化、公开透明&#xff0c;作为分布式账本技术&#xff0c;每个节点都可以参与数据库的…

SpringBoot之自定义Starter

目录 一、自己的理解 1. 理解一 2. 理解二 二、自定义starter&#xff08;重点&#xff09; 三、以mybatis-spring-boot-starter为例进行分析 1. 写好自己的自动配置类逻辑 2. 创建自己的starter项目并引入自动配置类项目的依赖 3. 在其它项目中使用自定义的starter 一…

Spring MVC学习随笔-文件下载和上传(配置文件上传解析器multipartResolver)

学习视频&#xff1a;孙哥说SpringMVC&#xff1a;结合Thymeleaf&#xff0c;重塑你的MVC世界&#xff01;&#xff5c;前所未有的Web开发探索之旅 学习视频&#xff1a;【编程不良人】继spring之后快速入门springmvc,面对SpringMVC不用慌 六、SpringMVC 文件上传下载 6.1 文件…

【算法】单调队列 滑动窗口最大值

文章目录 例题——239. 滑动窗口最大值相关练习1438. 绝对差不超过限制的最长连续子数组解法1——两个单调队列分别维护最大值和最小值解法2——有序集合TreeMap 2398. 预算内的最多机器人数目解法1——二分答案 单调队列解法2——双指针 单调队列 &#xff08;不固定大小的滑…