22.面向对象-继承-里氏替换原则
22.1 知识点
基本概念
- 里氏替换原则是面向对象七大原则中重要的原则
- 概念:任何父类出现的地方,子类都可以替代
- 重点:语法表现——父类容器装子类对象,因为子类对象包含了父类的所有内容
- 作用:方便进行对象存储和管理
基本实现
#pragma once
#include <iostream>
using namespace std;
class GameObject
{
public:
int id = 0;
string name = "";
virtual void Test()
{
cout << "Test" << endl;
}
};
#pragma once
#include "GameObject.h"
class Player : public GameObject
{
public:
void PlayerAtk()
{
cout << "Player攻击" << endl;
}
};
#pragma once
#include "GameObject.h"
class Monster : public GameObject
{
public:
void MonsterAtk()
{
cout << "Monster攻击" << endl;
}
};
#pragma once
#include "GameObject.h"
class Boss : public GameObject
{
public:
void BossAtk()
{
cout << "Boss攻击" << endl;
}
};
// 栈上分配
GameObject player = Player();
GameObject monster = Monster();
GameObject boss = Boss();
GameObject objects[3] = { Player(), Monster(), Boss() };
// 堆上分配
GameObject* player2 = new Player();
GameObject* monster2 = new Monster();
GameObject* boss2 = new Boss();
GameObject* objects2[3] = { new Player(), new Monster(), new Boss() };
栈上的里氏替换
- 虽然我们可以在栈上用父类容器装载子类对象
- 但是不建议大家使用栈上的里氏替换
主要原因如下:
- 对象切片:用父类容器装载子类对象,子类部分会被裁掉
- 无法转换:无法将父类容器转换为子类对象
因此,绝大多数情况下,我们应当避免在栈上使用里氏替换
堆上的里氏替换
- 当在堆上使用里氏替换原则
- 用父类容器装载子类对象
- 不会出现对象切片,并且可以进行转换
- 我们可以通过
dynamic_cast
将父类容器转为子类对象使用 - 当学了多态后,更能体会这一点(因为需要虚函数)
转换方法说明:
<>
是传入想要转换为的类型()
是具体想要转换谁
示例:
// 把 GameObject 转成 Player
Player* p = dynamic_cast<Player*>(player2);
// 转换成功 就不会为 nullptr;如果失败 就会为 nullptr
if (p != nullptr)
{
p->Test(); // Test
p->PlayerAtk(); // Player攻击
}
else
cout << "不是Player类型" << endl;
// 把 Player 转成 GameObject(但一般不会转,因为子类就应该包含了父类所有内容)
GameObject* g = dynamic_cast<GameObject*>(p);
if (g != nullptr)
{
cout << "转换成功" << endl; // 转换成功
}
总结
里式替换 —— 用父类容器装载子类对象
栈上不建议大家使用,因为会发生对象切片
堆上是经常会使用的,我们会通过
dynamic_cast
和判空组合使用来进行转换,或者判断某一个指针对象是不是某一个类型里氏替换的主要作用:用同一个父类容器装载不同类型的子类对象
22.2 知识点代码
GameObject.h
#pragma once
#include <iostream>
using namespace std;
class GameObject
{
public:
int id = 0;
string name = "";
virtual void Test()
{
cout << "Test" << endl;
}
};
Player.h
#pragma once
#include "GameObject.h"
class Player :
public GameObject
{
public:
void PlayerAtk()
{
cout << "Player攻击" << endl;
}
};
Monster.h
#pragma once
#include "GameObject.h"
class Monster :
public GameObject
{
public:
void MonsterAtk()
{
cout << "Monster攻击" << endl;
}
};
Boss.h
#pragma once
#include "GameObject.h"
class Boss :
public GameObject
{
public:
void BossAtk()
{
cout << "Boss攻击" << endl;
}
};
Lesson22_面向对象_继承_里氏替换原则.cpp
#include <iostream>
#include "Boss.h"
#include "Monster.h"
#include "Player.h"
int main()
{
#pragma region 知识点一 基本概念
// 里氏替换原则是面向对象七大原则中最重要的原则
// 概念:
// 任何父类出现的地方,子类都可以替代
// 重点:
// 语法表现——父类容器装子类对象,因为子类对象包含了父类的所有内容
// 作用:
// 方便进行对象存储和管理
#pragma endregion
#pragma region 知识点二 基本实现
//栈上分配
GameObject player = Player();
GameObject monster = Monster();
GameObject boss = Boss();
GameObject objects[3] = { Player(), Monster(), Boss() };
//堆上分配
GameObject* player2 = new Player();
GameObject* monster2 = new Monster();
GameObject* boss2 = new Boss();
GameObject* objects2[3] = { new Player(), new Monster(), new Boss() };
#pragma endregion
#pragma region 知识点三 栈上的里氏替换
//虽然我们可以在栈上用父类容器装载子类对象
//但是不建议大家使用栈上的里氏替换
//主要原因:
//1.对象切片:用父类容器装载子类对象,子类部分会被裁掉
//2.无法转换:无法将父类容器转换为子类对象
//因此,绝大多数情况下,我们应当避免在栈上使用里氏替换
#pragma endregion
#pragma region 知识点四 堆上的里氏替换
//当在堆上使用里氏替换原则
//用父类容器装载子类对象
//不会出现对象切片,并且可以进行转换
//我们可以通过 dynamic_cast 将父类容器转为子类对象使用
//当学了多态后,更能体会这一点(因为需要虚函数)
//一个转换的方法
//<>是传入想要转换为的类型
//()是具体想要转换谁
//把GameObject转成Player
Player* p = dynamic_cast<Player*>(player2);
//转换成功 就不会为nullptr 如果失败 就会为nullptr
if (p != nullptr)
{
p->Test();//Test
p->PlayerAtk();//Player攻击
}
else
cout << "不是Player类型" << endl;
//把Player转成GameObject 但一般不会转 因为子类就应该包含了父类所有内容
GameObject* g = dynamic_cast<GameObject*>(p);
if (g != nullptr)
{
cout << "转换成功" << endl;//转换成功
}
#pragma endregion
//总结
//里式替换——用父类容器装载子类对象
//栈上不建议大家使用 因为会 对象切片
//堆上是经常会使用的 我们会通过 dynamic_cast 和 判空 组合使用 来进行转换
//或者判断某一个指针对象 是不是某一个类型
//里氏替换的主要作用
//用同一父类容器 装载不同类型的子类对象
}
22.3 练习题
栈上和堆上的里氏替换有什么区别
#pragma once
#include <iostream>
using namespace std;
class A
{
public:
virtual void Test()
{
cout << "Test" << endl;
}
};
#pragma once
#include "A.h"
class B : public A
{
public:
void BTest()
{
cout << "BTest" << endl;
}
};
说明:
栈上的值存储会发生对象切片:
// A // B : A // A a = B(); // 此处发生对象切片,只保留了 B 对象的 A 部分
栈上的引用不会发生对象切片:
B b = B(); A& a = b; // 里氏替换,不会发生对象切片 B* ptr = dynamic_cast<B*>(&a); if (ptr != nullptr) { ptr->BTest(); // 输出 BTest ptr->Test(); // 输出 Test }
堆上的对象也不会发生对象切片,与引用情况类似。
写一个 Monster 类,它派生出 Boss 和 Goblin 两个类,Boss 有技能,小怪有攻击;随机生成 10 个怪,装载到数组中,遍历这个数组,调用它们的攻击方法,如果是 Boss 就释放技能
#pragma once
#include<iostream>
using namespace std;
class Monster
{
public:
virtual void Test()
{
}
};
#pragma once
#include "Monster.h"
class Boss : public Monster
{
public:
void Skill()
{
cout << "boss技能" << endl;
}
};
#pragma once
#include "Monster.h"
class Goblin : public Monster
{
public:
void Atk()
{
cout << "哥布林攻击" << endl;
}
};
Monster* monsters[10];
int random = 0;
// 随机生成10个怪物,装载到数组中
for (int i = 0; i < 10; i++)
{
random = getRandom(1, 100);
if (random < 50)
monsters[i] = new Boss();
else
monsters[i] = new Goblin();
}
// 遍历数组,释放技能或攻击
for (int i = 0; i < 10; i++)
{
Boss* boss = dynamic_cast<Boss*>(monsters[i]);
// 如果是 Boss 就释放技能
if (boss != nullptr)
{
boss->Skill();
}
else
{
Goblin* goblin = dynamic_cast<Goblin*>(monsters[i]);
// 如果是哥布林就攻击
if (goblin != nullptr)
{
goblin->Atk();
}
}
}
FPS 游戏模拟:写一个玩家类,玩家可以拥有各种武器,现在有四种武器,冲锋枪、散弹枪、手枪、匕首,玩家默认拥有匕首。请在玩家类中写一个方法,可以拾取不同的武器替换自己拥有的枪械
#pragma once
class Weapon
{
};
#pragma once
#include "Weapon.h"
class SubmachineGun : public Weapon
{
};
#pragma once
#include "Weapon.h"
class ShotGun : public Weapon
{
};
#pragma once
#include "Weapon.h"
class Pistol : public Weapon
{
};
#pragma once
#include "Weapon.h"
class Dagger : public Weapon
{
};
#pragma once
#include "Pistol.h"
class Player
{
private:
Weapon* weapon = nullptr;
public:
Player()
{
// 玩家默认拥有匕首
weapon = new Pistol();
}
void PickUp(Weapon* weapon)
{
if (this->weapon != nullptr)
{
delete this->weapon;
// this->weapon = nullptr;
}
this->weapon = weapon;
}
};
Player* player = new Player();
SubmachineGun* sg = new SubmachineGun();
player->PickUp(sg);
ShotGun* sg2 = new ShotGun();
player->PickUp(sg2);
22.4 练习题代码
A.h
#pragma once
#include <iostream>
using namespace std;
class A
{
public:
virtual void Test()
{
cout << "Test" << endl;
}
};
B.h
#pragma once
#include "A.h"
class B :public A
{
public:
void BTest()
{
cout << "BTest" << endl;
}
};
Monster.h
#pragma once
#include<iostream>
using namespace std;
class Monster
{
public:
virtual void Test()
{
}
};
Boss.h
#pragma once
#include "Monster.h"
class Boss :
public Monster
{
public:
void Skill()
{
cout << "boss技能" << endl;
}
};
Goblin.h
#pragma once
#include "Monster.h"
class Goblin :
public Monster
{
public:
void Atk()
{
cout << "哥布林攻击" << endl;
}
};
Weapon.h
#pragma once
class Weapon
{
};
Dagger.h
#pragma once
#include "Weapon.h"
class Dagger :
public Weapon
{
};
Pistol.h
#pragma once
#include "Weapon.h"
class Pistol :
public Weapon
{
};
ShotGun.h
#pragma once
#include "Weapon.h"
class ShotGun :
public Weapon
{
};
SubmachineGun.h
#pragma once
#include "Weapon.h"
class SubmachineGun :
public Weapon
{
};
Player.h
#pragma once
#include "Pistol.h";
class Player
{
private:
Weapon* weapon = nullptr;
public:
Player()
{
//玩家默认拥有匕首
weapon = new Pistol();
}
void PickUp(Weapon* weapon)
{
if (this->weapon != nullptr)
{
delete this->weapon;
//this->weapon = nullptr;
}
this->weapon = weapon;
}
};
Lesson22_练习题.cpp
#include <iostream>
#include "B.h"
#include "Boss.h"
#include "Goblin.h"
#include "CustomConsole.h"
#include "Player.h"
#include "SubmachineGun.h"
#include "ShotGun.h"
int main()
{
#pragma region 练习一
//栈上和堆上的里氏替换有什么区别
//栈上的值存储会存在 会进行对象切片
// A
// B : A
// A a = B();
//
//栈上的引用存储 不存在切片的
// B b = B();
// A& a = b;
// 这种情况下 我们甚至可以利用dynamic_cast去对引用进行转换
B b = B();
A& a = b; //里氏替换 不会对象切片
B* ptr = dynamic_cast<B*>(&a);
if (ptr != nullptr)
{
ptr->BTest();//BTest
ptr->Test();//Test
}
//堆上是不存在这样的问题的
#pragma endregion
#pragma region 练习二
//写一个Monster类,它派生出Boss和Goblin两个类,Boss有技能;
//小怪有攻击;随机生成10个怪,装载到数组中,遍历这个数组,调用他们的攻击方法,如果是boss就释放技能
Monster* monsters[10];
int random = 0;
//随机生成10个怪物 装载到了数组中
for (int i = 0; i < 10; i++)
{
random = getRandom(1, 100);
if (random < 50)
monsters[i] = new Boss();
else
monsters[i] = new Goblin();
}
for (int i = 0; i < 10; i++)
{
Boss* boss = dynamic_cast<Boss*>(monsters[i]);
//如果是boss 就释放技能
if (boss != nullptr)
{
boss->Skill();
}
else
{
Goblin* goblin = dynamic_cast<Goblin*>(monsters[i]);
//如果是哥布林 就攻击
if (goblin != nullptr)
{
goblin->Atk();
}
}
}
#pragma endregion
#pragma region 练习三
/*FPS游戏模拟
写一个玩家类,玩家可以拥有各种武器
现在有四种武器,冲锋枪,散弹枪,手枪,匕首
玩家默认拥有匕首
请在玩家类中写一个方法,可以拾取不同的武器替换自己拥有的枪械*/
Player* player = new Player();
SubmachineGun* sg = new SubmachineGun();
player->PickUp(sg);
ShotGun* sg2 = new ShotGun();
player->PickUp(sg2);
#pragma endregion
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com