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.先说系统和环境…

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

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

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位…

leetcode887 鸡蛋掉落

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

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;则返…

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;一个节点也可以是它自己的…

leetcode279 完全平方数

给定正整数 n&#xff0c;找到若干个完全平方数&#xff08;比如 1, 4, 9, 16, ...&#xff09;使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。 示例 1: 输入: n 12 输出: 3 解释: 12 4 4 4. 示例 2: 输入: n 13 输出: 2 解释: 13 4 9. 思路&#xf…

leetcode240. 搜索二维矩阵 II

编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target。该矩阵具有以下特性&#xff1a; 每行的元素从左到右升序排列。 每列的元素从上到下升序排列。 示例: 现有矩阵 matrix 如下&#xff1a; [ [1, 4, 7, 11, 15], [2, 5, 8, 12, 19], [3, 6…

NAT原理

网络地址转换(NAT,Network Address Translation)属接入广域网(WAN)技术&#xff0c;是一种将私有&#xff08;保留&#xff09;地址转化为合法IP地址的转换技术。下面介绍两类不同方式实现的NAT&#xff1a;NAT(Network Address Translators)&#xff1a;称为基本的NAT在客户机…

APK 安卓反编译

在学习Android开发的过程你&#xff0c;你往往会去借鉴别人的应用是怎么开发的&#xff0c;那些漂亮的动画和精致的布局可能会让你爱不释手&#xff0c;作为一个开发者&#xff0c;你可能会很想知道这些效果界面是怎么去实现的&#xff0c;这时&#xff0c;你便可以对改应用的A…

leetcode141 环形链表

给定一个链表&#xff0c;判断链表中是否有环。 为了表示给定链表中的环&#xff0c;我们使用整数 pos 来表示链表尾连接到链表中的位置&#xff08;索引从 0 开始&#xff09;。 如果 pos 是 -1&#xff0c;则在该链表中没有环。 示例 1&#xff1a; 输入&#xff1a;head …

iPhoneX适配

目录(?)[-]核心代码自动化修改代码参考资料iPhoneX适配&#xff0c;比较搓的一种方式&#xff0c;在不修改分辨率&#xff08;720 x 1280&#xff09;的情况下适配iphone X 主屏尺寸&#xff1a; 5.8英寸 主屏分辨率&#xff1a; 2436 x 1125核心代码修改 工程目录/Classes/Un…

leetcode142 环形链表II

给定一个链表&#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 为了表示给定链表中的环&#xff0c;我们使用整数 pos 来表示链表尾连接到链表中的位置&#xff08;索引从 0 开始&#xff09;。 如果 pos 是 -1&#xff0c;则在该链表中没有…

jquery、javascript实现(get、post两种方式)跨域解决方法

&#xfeff;&#xfeff;jquery、javascript实现(get、post两种方式)跨域解决方法一、实现get方式跨域请求数据浏览器端<script> $(document).ready(function(){$.ajax({url: "http://www.xxx.cn/index.php",type: "get",dataType: "jsonp&quo…

leetcode41 缺失的第一个正数

给定一个未排序的整数数组&#xff0c;找出其中没有出现的最小的正整数。 示例 1: 输入: [1,2,0] 输出: 3 示例 2: 输入: [3,4,-1,1] 输出: 2 示例 3: 输入: [7,8,9,11,12] 输出: 1 说明: 你的算法的时间复杂度应为O(n)&#xff0c;并且只能使用常数级别的空间。 思路&am…

Linux(11)-Ubuntu装系统

Ubuntu18.04装系统单系统双系统启动项dell 5820进不去bios。单系统 装过好多次ubuntu系统&#xff0c;每次要重装之前总是得搜索各种教程。现在总结一份简略教程如下&#xff0c;以备哪天装系统之需。 1.官网上下载Ios文件:https://ubuntu.com/download/desktop 2.找一个U盘…

Harris的角点检测和特征匹配

一.特征检测&#xff08;提取&#xff09; 基于特征的图像配准方法是图像配准中最常见的方法之一。它不是直接利用图像像素值&#xff0c;二十通过像素值导出的符号特征&#xff08;如特征点、特征线、特征区域&#xff09;来实现图像配准&#xff0c;因此可以克服利用灰度信息…

开始入坑深度学习(DeepLearning)

现在游戏越来越难做,国家广电总局审核越来越变态,国家各种打压游戏,游戏产业也成为教育失败的背锅侠,所以本人现在开始做深度学习方向。 深度学习研究的热潮持续高涨,各种开源深度学习框架也层出不穷,其中包括TensorFlow、Caffe、Keras、CNTK、Torch7、MXNet、Leaf、The…