24-x 第二十四章 概要与测验(todo)
概要
继承使我们能够在两个对象之间建立“是”的关系。被继承的对象称为父类、基类或超类。进行继承的对象称为子类、派生类或子类。
- 当派生类从基类继承时,派生类将获得基类的全部成员。
- 派生类构造时,先构造基类部分,再构造派生部分。具体流程如下:
- 为派生类预留内存(需同时容纳基类与派生类部分)。
- 调用对应的派生类构造函数。
- 使用基类构造函数优先构造基类对象。若未指定基类构造函数,则使用默认构造函数。
- 派生类的初始化列表对派生类成员进行初始化。
- 派生类构造函数主体执行。
- 控制权返回调用方。
析构过程按相反顺序进行,从最派生类到最基类。
C++有3种访问限定符:public、private和protected。protected限定符允许所属类、友元类及派生类访问受保护成员,但不允许public访问。
类可通过公用(publicly)、私有(privately)或受保护(protectedly)方式继承自其他类。类继承通常采用公用方式。
下表展示了所有访问限定符与继承类型的组合关系:
| Access specifier in base class | Access specifier when inherited publicly | Access specifier when inherited privately | Access specifier when inherited protectedly |
|---|---|---|---|
| Public | Public | Private | Protected |
| Private | Inaccessible | Inaccessible | Inaccessible |
| Protected | Protected | Private | Protected |
派生类可以添加新功能,改变基类中现有函数在派生类中的工作方式,修改继承成员的访问权限级别,或隐藏功能。
多重继承使派生类能够从多个父类继承成员。通常应避免多重继承,除非替代方案会导致更复杂的情况。
测验时间
问题 #1
对于以下每个程序,确定它们的输出结果,或若无法编译则说明原因。本练习需通过检查完成,请勿编译这些程序(否则答案将变得简单)。
a)
#include <iostream>
class Base
{
public:
Base()
{
std::cout << "Base()\n";
}
~Base()
{
std::cout << "~Base()\n";
}
};
class Derived: public Base
{
public:
Derived()
{
std::cout << "Derived()\n";
}
~Derived()
{
std::cout << "~Derived()\n";
}
};
int main()
{
Derived d;
return 0;
}
显示答案
构造操作按从最父元素到最子元素的顺序进行。销毁操作则按相反顺序进行。
Base()
Derived()
~Derived()
~Base()
b)
#include <iostream>
class Base
{
public:
Base()
{
std::cout << "Base()\n";
}
~Base()
{
std::cout << "~Base()\n";
}
};
class Derived: public Base
{
public:
Derived()
{
std::cout << "Derived()\n";
}
~Derived()
{
std::cout << "~Derived()\n";
}
};
int main()
{
Derived d;
Base b;
return 0;
}
提示:局部变量按与定义相反的顺序被销毁。
显示解答
首先我们构造d,其输出为:
Base()
Derived()
然后我们构造 b,其输出为:
Base()
然后我们销毁 b,这会输出:
~Base()
然后我们销毁变量d,这将输出:
~Derived()
~Base()
c)
#include <iostream>
class Base
{
private:
int m_x {};
public:
Base(int x): m_x{ x }
{
std::cout << "Base()\n";
}
~Base()
{
std::cout << "~Base()\n";
}
void print() const { std::cout << "Base: " << m_x << '\n'; }
};
class Derived: public Base
{
public:
Derived(int y): Base{ y }
{
std::cout << "Derived()\n";
}
~Derived()
{
std::cout << "~Derived()\n";
}
void print() const { std::cout << "Derived: " << m_x << '\n'; }
};
int main()
{
Derived d{ 5 };
d.print();
return 0;
}
显示答案
无法编译,派生类::print() 无法访问私有成员 m_x
d)
#include <iostream>
class Base
{
protected:
int m_x {};
public:
Base(int x): m_x{ x }
{
std::cout << "Base()\n";
}
~Base()
{
std::cout << "~Base()\n";
}
void print() const { std::cout << "Base: " << m_x << '\n'; }
};
class Derived: public Base
{
public:
Derived(int y): Base{ y }
{
std::cout << "Derived()\n";
}
~Derived()
{
std::cout << "~Derived()\n";
}
void print() const { std::cout << "Derived: " << m_x << '\n'; }
};
int main()
{
Derived d{ 5 };
d.print();
return 0;
}
显示答案
Base()
Derived()
Derived: 5
~Derived()
~Base()
e)
#include <iostream>
class Base
{
protected:
int m_x {};
public:
Base(int x): m_x{ x }
{
std::cout << "Base()\n";
}
~Base()
{
std::cout << "~Base()\n";
}
void print() const { std::cout << "Base: " << m_x << '\n'; }
};
class Derived: public Base
{
public:
Derived(int y): Base{ y }
{
std::cout << "Derived()\n";
}
~Derived()
{
std::cout << "~Derived()\n";
}
void print() const { std::cout << "Derived: " << m_x << '\n'; }
};
class D2 : public Derived
{
public:
D2(int z): Derived{ z }
{
std::cout << "D2()\n";
}
~D2()
{
std::cout << "~D2()\n";
}
// note: no print() function here
};
int main()
{
D2 d{ 5 };
d.print();
return 0;
}
显示答案
Base()
Derived()
D2()
Derived: 5
~D2()
~Derived()
~Base()
问题 #2
a) 编写一个继承自共同 Fruit 类的 Apple 类和 Banana 类。Fruit 类应包含两个成员:名称和颜色。
以下程序应能运行:
int main()
{
Apple a{ "red" };
Banana b{};
std::cout << "My " << a.getName() << " is " << a.getColor() << ".\n";
std::cout << "My " << b.getName() << " is " << b.getColor() << ".\n";
return 0;
}
并输出结果:
My apple is red.
My banana is yellow.

