多态性与虚函数

  • 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 的头文件都引入不了

C++(23)--多态性与虚函数相关推荐

  1. C++ 的多态性与虚函数

    腾讯二面时被问到关于C++的多态性和虚函数,当时脑袋发热没回答好,其实根本原因是这是很久之前看的,而且一直没有应用上,所以理所当然说不出个所以然来.趁着国庆假期,把C++草草捡了些回来,现在主要说下多 ...

  2. C++面试题-面向对象-多态性与虚函数

    C++面试题-面向对象-多态性与虚函数 问:在C++程序中调用被C编译器编译后的函数,为什么要加extern "C"? 答:C++语言支持函数重载,C语言不支持函数重载.函数被C+ ...

  3. 头歌C++面向对象 - 类的多态性与虚函数

    C++ 面向对象 - 类的多态性与虚函数 一.实训目的 1.掌握虚函数的基本使用. 2.掌握虚析构函数的使用. 3.掌握纯虚函数和抽象类的使用. 二.实训内容 1.人与复读机 设计人类.英语学生类和复 ...

  4. C++ 多态性和虚函数

    多态性是指当类之间存在层次结构,并且类之间是通过继承关联时,此时调用成员函数,会根据调用函数的对象的类型来执行不同的函数. 多态性可分为静态多态性和动态多态性: 静态多态性,通过函数重载实现,在编译时 ...

  5. C++之多态性与虚函数

    面向对象程序设计中的多态性是指向不同的对象发送同一个消息,不同对象对应同一消息产生不同行为.在程序中消息就是调用函数,不同的行为就是指不同的实现方法,即执行不同的函数体.也可以这样说就是实现了&quo ...

  6. 函数的多态性以及虚函数

    包含一个及一个以上的纯虚函数的类就是抽象基类,抽象基类没有也不能必要定义对象. **#include <iostream> using namespace std; class Shape ...

  7. c 结构体在声明时赋值_C/C++编程笔记:C++入门知识,C++多态性和虚函数解析

    本篇要学习的内容和知识结构概览 多态性 编译时的多态性称为静态联编. 当调用重载函数时, 在编译期就确定下来调用哪个函数. 运行时的多态性称为动态联编. 在运行时才能确定调用哪个函数, 由虚函数来支持 ...

  8. C++ 多态性之虚函数抽象类纯虚函数

    一. 虚函数 1.什么是虚函数:虚函数的定义是在基类中进行的,被 virtual 修饰的,当基类中的某个成员函数被声明为"虚函数"后,可以在一个或多个派生类中重新定义该函数,重新定 ...

  9. C++ 面向对象 - 类的多态性与虚函数

    任务描述 本关任务:设计人类.英语学生类和复读机类三个类. 相关知识 为了完成完成本关任务,你需要掌握虚函数的基本使用. 多态性 在面向对象的方法中,多态性是指向不同对象发送同一个消息,不同对象在接收 ...

最新文章

  1. 初始化CISCO路由器和交换机密码
  2. jupyter notebook python插件_VS Code Python 将支持 Jupyter Notebook
  3. Android之稍微靠谱点的透明Activity(不获取触摸事件)
  4. Cow Bowling POJ - 3176(基础的动态规划算法)
  5. Codeforces Global Round 15 (A-D)没有C
  6. YOLOv3改进方法增加特征尺度和训练层数
  7. indexOf、lastIndexOf、substring等详解
  8. apache开源项目--Apache Commons Imaging
  9. Python:PDF文件转图像
  10. 2019一注结构成绩_2019年福建地区计算机考研汇总分析
  11. 烽火路由路虚拟服务器,烽火路由器怎么设置普通专线?
  12. 西方哲学史人物学说时间线
  13. git push reject : pre-receive hook declined
  14. 白云市场高仿包值不值得买?
  15. 舞伴问题(数据结构队列,c语言版)
  16. python pygame 动画_pygame行走的小猫多帧动画演示程序
  17. python抓取静态网页
  18. python之路金角大王_python 之路,致那些年,我们依然没搞明白的编码
  19. php和plc哪个难,西门子plc和三菱plc那个好学?西门子plc和三菱plc的区别
  20. 简练软考知识点整理-项目配置审核

热门文章

  1. res_config_mysql和chan_sip模块的加载分析
  2. asterisk 扩展应用(3)——IVR 实现
  3. BootLoader与Linux内核的参数传递
  4. VxWorks下几种定时延时方法的小结
  5. html 调用c#dll中的控件,C#调用ActiveX控件的方法
  6. 延边大学c语言题库,延边大学-SPOC官方网站
  7. 【转】算法导论学习笔记 一 分治算法
  8. 【转】第01课:生活中的监听模式——一坑爹的热水器
  9. 【转】.NET 的 WebSocket 开发包比较
  10. SharePoint 开发TimerJob 介绍