24-x 第二十四章 概要与测验(todo)

概要

继承使我们能够在两个对象之间建立“是”的关系。被继承的对象称为父类、基类或超类。进行继承的对象称为子类、派生类或子类。

  1. 当派生类从基类继承时,派生类将获得基类的全部成员。
  2. 派生类构造时,先构造基类部分,再构造派生部分。具体流程如下:
  3. 为派生类预留内存(需同时容纳基类与派生类部分)。
  4. 调用对应的派生类构造函数。
  5. 使用基类构造函数优先构造基类对象。若未指定基类构造函数,则使用默认构造函数。
  6. 派生类的初始化列表对派生类成员进行初始化。
  7. 派生类构造函数主体执行。
  8. 控制权返回调用方。

析构过程按相反顺序进行,从最派生类到最基类。

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.

image

显示答案

#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.

image

显示答案

#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;
}
posted @ 2026-02-01 09:15  游翔  阅读(0)  评论(0)    收藏  举报