显示答案
#include <iostream>
#include <string>
#include <string_view>
class Fruit
{
private:
std::string m_name;
std::string m_color;
public:
Fruit(std::string_view name, std::string_view color)
: m_name{ name }, m_color{ color }
{
}
const std::string& getName() const { return m_name; }
const std::string& getColor() const { return m_color; }
};
class Apple: public Fruit
{
public:
Apple(std::string_view color="red")
: Fruit{ "apple", color }
{
}
};
class Banana : public Fruit
{
public:
Banana()
: Fruit{ "banana", "yellow" }
{
}
};
int main()
{
Apple a{ "red" };
Banana b;
std::cout << "My " << a.getName() << " is " << a.getColor() << ".\n";
std::cout << "My " << b.getName() << " is " << b.getColor() << ".\n";
return 0;
}
b) 在先前程序中添加一个名为 GrannySmith 的新类,该类继承自 Apple。
以下程序应能运行:
int main()
{
Apple a{ "red" };
Banana b;
GrannySmith c;
std::cout << "My " << a.getName() << " is " << a.getColor() << ".\n";
std::cout << "My " << b.getName() << " is " << b.getColor() << ".\n";
std::cout << "My " << c.getName() << " is " << c.getColor() << ".\n";
return 0;
}
并输出结果:
My apple is red.
My banana is yellow.
My granny smith apple is green.

