多態性與虛函數
- 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 的頭文件都引入不了