C++(23)--多态性与虚函数

多态性与虚函数

  • 1.静态多态-重载
  • 2.动态多态-重写
    • 2.1 向上转换/向下转换
  • 3.虚函数的工作原理
  • 4.纯虚函数和抽象类
  • 5.补充项目(都市浮生记)-卒

《老九学堂C++课程》学习笔记。《老九学堂C++课程》详情请到B站搜索《老九零基础学编程C++入门》
-------------简单的事情重复做,重复的事情用心做,用心的事情坚持做(老九君)---------------

多态–多种表现形式,生物学名词。
同一个名称的函数,可以实现不同的功能。

什么是多态
面向对象编程的多态性包括:
1.面向不同的对象发送同一条信息–多个对象调用同一个函数
2.不同的对象在接收时回产生不同的行为–
不同的行为–不同的实现,即执行不同的函数功能。函数名相同,但执行的具体细节不同。

1.静态多态-重载

静态多态–重载
静态多态也叫编译时多态。
demo1.游戏引擎调用得中类对象进行移动操作

// GameCore.h
//
// Created by 陈莹莹 on 2021/3/24.
//
#ifndef CHAPTER14_GAMECORE_H
#define CHAPTER14_GAMECORE_H
#include <iostream>
#include <string>
#include <vector>
#include "Hero.h"
#include "Warrior.h"
#include "Archmage.h"
/** 游戏引擎/游戏业务/游戏核心类* **/
class GameCore {
public:GameCore();~GameCore();// 定义一个函数,用来移动游戏角色// 重载--函数名相同,参数列表类型或数量不同void MoveRole(Warrior& warrior){warrior.Move();     // 实际上就是调用传入战士的移动方法}void MoveRole(Archmage& archmage){archmage.Move();}// 移动一批战士void MoveRole(vector<Warrior*> vecWarrior){for(auto warrior:vecWarrior){warrior->Move();}}
};
#endif //CHAPTER14_GAMECORE_H
//main.cpp
#include <iostream>
#include <string>
#include <vector>
#include "Hero.h"
#include "Warrior.h"
#include "Archmage.h"
#include "GameCore.h"
using namespace std;
void HeroTest();
int main() {HeroTest();return 0;
}void HeroTest(){Hero hero("布衣");Warrior warrior1("吕布1",50);Warrior warrior2("吕布2",50);Warrior warrior3("吕布3",50);Archmage archmage("甘道夫",80);GameCore gamecore;
//    gamecore.MoveRole(warrior);
//    gamecore.MoveRole(archmage);vector<Warrior *> vecWarrior;vecWarrior.push_back(&warrior1);vecWarrior.push_back(&warrior2);vecWarrior.push_back(&warrior3);// 主要观察,调用游戏业务方法来统一操作传入的多个战士gamecore.MoveRole(vecWarrior);}

输出

调用了Hero 四个参数版本的构造
调用了Hero 一个参数版本的构造
调用了Hero 四个参数版本的构造
调用了Hero 四个参数版本的构造
调用了Hero 四个参数版本的构造
调用了Hero 四个参数版本的构造
战士《吕布1》背着一大堆近战武器正在前进。。。
战士《吕布2》背着一大堆近战武器正在前进。。。
战士《吕布3》背着一大堆近战武器正在前进。。。

2.动态多态-重写

动态多态–重写
动态多态也叫运行时多态,函数在执行的过程中才能确定要执行的是哪一个。

父类方法中加virtual关键字,在核心引擎类中的RoleMove参数使用hero 对象,那么可以给RoleMove传递各种hero子类实现各种移动。

//mian.cpp
#include <iostream>
#include <string>
#include <vector>
#include "Hero.h"
#include "Warrior.h"
#include "Archmage.h"
#include "GameCore.h"
#include "Assassin.h"
using namespace std;
void HeroTest();
int main() {HeroTest();return 0;
}void HeroTest(){Hero hero("布衣");Warrior warrior1("吕布1",50);Warrior warrior2("吕布2",50);Warrior warrior3("吕布3",50);Archmage archmage("甘道夫",80);GameCore gamecore;// 不使用virtual 关键字的效果// 编译器就会根据当前对象的类型,调用类型中定义的move 方法gamecore.MoveRole(warrior1);gamecore.MoveRole(archmage);// 使用virtual 关键字,派生类重写了基类的方法//不使用virtual 输出// 普通英雄吕布1正在奔跑在艾泽拉斯大陆上// 普通英雄甘道夫正在奔跑在艾泽拉斯大陆上//使用virtual 输出// 战士《吕布1》背着一大堆近战武器正在前进。。。// 大法师甘道夫为了节省魔法, 只好用双脚赶路// 不修改核心逻辑,直接传入新类型对象Assassin assa("飞檐走壁",100);gamecore.MoveRole(assa);
}

新增的刺客类

// Assassin.h
//
// Created by 陈莹莹 on 2021/3/25.
//#ifndef CHAPTER14_ASSASSIN_H
#define CHAPTER14_ASSASSIN_H
#include <iostream>
#include <string>
#include "Hero.h"
using namespace std;
/** 体会程序是如何进行升级的* 假定游戏需要增加一个新的职业:刺客,但是核心业务类肯定不能够随便修改*/class Assassin:public Hero{
public:Assassin();Assassin(const string& nickName, int power):Hero(nickName),m_Power(power){}void Move() override{cout << "隐藏在黑暗中的刺客" << GetNickName() << "正在偷偷地潜入一座宫殿"<< endl;}~Assassin();
private:int m_Power;};#endif //CHAPTER14_ASSASSIN_H
//Assassin.h
//
// Created by 陈莹莹 on 2021/3/25.
//#include "Assassin.h"Assassin::Assassin() {}
Assassin::~Assassin(){}

2.1 向上转换/向下转换

    // 为了能够让同一个函数操作不同类型的子类对象,所以我们把参数类型定义成基类对象// 当传递Hero类型的子类型时,参数类型可以自动转换// 关于向上和向下转换// 当B是A的子类型(class B: public A ),意味着所有对A对象的操作都可以对B对象进行// 即B重用A的操作来实现自己的操作// 向上转型:把子类型对象转换为父类型对象,下面有三个注意点:// 1.向上转型是安全的// 2.向上转型是自动完成的(自动类型转换)// 3.向上转型的过程中,会丢失子类型的信息。// Warrior warrior;         // 子类型对象// Hero& hero = warrior;    // 父类型引用指向了子类型对象--向上转型// hero.XiaoQuanQuan();     // 编译器会报错--丢失了子类型信息// 如果还想使用子类型方法,那么就需要再进行强制类型转换--向下转型// warrior& newWarrior = (Warrior&)hero;   // 向下转型不安全// hero对象有可能是父类型的另一个子类型// Archmage warrior;// Hero& hero = warrior;// Warrior& newWarrior = (Warrior&)hero; // 编译时不会报错,但是执行时会报错,(老师演示的时候还能够运行的)

3.虚函数的工作原理

1.构造函数不能是虚函数
2.析构函数应该定义成虚函数,除非该类不做基类。为了安全起见,为将类的析构函数定义为虚函数。
3.友元函数不能是虚函数。

虚函数的工作原理:会为父类对象构建一个隐藏成员,为指向虚函数表的指针。子类重写了父类方法的话,也会为子类对象构建一个隐藏成员,为指向虚函数表的指针。但是具体的函数指针变了的。
在这里插入图片描述
demo1:观察虚函数列表地址的变化(实验现象没有实现

//mian.cpp
void VirtualPointTest(){
//    Base base;      // 基类对象
//    long* baseAdress = (long*) &base;   // 转换成长整形指针,方便待会指针移动和转换
//    // cout << "基类对象地址" << &base << endl;
//    cout << "基类对象地址" << baseAdress << endl;
//    long* virTablePtr = (long*)(baseAdress + 0);    // 虚函数表的地址就是这么求的
//    cout << "虚函数表的地址:" << virTablePtr << endl;
//    long* virFunctionPtr1 = (long*) *(virTablePtr + 0);
//    cout << "虚函数表中第一个虚函数的地址" << virFunctionPtr1 << endl;
//    long* virFunctionPtr2 = (long*) *(virTablePtr + 1);
//    cout << "虚函数表中第一个虚函数的地址" << virFunctionPtr1 << endl;
//    long* virFunctionPtr3 = (long*) *(virTablePtr + 2);
//    cout << "虚函数表中第一个虚函数的地址" << virFunctionPtr1 << endl;Base base;      // 基类对象int* baseAdress = (int*)&base;            // 基类对象cout << "基类对象地址" << baseAdress << endl; // 保存基类对象的地址int* virTablePtr = (int*)*(baseAdress + 0); //虚拟表的指针地址cout << "基类隐藏成员:虚拟表的指针地址:" << virTablePtr << endl;
//    // 虚拟表中第一个虚函数的地址
//    int* virFunctionPtr = (int*) *(virTablePtr + 0);
//    cout << "虚拟表中第一个虚函数的地址:" << virFunctionPtr << endl;   //没输出成功呀
//    cout << "end" << endl;
//    //强制转换成函数来调用
//    void(*BaseVirtual1)() = (void(*)())virFunctionPtr;
//    BaseVirtual1();                                 // 取出第一个虚函数后调用。
//    // 下面注意:GCC mingW64 指针+ 2,如果使用的是VS20xx版本,指针需要加1, mac gcc +2
//    int* virFunctionPtr2 = (int*) *(virTablePtr + 2);
//    void(*BaseVirtual2)() = (void(*)())virFunctionPtr2;
//    BaseVirtual2();                                 // 取出第一个虚函数后调用。
//    int* virFunctionPtr3 = (int*) *(virTablePtr + 4);
//    void(*BaseVirtual3)() = (void(*)())virFunctionPtr3;
//    BaseVirtual3();                                 // 取出第一个虚函数后调用。// 取出第一私有成员cout << "第一个私有成员member的值:" << *(baseAdress + 2) << endl;  // 9527 取处出来了cout << "---------- 派生类对象的内存信息如下----------------" << endl;Son son;int* sonAdress = (int*)&son;cout << "派生类对象的地址:" << sonAdress << endl;virTablePtr = (int*)*(sonAdress + 0);cout << "派生类对象的虚拟表的地址:" << virTablePtr << endl;// 有三个虚函数,一个被覆盖了(地址变了),其余两个没有变。
}
// VirtualPointDemo1.h
//
// Created by 陈莹莹 on 2021/3/27.
//
#ifndef CHAPTER14_VIRTUALPOINTDEMO1_H
#define CHAPTER14_VIRTUALPOINTDEMO1_H
#include <iostream>
#include <string>
using namespace std;
class Base {
private:int menber;
public:Base(){menber = 9527;}virtual void baseVirtual1(){cout << "基类中的虚函数版本1"<<endl;}virtual void baseVirtual2(){cout << "基类中的虚函数版本2"<<endl;}virtual void baseVirtual3(){cout << "基类中的虚函数版本3"<<endl;}
};class Son :public Base{
public:void baseVirtual2() override{cout << "派生类中唯一实现的2版本的基类虚函数" << endl;}
};#endif //CHAPTER14_VIRTUALPOINTDEMO1_H

4.纯虚函数和抽象类

抽象类–天生的父类,实例出来没啥用,需要进行扩展。(生物对象:血量,攻击力)

语法上一个抽象类无法被实例化

抽象类的虚函数都为纯虚函数,纯虚函数让基类函数没有函数体,在基类中不能被调用。纯虚函数必须有派生类来实现纯虚函数体的功能。(一个类如果有一个纯虚函数,那么这个类就是抽象类)

纯虚函数语法格式

virtual 返回类型 函数名(参数列表) const=0;

demo:多态的方式来模拟“星际争霸”中的指挥官和各种兵种之间的互动关系。
指挥官发出指令–Rolling Thunder,各单位发起进攻

//mian.cpp
#include <iostream>
#include <vector>
#include "AbstractClass.h"void AbstractTest();
int main() {AbstractTest();return 0;
}void AbstractTest(){// 尝试实例化一个抽象类类// BattleUnit battleUnit;   提示是一个抽象类不能被实例化// 没有重载全部虚函数,子类还是会被认为是抽象类Marin marin1("巫妖王");Marin marin2("死亡骑士");marin1.Fight(marin2);SiegeTank tank1("坦克1");tank1.Move(10,20);Viking viking1("北欧海盗");vector<BattleUnit*> units;units.push_back(&marin1);units.push_back(&marin2);units.push_back(&tank1);units.push_back(&viking1);Commander commander;cout << "让指挥官移动多个不同类型的战斗单位" << endl;commander.Move(units,50,50);
}
//AbstractClass.h
//
// Created by 陈莹莹 on 2021/4/2.
//
#ifndef STAR_WAR_ABSTRACTCLASS_H
#define STAR_WAR_ABSTRACTCLASS_H
#include <iostream>
#include <string>
#include <vector>
using namespace std;
/** 实现一个简单版的星际争霸游戏,用来加深对多态及抽象类的理解* */
class Point{
private:int m_x;int m_y;
public:Point(){}Point(int _x, int _y):m_x(_x),m_y(_y){}int GetX() {return m_x;}int GetY() {return m_y;}void SetX(int x) {this->m_x = x;}void SetY(int y) {this->m_y = y;}friend ostream& operator << (ostream& out, const Point& p){out << "(" << p.m_x << "," << p.m_y << ")" << endl;return out;}
};class BattleUnit{// 战斗单位了
private:
protected:string name;int maxHp;int currHp;Point position;int attDistance;    // 当前对象的攻击距离
public:BattleUnit(){}BattleUnit(const string& _name): name(_name){maxHp = 100;currHp = 100;position.SetX(0);position.SetY(0);attDistance = 100;}// 设置某个方法分为纯虚函数,Battle类变成抽象类,不能实例化virtual void Fight(BattleUnit& other) = 0;virtual void Move(int x, int y) = 0;virtual void Move(Point& position) = 0;const string & GetName() const{return name;}
};
// 我们可以提供抽象的基类纯虚方法的默认实现
void BattleUnit::Fight(BattleUnit& other){// 每个单位进行对战前,依据当前坐标计算两个单位间的距离// 如果距离超过的攻击距离,攻击失败。cout << name << "正在攻击另一个战斗单位:" << other.GetName() << endl;
}
void BattleUnit::Move(int x, int y){position.SetX(x);position.SetX(y);
}class Marin:public BattleUnit{
public:Marin(){}Marin(const string& _name):BattleUnit(_name){}void Fight(BattleUnit& other) override;void Move(int x, int y){BattleUnit::Move(x,y);cout << "陆战队员接到命令,立即前往坐标点: " << position << endl;}void Move(Point& position){}
};
void Marin::Fight(BattleUnit& other){// 在子类中调用父类的同名方法,需要使用到域运算符BattleUnit :: Fight(other);cout << "陆战队员" << GetName() << "正在攻击敌人:" << other.GetName() << endl;
}class SiegeTank : public BattleUnit{
public:SiegeTank(){}SiegeTank(const string& _name) : BattleUnit(_name){}// undifined reference to "Vtable" for SiegeTank // 没有实现完全父类的纯虚函数void Fight(BattleUnit& other) override{}void Move(int x, int y)override{position.SetX(x);position.SetY(y);cout << "工程坦克" << GetName() << "收到移动命令:" << position << endl;}void Move(Point& position)override{}
};
class Viking : public BattleUnit{
public:Viking(){}Viking(const string& _name) : BattleUnit(_name){}void Fight(BattleUnit& other) override{}void Move(int x, int y)override{position.SetX(x);position.SetY(y);cout << "维京战机" << GetName() << "立即飞往坐标:" << position << endl;}void Move(Point& position)override{}
};class Commander{// 游戏中的核心业务类,引擎
public:// 模拟了指挥官的rolling thunder// 一个指挥官同时移动了多个战斗单位void Move(vector<BattleUnit*> units, int x, int y){for(auto unit : units){unit->Move(x,y);}}
};
#endif //STAR_WAR_ABSTRACTCLASS_H

5.补充项目(都市浮生记)-卒

window 编程呀,mac 的头文件都引入不了

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

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

相关文章

如何在Appscale下发布自己的应用(一)

本篇文章主要讲如何在本地搭建appscale环境。由于国内的信息资源有限&#xff0c;很多重要的论坛被墙了&#xff0c;所以遇到不少麻烦&#xff0c;由于最近一段时间vpn也被封掉了&#xff0c;我只能通过特殊渠道方法来翻墙查阅资料&#xff0c;走了不少弯路。 1.先说系统和环境…

总结了线程安全性的二十四个精华问题

1、对象的状态&#xff1a;对象的状态是指存储在状态变量中的数据&#xff0c;对象的状态可能包括其他依赖对象的域。在对象的状态中包含了任何可能影响其外部可见行为的数据。 2、一个对象是否是线程安全的&#xff0c;取决于它是否被多个线程访问。这指的是在程序中访问对象的…

如何在Appscale下发布自己的应用(二)

本文开始讲如何发布自己的app应用到appscle上 建好appscle网站后&#xff0c;可以在命令行通过 appscle deploy apppathname 来发布自己应用。 除了用命令行提交应用之外&#xff0c;还可以通过appscale的网站直接提交&#xff0c;选择 upload application->选择上传文件-&g…

Python模块(7)-SciPy 简易使用教程

SciPy 简易使用教程1. 符号计算2. 函数向量化3. 波形处理scipy.signal3.1 滤波器3.2 波峰定位基于numpy的一个高级模块&#xff0c;为数学&#xff0c;物理&#xff0c;工程等方面的科学计算提供无可替代的支持。 做重要的思想是&#xff1a;符号计算和函数向量化 1. 符号计算…

Xcode的Architectures和Valid Architectures的区别

目录[-] Xcode的Architectures和Valid Architectures的区别 Architectures Valid Architectures 原因解释如下&#xff1a; 参考1&#xff1a; 所有IOS设备详情列表 List of iOS devices - Wikipedia, the free encyclopedia 参考2&#xff1a; iOS 7: 如何为iPhone 5S编译64位…

Python模块(8)-sklearn 简易使用教程

sklearn 简易使用教程1.scikit-learn的数据集2.scikit-learn 的训练和预测scikit-learn 是在Numpy,SciPy,Matplotlib三个模块上编写的&#xff0c;数据挖掘和数据分析的一个简单有效的工具。scikit-learn包括6大功能&#xff1a;分类&#xff0c;回归&#xff0c;聚类&#xff…

如何发布GAE的应用(一)

安装googleSDK的环境&#xff1a; 1 下载安装包从官网下载 https://cloud.google.com/sdk/downloads -> https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-170.0.0-windows-x86_64-bundled-python.zip 2 如果本地安装了python&#xff0c;直…

leetcode887 鸡蛋掉落

你将获得 K 个鸡蛋&#xff0c;并可以使用一栋从 1 到 N 共有 N 层楼的建筑。 每个蛋的功能都是一样的&#xff0c;如果一个蛋碎了&#xff0c;你就不能再把它掉下去。 你知道存在楼层 F &#xff0c;满足 0 < F < N 任何从高于 F 的楼层落下的鸡蛋都会碎&#xff0c;…

Docker 的日志相关整理

1 Docker daemon日志的位置 Docker daemon日志的位置&#xff0c;根据系统不同各不相同。 Ubuntu - /var/log/upstart/docker.logBoot2Docker - /var/log/docker.logDebian GNU/Linux - /var/log/daemon.logCentOS - /var/log/daemon.log | grep dockerFedora - journalctl -u…

PaperNotes(15)-图神经网络、PyG极简版入门笔记

图神经网络概况1.GNN,GCN,GE的区别2.图卷积的通式--矩阵该如何作用2.1实现12.2实现22.3实现33.PyTorch geometric3.1 PyG内置数据集3.1.1ENZYMES dataset3.1.2Cora3.2 PyG自定义数据集3.2.1Data构建简单的图结构3.2.2 Dataset3.2.3 InMemoryDataset一文读懂图卷积GCN(https://z…

leetcode76 最小覆盖子串

给你一个字符串 S、一个字符串 T&#xff0c;请在字符串 S 里面找出&#xff1a;包含 T 所有字母的最小子串。 示例&#xff1a; 输入: S "ADOBECODEBANC", T "ABC" 输出: "BANC" 说明&#xff1a; 如果 S 中不存这样的子串&#xff0c;则返…

Unity的匹配系统

这个匹配系统是指一个玩家&#xff0c;可以创建一个自己随意命名的房间&#xff0c;然后其他玩家可以通过联网去搜索房间&#xff0c;然后加入房间一起游戏 我先讲讲怎么使用这个匹配系统&#xff1a; 在运行游戏后&#xff0c;因为添加了Network Manager HUD组件&#xff0c;所…

PaperNotes(16)-图神经网络GNN简史、不动点建模-笔记

图神经网络简史、简介1.图神经网络简史2.图神经网络--学习过程3.图神经网络--理论基础4.图神经网络的局限5.GNN,RNN,GGNN6.小结阅读笔记&#xff1a;从图(Graph)到图卷积(Graph Convolution)&#xff1a;漫谈图神经网络模型 (一)(https://www.cnblogs.com/SivilTaram/p/graph_n…

Matchmaker

Unity的多玩家网络功能包含了玩家在因特网上互相玩而不需要公共IP地址的服务。用户可以创建游戏,获取活动游戏列表;加入并退出游戏。当在internet上玩时,网络流量将通过云中的Unity,而不是直接在客户端之间进行。这就避免了防火墙和NATs的问题,几乎可以在任何地方玩游戏。 …

PaperNotes(17)-图卷积神经网络GCN-笔记

图卷积神经网络GCN-笔记1.卷积是什么2.图卷积的源起3.空域卷积3.1消息传递网络MPNN3.2 图采样与聚合GraphSage4.频域卷积5.图结构的序列化-Patch-SAN从图(Graph)到图卷积(Graph Convolution)&#xff1a;漫谈图神经网络模型 (二)(https://www.cnblogs.com/SivilTaram/p/graph_n…

Servlet 工程 web.xml 中的 servlet 和 servlet-mapping 标签

摘录某个工程的 web.xml 文件片段&#xff1a;访问顺序为1—>2—>3—>4&#xff0c;其中2和3的值必须相同。 url-pattern 标签中的值是要在浏览器地址栏中输入的 url&#xff0c;可以自己命名&#xff0c;这个 url 访问名为 servlet-name 中值的 servlet&#xff0c;两…

leetcode236 二叉树的最近公共祖先

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个结点 p、q&#xff0c;最近公共祖先表示为一个结点 x&#xff0c;满足 x 是 p、q 的祖先且 x 的深度尽可能大&#xff08;一个节点也可以是它自己的…

Unity的 UNet组件介绍

UNet常见概念简介 Spawn:简单来说,把服务器上的GameObject,根据上面的NetworkIdentity组件找到对应监视连接,在监视连接里生成相应的GameObject.Command:客户端调用,服务器执行,这样客户端调用的参数必需要UNet可以序列化,这样服务器在执行时才能把参数反序列化。需要注意…

MachineLearning(10)-聚类

聚类1.K-mean2.系统聚类3.DBSCAN聚类算法聚类&#xff1a;无监督学习&#xff0c;将相似的样本聚为一类。核心如何定义相似。分类&#xff1a;有监督学习&#xff0c;依据分类准则&#xff0c;将样本划分为不同的类。核心分类器的设计&#xff08;KNN&#xff09;聚类&#xff…

帧同步和状态同步(一)

帧同步 什么是帧同步&#xff1a;帧同步常被RTS(即时战略)游戏常采用。在游戏中同步的是玩家的操作指令&#xff0c;操作指令包含当前的帧索引。一般的流程是客户端上传操作到服务器&#xff0c; 服务器收到后并不计算游戏行为&#xff0c; 而是转发到所有客户端。这里最重要的…