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


《老九学堂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 的头文件都引入不了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值