艾伦的外星人游戏

概念学习C++17

简介

类

现在开始学习 C++ 的核心编程范式之一:面向对象编程,简称 OOP。它以类为中心;类是用户定义的数据类型,包含一组相关函数。本节从基础开始,后续课程会介绍进阶主题。

成员

类可以拥有成员变量和成员函数,通过成员选择运算符 . 访问。和类外变量一样,建议在声明成员变量时赋予初始值。这个值将作为该类新对象的默认值。

封装与信息隐藏

类可以限制对成员的访问。两个基本访问说明符是 private 和 public。私有成员不能从类外访问,公开成员可以自由访问。class 的成员默认都是 private,只有明确标记为 public 的成员才能从类外直接使用。

基本示例

下面展示了类的定义方式,注意定义结束后的分号 ;:

class Wizard {
  public:               // from here on all members are publicly accessible
    int cast_spell() {  // defines the public member function cast_spell
      return damage;
    }
    std::string name{}; // defines the public member variable `name`
  private:              // from here on all members are private
    int damage{5};      // defines the private member variable `damage`
};

在类内可以访问所有成员变量,请观察 cast_spell 中对 damage 的使用。类外不能直接读取或修改私有成员:

Wizard silverhand{};
// calling the `cast_spell` function is okay, it is public:
silverhand.cast_spell();
// => 5

// name is public and can be changed:
silverhand.name = "Laeral";

// damage is private:
silverhand.damage = 500;
 // => Compilation error

构造函数

构造函数用于在对象创建时给成员变量赋值。它与类同名,没有返回类型。一个类可以有多个构造函数,便于选择性设置变量。例如,有时保留其他默认值、只修改 name;对于更强的巫师,也可能同时修改伤害值,此时可以提供两个构造函数。

class Wizard {
  public:
    Wizard(std::string new_name) {
      name = new_name;
    }
    Wizard(std::string new_name, int new_damage) {
      name = new_name;
      damage = new_damage;
    }
    int cast_spell() {
      return damage;
    }
    std::string name{};
  private:
    int damage{5};
};

Wizard el{"Eleven"};       // deals  5 damage
Wizard vecna{"Vecna", 50}; // deals 50 damage

构造函数有许多细节。如果没有为类显式定义任何构造函数,编译器通常会提供默认构造函数;上面的第一个示例就是这种情况。silverhand 对象通过不传参数调用默认构造函数来创建,成员采用类定义中指定的初始值。如果没有提供初始值,某些变量可能未初始化,从而产生意外后果。

补充:结构体

结构体来自 C++ 的 C 语言基础,与 C++ 一样历史悠久。它与类基本相同,但有一个重要区别:class 的成员默认私有,struct 的成员默认公开。习惯上,struct 常用于仅保存数据的结构,class 则常用于需要维持某些性质的对象。例如,Wizard 的 damage 不能为负数就是一种不变条件。把 damage 设为私有,并让所有修改伤害的函数都保证这一条件,就能维护该性质。

任务说明

艾伦正在制作一个玩家与外星人战斗的游戏。她刚学会面向对象编程,迫不及待地想利用类来改进程序。

你提出帮助她,她很高兴,并请你编写玩家将要面对的外星人。

1. 创建 Alien 类

定义 Alien 类,构造函数接收两个 int 参数 x、y,分别保存到成员变量 x_coordinate 和 y_coordinate。每个外星人的初始生命值都是 3,因此也要初始化成员变量 health。

health 应是私有成员。为让程序其他部分读取生命值,提供返回 int 的公开方法 get_health()。

Alien alien{2, 0};
alien.x_coordinate;
// => 2
alien.y_coordinate;
// => 0
alien.get_health();
// => 3

现在,每个外星人都应能保存自己的位置和生命值。

2. hit 方法

为 Alien 添加 hit 方法,每次调用使生命值减 1。这样艾伦可以直接调用 some_alien_instance.hit(),而不必手动修改生命值。生命值不能小于 0。目前该方法应返回 true;艾伦计划以后增加护盾,届时护盾生效时将返回 false。

Alien alien {0, 0};
alien.get_health();
// => 3 (Initial health value)

alien.hit(); // Decrements health by 1 point.
alien.get_health();
// => 2

3. is_alive 方法

随着生命值不断减少,它最终可能变为 0。添加 is_alive 方法,方便艾伦快速判断外星人是否存活。some_alien_instance.is_alive() 应返回布尔值。

alien.get_health();
// => 1
alien.is_alive();
// => true
alien.hit();
alien.get_health();
// => 0
alien.is_alive();
// => false

4. teleport 方法

游戏中的外星人可以瞬间移动!编写 teleport 方法,接收 x_new、y_new 并相应更新坐标。目前返回 true。艾伦计划在后续关卡加入阻止传送的炸弹,届时传送失败将返回 false。

alien.teleport(5, -4);
alien.x_coordinate;
// => 5
alien.y_coordinate;
// => -4

5. collision_detection 方法

既然外星人可能被击中,就需要检测碰撞。艾伦需要判断两个外星人是否占据相同坐标。collision_detection() 接收另一个外星人对象,返回 bool。

Alien lrrr {3, 6};
Alien ndnd {-2, 12};
lrrr.collision_detection(ndnd);
// => false
ndnd.teleport(3, 6);
ndnd.collision_detection(lrrr);
// => true
需要一点帮助?查看分步提示

提示

1. 创建 Alien 类

  • 构造函数没有返回类型,与类同名。
  • 类名通常使用 PascalCase,即每个单词首字母大写。

2. hit 方法

  • 记住不变条件:生命值不能为负数。

3. is_alive 方法

  • 生命值为 0 是唯一的死亡条件。

4. teleport 方法

  • 类的任意成员函数都可以更新成员变量。

5. collision_detection 方法

  • 类外可以访问所有公开成员变量。