多态性与虚函数
- 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 的头文件都引入不了