显示答案
#include <iostream>
#include <string>
#include <string_view>
class Fruit
{
private:
std::string m_name;
std::string m_color;
public:
Fruit(std::string_view name, std::string_view color)
: m_name{ name }, m_color{ color }
{
}
const std::string& getName() const { return m_name; }
const std::string& getColor() const { return m_color; }
};
class Apple: public Fruit
{
// The previous constructor we used for Apple had a fixed name ("apple").
// We need a new constructor for GrannySmith to use to set the name of the fruit
protected: // protected so only derived classes can access
Apple(std::string_view name, std::string_view color)
: Fruit{ name, color }
{
}
public:
Apple(std::string_view color="red")
: Fruit{ "apple", color }
{
}
};
class Banana : public Fruit
{
public:
Banana()
: Fruit{ "banana", "yellow" }
{
}
};
class GrannySmith : public Apple
{
public:
GrannySmith()
: Apple{ "granny smith apple", "green" }
{
}
};
int main()
{
Apple a{ "red" };
Banana b;
GrannySmith c;
std::cout << "My " << a.getName() << " is " << a.getColor() << ".\n";
std::cout << "My " << b.getName() << " is " << b.getColor() << ".\n";
std::cout << "My " << c.getName() << " is " << c.getColor() << ".\n";
return 0;
}
问题 #3(todo)
挑战时刻!以下测验题难度更高且篇幅较长。我们将编写一款简单的战斗游戏,玩家需对抗怪物。游戏目标是在死亡或达到20级前尽可能收集更多金币。
程序将包含三个类:Creature(生物)类、Player(玩家)类和Monster(怪物)类。Player和Monster均继承自Creature类。
a) 首先创建生物类。生物具有5个属性:名称(std::string)、图标(char)、生命值(int)、每次攻击造成的伤害值(int)以及携带的金币数量(int)。将这些属性实现为类成员。编写完整的获取器函数(每个成员对应一个get函数)。另添加三项功能:void reduceHealth(int) 使生物生命值减少指定整数值;bool isDead() 当生命值为0或更低时返回true;void addGold(int) 为生物增加金币。
需运行以下程序:
#include <iostream>
#include <string>
int main()
{
Creature o{ "orc", 'o', 4, 2, 10 };
o.addGold(5);
o.reduceHealth(1);
std::cout << "The " << o.getName() << " has " << o.getHealth() << " health and is carrying " << o.getGold() << " gold.\n";
return 0;
}
并输出结果:
The orc has 3 health and is carrying 15 gold.
解决方案
#include <iostream>
#include <string>
#include <string_view> // Requires C++17
class Creature
{
protected:
std::string m_name;
char m_symbol {};
int m_health {};
int m_damage {};
int m_gold {};
public:
Creature(std::string_view name, char symbol, int health, int damage, int gold)
: m_name{ name }
, m_symbol{ symbol }
, m_health{ health }
, m_damage{ damage }
, m_gold{ gold }
{
}
const std::string& getName() const { return m_name; }
char getSymbol() const { return m_symbol; }
int getHealth() const { return m_health; }
int getDamage() const { return m_damage; }
int getGold() const { return m_gold; }
void reduceHealth(int health) { m_health -= health; }
bool isDead() const { return m_health <= 0; }
void addGold(int gold) { m_gold += gold; }
};
int main()
{
Creature o{ "orc", 'o', 4, 2, 10 };
o.addGold(5);
o.reduceHealth(1);
std::cout << "The " << o.getName() << " has " << o.getHealth() << " health and is carrying " << o.getGold() << " gold.\n";
return 0;
}
b) 现在我们将创建Player类。Player类继承自Creature类。Player类新增了一个成员变量——玩家等级,初始值为1。玩家拥有自定义名称(由用户输入),使用符号'@',初始生命值为10,初始伤害值为1,且没有金币。编写名为levelUp()的函数,用于将玩家等级与伤害值各提升1点。同时为level成员编写获取器。最后编写名为hasWon()的函数,当玩家达到20级时返回true。
编写新的main()函数,用于向用户征集角色名称并生成如下输出:
Enter your name: Alex
Welcome, Alex.
You have 10 health and are carrying 0 gold.
解决方案
#include <iostream>
#include <string>
#include <string_view> // std::string_view requires C++17
class Creature
{
protected:
std::string m_name;
char m_symbol {};
int m_health {};
int m_damage {};
int m_gold {};
public:
Creature(std::string_view name, char symbol, int health, int damage, int gold)
: m_name{ name }
, m_symbol{ symbol }
, m_health{ health }
, m_damage{ damage }
, m_gold{ gold }
{
}
const std::string& getName() const { return m_name; }
char getSymbol() const { return m_symbol; }
int getHealth() const { return m_health; }
int getDamage() const { return m_damage; }
int getGold() const { return m_gold; }
void reduceHealth(int health) { m_health -= health; }
bool isDead() const { return m_health <= 0; }
void addGold(int gold) { m_gold += gold; }
};
class Player : public Creature
{
int m_level{ 1 };
public:
Player(std::string_view name)
: Creature{ name, '@', 10, 1, 0 }
{
}
void levelUp()
{
++m_level;
++m_damage;
}
int getLevel() const { return m_level; }
bool hasWon() const { return m_level >= 20; }
};
int main()
{
std::cout << "Enter your name: ";
std::string playerName;
std::cin >> playerName;
Player p{ playerName };
std::cout << "Welcome, " << p.getName() << ".\n";
std::cout << "You have " << p.getHealth() << " health and are carrying " << p.getGold() << " gold.\n";
return 0;
}
c) 接下来是怪物类。怪物同样继承自生物类。怪物没有非继承的成员变量。
首先编写一个继承自Creature的空怪物类,然后在怪物类内部添加一个名为Type的枚举,其中包含游戏中将出现的3种怪物枚举项:龙dragon、兽人orc、史莱姆slime(你还需要添加一个max_types枚举项,稍后会用到它)。
解决方案
class Monster : public Creature
{
public:
enum Type
{
dragon,
orc,
slime,
max_types
};
};
d) 每种怪物类型将拥有不同的名称name、图标symbol、初始生命值starting health、金币gold和伤害值damage。以下是各类怪物属性的统计表:
| Type | Name | Symbol | Health | Damage | Gold |
|---|---|---|---|---|---|
| dragon | dragon | D | 20 | 4 | 100 |
| orc | orc | o | 4 | 2 | 25 |
| slime | slime | s | 1 | 1 | 10 |
下一步是编写Monster构造函数,以便创建怪物。该构造函数应接受Type枚举作为参数,并根据该怪物类型创建具有相应属性的怪物。
实现方式有多种(优劣各异)。但本例中,由于所有怪物属性均为预定义(非随机生成或个体定制),可采用查找表方案。该查找表将采用C风格的Creature数组形式,通过Type索引即可返回对应类型的Creature。
由于该Creature表专属于Monster类,可将其定义在Monster类内部:static inline Creature monsterData[] { },并用Creature元素初始化。
此时Monster构造函数便简单了:调用Creature的复制构造函数,并从monsterData表中传入对应的Creature对象。
以下程序应能编译通过:
#include <iostream>
#include <string>
int main()
{
Monster m{ Monster::Type::orc };
std::cout << "A " << m.getName() << " (" << m.getSymbol() << ") was created.\n";
return 0;
}
并打印:
``txt
A orc (o) was created.
解决方案
```cpp
#include <array>
#include <iostream>
#include <string>
#include <string_view>
class Creature
{
protected:
std::string m_name;
char m_symbol {};
int m_health {};
int m_damage {};
int m_gold {};
public:
Creature(std::string_view name, char symbol, int health, int damage, int gold)
: m_name{ name }
, m_symbol{ symbol }
, m_health{ health }
, m_damage{ damage }
, m_gold{ gold }
{ }
char getSymbol() const { return m_symbol; }
const std::string& getName() const { return m_name; }
bool isDead() const { return m_health <= 0; }
int getGold() const { return m_gold; }
void addGold(int gold) { m_gold += gold; }
void reduceHealth(int health) { m_health -= health; }
int getHealth() const { return m_health; }
int getDamage() const { return m_damage; }
};
class Player : public Creature
{
int m_level{ 1 };
public:
Player(const std::string& name)
: Creature{ name, '@', 10, 1, 0 }
{
}
void levelUp()
{
++m_level;
++m_damage;
}
int getLevel() const { return m_level; }
bool hasWon() const { return m_level >= 20; }
};
class Monster : public Creature
{
public:
enum Type
{
dragon,
orc,
slime,
max_types
};
private:
inline static Creature monsterData[] {
Creature { "dragon", 'D', 20, 4, 100 },
Creature { "orc", 'o', 4, 2, 25 },
Creature { "slime", 's', 1, 1, 10 }
};
static_assert(std::size(monsterData) == max_types);
public:
Monster(Type type)
: Creature{ monsterData[type] }
{
}
};
int main()
{
Monster m{ Monster::Type::orc };
std::cout << "A " << m.getName() << " (" << m.getSymbol() << ") was created.\n";
return 0;
}
e) 最后,在Monster类中添加一个名为getRandomMonster()的静态函数。该函数应从0到max_types-1之间随机选取一个数值,并返回一个具有该类型的怪物(按值传递)。(你需要将int类型静态转换为Type类型,以便传递给Monster构造函数)。
第8.15课——全局随机数(Random.h)包含可用于生成随机数的代码。
以下main函数应能运行:
#include <iostream>
#include <string>
int main()
{
for (int i{ 0 }; i < 10; ++i)
{
Monster m{ Monster::getRandomMonster() };
std::cout << "A " << m.getName() << " (" << m.getSymbol() << ") was created.\n";
}
return 0;
}
该计划的结果应随机化。
解决方案
#include "Random.h" // https://www.learncpp.com/cpp-tutorial/global-random-numbers-random-h/
#include <array>
#include <iostream>
#include <string>
#include <string_view>
class Creature
{
protected:
std::string m_name;
char m_symbol {};
int m_health {};
int m_damage {};
int m_gold {};
public:
Creature(std::string_view name, char symbol, int health, int damage, int gold)
: m_name{ name }
, m_symbol{ symbol }
, m_health{ health }
, m_damage{ damage }
, m_gold{ gold }
{ }
char getSymbol() const { return m_symbol; }
const std::string& getName() const { return m_name; }
bool isDead() const { return m_health <= 0; }
int getGold() const { return m_gold; }
void addGold(int gold) { m_gold += gold; }
void reduceHealth(int health) { m_health -= health; }
int getHealth() const { return m_health; }
int getDamage() const { return m_damage; }
};
class Player : public Creature
{
int m_level{ 1 };
public:
Player(const std::string& name)
: Creature{ name, '@', 10, 1, 0 }
{
}
void levelUp()
{
++m_level;
++m_damage;
}
int getLevel() const { return m_level; }
bool hasWon() const { return m_level >= 20; }
};
class Monster : public Creature
{
public:
enum Type
{
dragon,
orc,
slime,
max_types
};
private:
inline static Creature monsterData[] {
Creature { "dragon", 'D', 20, 4, 100 },
Creature { "orc", 'o', 4, 2, 25 },
Creature { "slime", 's', 1, 1, 10 }
};
static_assert(std::size(monsterData) == max_types);
public:
Monster(Type type)
: Creature{ monsterData[type] }
{
}
static Monster getRandomMonster()
{
int num{ Random::get(0, max_types - 1) };
return Monster{ static_cast<Type>(num) };
}
};
int main()
{
for (int i{ 0 }; i < 10; ++i)
{
Monster m{ Monster::getRandomMonster() };
std::cout << "A " << m.getName() << " (" << m.getSymbol() << ") was created.\n";
}
return 0;
}
f) 我们终于要编写游戏逻辑了!
游戏规则如下:
玩家每次遭遇一个随机生成的怪物。
面对每个怪物,玩家有两种选择:(R)逃跑或(F)战斗。
若选择逃跑,有50%概率成功脱身。
若成功逃脱,玩家将无损进入下一次遭遇战。
若逃脱失败,怪物将发动一次免费攻击,玩家需选择后续行动。
若选择战斗,玩家先手攻击。怪物生命值将扣除玩家造成的伤害值。
若怪物死亡,玩家可拾取其携带的金币,同时升级:等级与伤害值各提升1点。
若怪物存活,将对玩家进行反击。玩家生命值将扣除怪物造成的伤害值。
当玩家死亡(失败)或达到20级(胜利)时游戏结束
若玩家死亡,游戏应告知当前等级与金币数量
若玩家获胜,游戏应宣告胜利并显示最终金币数
以下为游戏实例:
输入姓名:Alex
欢迎,Alex
你遭遇了史莱姆(s)。
(R)逃跑或(F)战斗:f
你对史莱姆造成1点伤害。
你击杀了史莱姆。
你当前等级为2级。
你获得10金币。
你遭遇了巨龙(D)。
(R)逃跑或(F)战斗:r
逃跑失败。
龙对你造成4点伤害。
(R)逃跑或(F)战斗:r
你成功逃脱。
你遭遇了一只兽人(o)。
(R)逃跑或(F)战斗:f
你对兽人造成2点伤害。
兽人对你造成2点伤害。
(R)逃跑或(F)战斗:f
你对兽人造成2点伤害。
你击杀了兽人。
你现已升至3级。
你拾获25金币。
你遭遇了一条巨龙(D)。
(R)逃跑或(F)战斗:r
逃跑失败。
巨龙造成4点伤害。
你于3级身亡,遗留35金币。
可惜财物无法带走!
提示:创建4个函数:
- main()函数负责游戏初始化(创建玩家角色)和主游戏循环。
- fightMonster()处理玩家与单个怪物战斗,包括询问玩家行动选择并处理逃跑/战斗分支。
- attackMonster()处理玩家攻击怪物,包含升级逻辑。
- attackPlayer()处理怪物攻击玩家。
解决方案
#include "Random.h" // https://www.learncpp.com/cpp-tutorial/global-random-numbers-random-h/
#include <array>
#include <iostream>
#include <string>
#include <string_view>
class Creature
{
protected:
std::string m_name;
char m_symbol {};
int m_health {};
int m_damage {};
int m_gold {};
public:
Creature(std::string_view name, char symbol, int health, int damage, int gold)
: m_name{ name }
, m_symbol{ symbol }
, m_health{ health }
, m_damage{ damage }
, m_gold{ gold }
{ }
char getSymbol() const { return m_symbol; }
const std::string& getName() const { return m_name; }
bool isDead() const { return m_health <= 0; }
int getGold() const { return m_gold; }
void addGold(int gold) { m_gold += gold; }
void reduceHealth(int health) { m_health -= health; }
int getHealth() const { return m_health; }
int getDamage() const { return m_damage; }
};
class Player : public Creature
{
int m_level{ 1 };
public:
Player(const std::string& name)
: Creature{ name, '@', 10, 1, 0 }
{
}
void levelUp()
{
++m_level;
++m_damage;
}
int getLevel() const { return m_level; }
bool hasWon() const { return m_level >= 20; }
};
class Monster : public Creature
{
public:
enum Type
{
dragon,
orc,
slime,
max_types
};
private:
inline static Creature monsterData[] {
Creature { "dragon", 'D', 20, 4, 100 },
Creature { "orc", 'o', 4, 2, 25 },
Creature { "slime", 's', 1, 1, 10 }
};
static_assert(std::size(monsterData) == max_types);
public:
Monster(Type type)
: Creature{ monsterData[type] }
{
}
static Monster getRandomMonster()
{
int num{ Random::get(0, max_types - 1) };
return Monster{ static_cast<Type>(num) };
}
};
// This function handles the player attacking the monster
void attackMonster(Player& player, Monster& monster)
{
// If the player is dead, we can't attack the monster
if (player.isDead())
return;
std::cout << "You hit the " << monster.getName() << " for " << player.getDamage() << " damage.\n";
// Reduce the monster's health by the player's damage
monster.reduceHealth(player.getDamage());
// If the monster is now dead, level the player up
if (monster.isDead())
{
std::cout << "You killed the " << monster.getName() << ".\n";
player.levelUp();
std::cout << "You are now level " << player.getLevel() << ".\n";
std::cout << "You found " << monster.getGold() << " gold.\n";
player.addGold(monster.getGold());
}
}
// This function handles the monster attacking the player
void attackPlayer(const Monster& monster, Player& player)
{
// If the monster is dead, it can't attack the player
if (monster.isDead())
return;
// Reduce the player's health by the monster's damage
player.reduceHealth(monster.getDamage());
std::cout << "The " << monster.getName() << " hit you for " << monster.getDamage() << " damage.\n";
}
// This function handles the entire fight between a player and a randomly generated monster
void fightMonster(Player& player)
{
// First randomly generate a monster
Monster monster{ Monster::getRandomMonster() };
std::cout << "You have encountered a " << monster.getName() << " (" << monster.getSymbol() << ").\n";
// While the monster isn't dead and the player isn't dead, the fight continues
while (!monster.isDead() && !player.isDead())
{
std::cout << "(R)un or (F)ight: ";
char input{};
std::cin >> input;
if (input == 'R' || input == 'r')
{
// 50% chance of fleeing successfully
if (Random::get(1, 2) == 1)
{
std::cout << "You successfully fled.\n";
return; // success ends the encounter
}
else
{
// Failure to flee gives the monster a free attack on the player
std::cout << "You failed to flee.\n";
attackPlayer(monster, player);
continue;
}
}
if (input == 'F' || input == 'f')
{
// Player attacks first, monster attacks second
attackMonster(player, monster);
attackPlayer(monster, player);
}
}
}
int main()
{
std::cout << "Enter your name: ";
std::string playerName;
std::cin >> playerName;
Player player{ playerName };
std::cout << "Welcome, " << player.getName() << '\n';
// If the player isn't dead and hasn't won yet, the game continues
while (!player.isDead() && !player.hasWon())
fightMonster(player);
// At this point, the player is either dead or has won
if (player.isDead())
{
std::cout << "You died at level " << player.getLevel() << " and with " << player.getGold() << " gold.\n";
std::cout << "Too bad you can't take it with you!\n";
}
else
{
std::cout << "You won the game with " << player.getGold() << " gold!\n";
}
return 0;
}
g) 额外任务:
读者Tom的剑不够锋利,无法击败强大的巨龙。请通过制作不同尺寸的药水来帮助他:
| Type | Effect (Small) | Effect (Medium) | Effect (Large) |
|---|---|---|---|
| Health | +2 Health | +2 Health | +5 Health |
| Strength | +1 Damage | +1 Damage | +1 Damage |
| Poison | -1 Health | -1 Health | -1 Health |
请尽情发挥创意,添加更多药水或改变它们的效果!
玩家在每次获胜后有30%的几率获得药水,并可选择饮用或弃置。若玩家选择弃置,药水将消失。玩家在饮用前无法知晓药水类型,饮用时才会揭示药水种类与容量,并触发相应效果。
以下示例中,玩家拾取毒药后饮用致死(此例中中毒伤害大幅提升)
You have encountered a slime (s).
(R)un or (F)ight: f
You hit the slime for 1 damage.
You killed the slime.
You are now level 2.
You found 10 gold.
You found a mythical potion! Do you want to drink it? [y/n]: y
You drank a Medium potion of Poison
You died at level 2 and with 10 gold.
Too bad you can't take it with you!
显示提示
提示:添加一个药水类,包含类型和容量成员变量,以及一个返回名称的成员函数和一个创建随机药水的静态成员函数(类似于getRandomMonster()函数)。
在玩家类中添加一个drinkPotion()成员函数,用于施加药水效果。
显示解答
#include "Random.h" // https://www.learncpp.com/cpp-tutorial/global-random-numbers-random-h/
#include <array>
#include <iostream>
#include <sstream>
#include <string>
#include <string_view>
class Potion
{
public:
// All possible types of potions
enum Type
{
health,
strength,
poison,
// For random potion generation
max_type
};
enum Size
{
small,
medium,
large,
max_size
};
private:
Type m_type{};
Size m_size{};
public:
Potion(Type type, Size size)
: m_type{ type },
m_size{ size }
{
}
Type getType() const { return m_type; }
Size getSize() const { return m_size; }
// The names of potions are compile-time literals, we can
// return a std::string_view.
static std::string_view getPotionTypeName(Type type)
{
static constexpr std::string_view names[] {
"Health",
"Strength",
"Poison"
};
return names[type];
}
static std::string_view getPotionSizeName(Size size)
{
static constexpr std::string_view names[] {
"Small",
"Medium",
"Large"
};
return names[size];
}
std::string getName() const
{
// We use a std::stringstream, but this could also be solved using
// std::string.
// We first used std::stringstream in lesson 7.13.
std::stringstream result{};
result << getPotionSizeName(getSize()) << " potion of " << getPotionTypeName(getType());
// We can extract the string from an std::stringstream by using the str()
// member function.
return result.str();
}
static Potion getRandomPotion()
{
return Potion{
static_cast<Type>(Random::get(0, max_type - 1)),
static_cast<Size>(Random::get(0, max_size - 1))
};
}
};
class Creature
{
protected:
std::string m_name;
char m_symbol {};
int m_health {};
int m_damage {};
int m_gold {};
public:
Creature(std::string_view name, char symbol, int health, int damage, int gold)
: m_name{ name }
, m_symbol{ symbol }
, m_health{ health }
, m_damage{ damage }
, m_gold{ gold }
{ }
char getSymbol() const { return m_symbol; }
const std::string& getName() const { return m_name; }
bool isDead() const { return m_health <= 0; }
int getGold() const { return m_gold; }
void addGold(int gold) { m_gold += gold; }
void reduceHealth(int health) { m_health -= health; }
int getHealth() const { return m_health; }
int getDamage() const { return m_damage; }
};
class Player : public Creature
{
int m_level{ 1 };
public:
Player(const std::string& name)
: Creature{ name, '@', 10, 1, 0 }
{
}
void levelUp()
{
++m_level;
++m_damage;
}
// Applies a potion's effect to the player
void drinkPotion(const Potion& potion)
{
switch (potion.getType())
{
case Potion::health:
// Only a health potion's size affects its power. All other
// potions are independent of size.
m_health += ((potion.getSize() == Potion::large) ? 5 : 2);
break;
case Potion::strength:
++m_damage;
break;
case Potion::poison:
reduceHealth(1);
break;
// Handle max_type to silence the compiler warning. Don't use default:
// because we want the compiler to warn us if we add a new potion but
// forget to implement its effect.
case Potion::max_type:
break;
}
}
int getLevel() const { return m_level; }
bool hasWon() const { return m_level >= 20; }
};
class Monster : public Creature
{
public:
enum Type
{
dragon,
orc,
slime,
max_types
};
private:
inline static Creature monsterData[] {
Creature { "dragon", 'D', 20, 4, 100 },
Creature { "orc", 'o', 4, 2, 25 },
Creature { "slime", 's', 1, 1, 10 }
};
static_assert(std::size(monsterData) == max_types);
public:
Monster(Type type)
: Creature{ monsterData[type] }
{
}
static Monster getRandomMonster()
{
int num{ Random::get(0, max_types - 1) };
return Monster{ static_cast<Type>(num) };
}
};
// We moved this out of attackMonster() to keep the function shorter.
void onMonsterKilled(Player& player, const Monster& monster)
{
std::cout << "You killed the " << monster.getName() << ".\n";
player.levelUp();
std::cout << "You are now level " << player.getLevel() << ".\n";
std::cout << "You found " << monster.getGold() << " gold.\n";
player.addGold(monster.getGold());
// 30% chance of finding a potion
constexpr int potionChance{ 30 };
if (Random::get(1, 100) <= potionChance)
{
// Generate a random potion
auto potion{ Potion::getRandomPotion() };
std::cout << "You found a mythical potion! Do you want to drink it? [y/n]: ";
char choice{};
std::cin >> choice;
if (choice == 'Y' || choice == 'y')
{
// Apply the effect
player.drinkPotion(potion);
// Reveal the potion type and size
std::cout << "You drank a " << potion.getName() << ".\n";
}
}
}
// This function handles the player attacking the monster
void attackMonster(Player& player, Monster& monster)
{
// If the player is dead, we can't attack the monster
if (player.isDead())
return;
std::cout << "You hit the " << monster.getName() << " for " << player.getDamage() << " damage.\n";
// Reduce the monster's health by the player's damage
monster.reduceHealth(player.getDamage());
// If the monster is now dead, level the player up
if (monster.isDead())
{
// Reward the player
onMonsterKilled(player, monster);
}
}
// This function handles the monster attacking the player
void attackPlayer(const Monster& monster, Player& player)
{
// If the monster is dead, it can't attack the player
if (monster.isDead())
return;
// Reduce the player's health by the monster's damage
player.reduceHealth(monster.getDamage());
std::cout << "The " << monster.getName() << " hit you for " << monster.getDamage() << " damage.\n";
}
// This function handles the entire fight between a player and a randomly generated monster
void fightMonster(Player& player)
{
// First randomly generate a monster
Monster monster{ Monster::getRandomMonster() };
std::cout << "You have encountered a " << monster.getName() << " (" << monster.getSymbol() << ").\n";
// While the monster isn't dead and the player isn't dead, the fight continues
while (!monster.isDead() && !player.isDead())
{
std::cout << "(R)un or (F)ight: ";
char input{};
std::cin >> input;
if (input == 'R' || input == 'r')
{
// 50% chance of fleeing successfully
if (Random::get(1, 2) == 1)
{
std::cout << "You successfully fled.\n";
return; // success ends the encounter
}
else
{
// Failure to flee gives the monster a free attack on the player
std::cout << "You failed to flee.\n";
attackPlayer(monster, player);
continue;
}
}
if (input == 'F' || input == 'f')
{
// Player attacks first, monster attacks second
attackMonster(player, monster);
attackPlayer(monster, player);
}
}
}
int main()
{
std::cout << "Enter your name: ";
std::string playerName;
std::cin >> playerName;
Player player{ playerName };
std::cout << "Welcome, " << player.getName() << '\n';
// If the player isn't dead and hasn't won yet, the game continues
while (!player.isDead() && !player.hasWon())
fightMonster(player);
// At this point, the player is either dead or has won
if (player.isDead())
{
std::cout << "You died at level " << player.getLevel() << " and with " << player.getGold() << " gold.\n";
std::cout << "Too bad you can't take it with you!\n";
}
else
{
std::cout << "You won the game with " << player.getGold() << " gold!\n";
}
return 0;
}

浙公网安备 33010602011771号