Россия и Беларусь начали и продолжают войну против народа Украины!

Глава №11. Итоговый тест

  Юрий  | 

  |

  Обновл. 17 Июл 2021  | 

 34774

 ǀ   27 

В этой главе мы рассмотрели наследование в языке C++. Пора закрепить пройденный материал.

Теория

Наследование позволяет моделировать отношения типа «является» между двумя объектами. Объект, который наследует, называется дочерним классом. Объект, которого наследуют, называется родительским классом.

При наследовании дочерний класс наследует все члены родительского класса.

При инициализации объектов дочернего класса, сначала выполняется построение родительской части объекта, а затем уже дочерней части объекта. Рассмотрим детально:

   Сначала выделяется память для объекта дочернего класса (достаточная порция для двух частей, из которых состоит объект: родительская и дочерняя).

   Вызывается соответствующий конструктор дочернего класса.

   Выполняется построение родительской части с использованием соответствующего конструктора родительского класса. Если конструктор не указан, то используется конструктор по умолчанию родительского класса.

   Список инициализации дочернего класса инициализирует члены дочернего класса.

   Выполняется тело конструктора дочернего класса.

   Управление возвращается обратно в caller.

Освобождение памяти (уничтожение) происходит в порядке, противоположном построению: от дочерних до родительских классов.

Язык C++ имеет 3 спецификатора доступа: public, private и protected. Спецификатор protected используется для разрешения доступа к членам дружественным классам/функциям и дочерним классам, всем остальным объектам — доступ закрыт.

Есть 3 типа наследования: public, private и protected. Наиболее распространенный тип наследования — public.

Таблица спецификаторов доступа и типов наследования:

Спецификатор доступа в родительском классе Спецификатор доступа при наследовании типа public в дочернем классе Спецификатор доступа при наследовании типа private в дочернем классе Спецификатор доступа при наследовании типа protected в дочернем классе
public public private protected
private Недоступен Недоступен Недоступен
protected protected private protected

Дочерние классы могут изменять методы родительского класса, добавлять свой функционал, изменять спецификатор доступа наследуемых членов или даже скрывать методы родительского класса. Всё это выполняется в теле дочернего класса.

Множественное наследование позволяет дочернему классу иметь сразу несколько родительских классов. Не рекомендуется использовать множественное наследование, если есть альтернативные решения.

Тест


Задание №1

Для каждой из следующих программ определите результат выполнения. Если программа не скомпилируется, то объясните почему. Запускать код не нужно, вы должны определить результат/ошибки программ без помощи компилятора.

a)

Ответ №1.a)

Сначала инициализируется родительская часть объекта, а затем уже дочерняя. Уничтожение происходит в обратном порядке.

Parent()
Child()
~Child()
~Parent()

b)

Подсказка: Локальные переменные уничтожаются в порядке противоположном определению.

Ответ №1.b)

Сначала выполняется построение ch:

Parent()
Child()

Затем построение p:

Parent()

Затем уничтожение p:

~Parent()

Затем уничтожение ch:

~Child()
~Parent()

c)

Ответ №1.c)

Не скомпилируется. Метод Child::print() не имеет доступа к закрытому члену m_x.

d)

Ответ №1.d)

Результат:

Parent()
Child()
Child: 7
~Child()
~Parent()

e)

Ответ №1.e)

Результат:

Parent()
Child()
D2()
Child: 7
~D2()
~Child()
~Parent()

Задание №2

a) Создайте классы Apple и Banana, которые наследуют класс Fruit. У класса Fruit есть две переменные-члены: name и color.

Следующий код:

Должен выдавать следующий результат:

My apple is red.
My banana is yellow.

Ответ №2.a)

b) Добавьте новый класс GrannySmith, который наследует класс Apple.

Следующий код:

Должен выдавать следующий результат:

My apple is red.
My banana is yellow.
My Granny Smith apple is green.

Ответ №2.b)

Задание №3

Самое любимое! Будем создавать простую игру, в которой вы будете сражаться с монстрами. Цель игры — собрать максимум золота, прежде чем вы умрете или достигнете 20 уровня.

Игра состоит из трех классов: Creature, Player и Monster. Player и Monster наследуют класс Creature.

a) Сначала создайте класс Creature со следующими членами:

   имя (std::string);

   символ (char);

   количество здоровья (int);

   количество урона, которое он наносит врагу во время атаки (int);

   количество золота, которое он имеет (int).

Создайте полный набор геттеров (по одному на каждую переменную-член класса). Добавьте еще три метода:

   void reduceHealth(int) — уменьшает здоровье Creature на указанное целочисленное значение;

   bool isDead() — возвращает true, если здоровье Creature равно 0 или меньше;

   void addGold(int) — добавляет золото Creature-у.

Следующий код:

Должен выдавать следующий результат:

The orc has 3 health and is carrying 15 gold.

Ответ №3.a)

b) Теперь нам нужно создать класс Player, который наследует Creature. Player имеет:

   переменную-член level, которая начинается с 1;

   имя (пользователь вводит с клавиатуры);

   символ @;

   10 очков здоровья;

   1 очко урона (для начала);

   0 золота.

Напишите метод levelUp(), который увеличивает уровень Player-а и его урон на 1. Также напишите геттер для члена level и метод hasWon(), который возвращает true, если Player достиг 20 уровня.

Допишите в функцию main() код, который спрашивает у пользователя его имя и выводит количество его здоровья и золота:

Enter your name: Anton
Welcome, Anton.
You have 10 health and are carrying 0 gold.

Ответ №3.b)

c) Следующий класс Monster также наследует Creature и у него нет собственных переменных-членов. Но есть перечисление Type, которое содержит 3 перечислителя, они обозначают типы монстров: DRAGON, ORC и SLIME (вам также нужен дополнительный перечислитель MAX_TYPES).

Ответ №3.c)

d) Каждый тип Монстра имеет свое имя, символ, определенное количество здоровья, урона и золота:

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 в качестве параметра, а затем создавать монстра с соответствующими таблице характеристиками.

Это можно реализовать по-разному. Однако, поскольку все наши свойства типов монстров предопределены (не случайны), то мы будем использовать таблицу поиска. Таблица поиска — это массив, который содержит все предопределенные атрибуты (свойства) чего-либо. Мы можем использовать таблицу поиска для просмотра характеристики определенного типа монстра по мере необходимости.

Как это сделать? Нам нужны всего лишь две вещи. Во-первых, массив с отдельным элементом для каждого типа монстра. Во-вторых, этот элемент будет содержать структуру, в которой будут находиться все предопределенные значения атрибутов для конкретного типа монстра.

   Шаг №1: Создайте структуру MonsterData внутри класса Monster. Эта структура должна иметь следующие перечислители: name, symbol, health, damage и gold.

   Шаг №2: Объявите статический массив этой структуры с именем monsterData.

   Шаг №3: Добавьте код определения нашей таблицы поиска вне тела класса:

Теперь мы можем искать любые значения, которые нам нужны! Например, чтобы узнать количество золота Dragon, мы можем использовать monsterData[DRAGON].gold.

Используйте эту таблицу поиска для реализации вашего конструктора:

Следующий код:

Должен выдавать следующий результат:

A orc (o) was created.

Ответ №3.d)

e) Наконец, добавьте статический метод getRandomMonster() в класс Monster. Этот метод должен генерировать случайное число от 0 до MAX_TYPES-1 и возвращать (возврат по значению) определенный тип монстра (вам нужно использовать оператор static_cast для конвертации int в Type, чтобы передать его конструктору класса Monster).

Вы можете использовать следующий код для генерации случайного числа:

Следующий код:

Должен сгенерировать 10 рандомных монстров:

A slime (s) was created.
A orc (o) was created.
A slime (s) was created.
A slime (s) was created.
A orc (o) was created.
A orc (o) was created.
A dragon (D) was created.
A slime (s) was created.
A orc (o) was created.
A orc (o) was created.

Ответ №3.e)

f) Готово, теперь нам нужно разобраться с логикой выполнения нашей игры!

Суть:

   Игрок сталкивается с одним случайно выбранным монстром.

   С каждым монстром игрок может либо (R)un, либо (F)ight.

   Если игрок решает Run, то шансы на удачный побег составляют 50%.

   Если игроку удается сбежать, то ​​он благополучно переходит к следующему монстру (его здоровье/урон/золото при этом не уменьшается).

   Если игроку не удается сбежать, то монстр его атакует. Здоровье игрока уменьшается от урона монстра. Затем игрок выбирает свое следующее действие.

   Если игрок выбирает Fight, то он атакует монстра. Здоровье монстра уменьшается от урона игрока.

   Если монстр умирает, то игрок забирает всё золото монстра + увеличивает свой level и урон на 1.

   Если монстр не умирает, то он атакует игрока. Здоровье игрока уменьшается от урона монстра.

   Игра заканчивается, если игрок умер (проигрыш) или достиг 20 уровня (выигрыш).

   Если игрок умирает, то программа должна сообщить игроку, какой уровень у него был и сколько золота он имел.

   Если игрок побеждает, то игра должна сообщить игроку, что он выиграл и сколько у него есть золота.

Пример игры:

Enter your name: Anton
Welcome, Anton
You have encountered a orc (o).
(R)un or (F)ight: r
You successfully fled.
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 have encountered a dragon (D).
(R)un or (F)ight: f
You hit the dragon for 2 damage.
The dragon hit you for 4 damage.
(R)un or (F)ight: f
You hit the dragon for 2 damage.
The dragon hit you for 4 damage.
(R)un or (F)ight: f
You hit the dragon for 2 damage.
The dragon hit you for 4 damage.
You died at level 2 and with 10 gold.
Too bad you can't take it with you!

Подсказка: У вас должны быть следующие 4 функции:

   Функция создания Игрока и основной игровой цикл (в функции main()).

   Функция fightMonster(), которая обрабатывает бой между Игроком и Монстром, и спрашивает у игрока, что он хочет сделать: Run или Fight.

   Функция attackMonster(), которая обрабатывает атаку монстра игроком, включая увеличение уровня игрока.

   Функция attackPlayer(), которая обрабатывает атаку игрока монстром.

Ответ №3.f)

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (118 оценок, среднее: 4,97 из 5)
Загрузка...

Комментариев: 27

  1. Антон:

    Не понял только, зачем нам условие победы при достижении 20 лвл, если монстров всего 10, а значит макс лвл 11

  2. Сергей:

    Задание 3. Отличное задание на закрепление! Спасибо Юрий!
    Я не сразу понял логику игры, поэтому пришлось немного повозиться…

    Предположил что количество монстров фиксированное, поэтому пришлось организовать сначала масив монстров, а потом и контейнерный класс для хранения всех монстров, конкретно их здоровья. 🙂

    1. Сергей:

      Вот что в итоге вышло. Коенчно игроку пришлось добавить здоровья. И есть много других идей на развитие логики, ну да это может быть позже…

  3. Сергей:

    Задание 3. Не сразу понял логику игры. Поэтому получилось немного по другому, но зато пришлось повторить/закрепить прошедший материал.

    Решил что в каждой битве игрок имеет только одну битву с монстром, который потом убегает и в следующей битве игрок сталкивается снова со случайным монстром.
    В связи с этим, пришлось создать сначала просто массив, а потом контейнерный класс для хранения монстров и их здоровья.
    Заодно пришлось поработать с указателями и ссылками, что вышло довольно головняково и возможно несколько запутанно.

    Итак:

  4. Vlad:

    Разве в задании 2.b не лучше было переопределить, а точнее перегрузить метод (поскольку переопределение методов дочерних классов это уже тема виртуальных функций) getName() для GrannySmith, чтобы тот возвращал "Granny Smith " + Apple::getName() ???
    Ведь если класс Apple уже был написан и "протестирован", то безопаснее было бы не менять в нём ничего. Да, разумеется в этом задании масштабы не такие огромные, но всё же нужно приучивать себя писать безопасно (на работе точно не помешает).

    1. Юрий:

      сразу так и написал)

  5. Владимир:

    Добавил в код и прошёл игры со второго раза, так игра выглядит более логичной.

  6. cybersatori:

    с кодом вс понятно, но как пройти эту ляцкую игру!)

    1. cybersatori:

      попытки с 50й прошёл игру, давно так не нервничал, дарксолс нервно курит)

  7. Арбузик❤❤❤:

    Давайте хвастаться кто до какого лвла дошел 😉

    Я дошел до 11 с 4 попытки 😀

    1. Vlad:

      Прошёл с 7 попытки, 295 голды.

    2. Andrey:

      Прошёл с 6 попытки с 310 золота, видимо более 50 часов геймплея MGQ даром не прошли 🙂 Ох, пока делал эту игру, такая ностальгия напала, как будто делаю и прохожу свою версию "Monster Girl Quest" со слаймами, орками и драконами…

  8. Виктор:

    Этот вариант немного отличается от задания.

  9. Александр:

    Здравствуйте ! Вопрос к автору. В какой версии MS Visual Studio компилируются примеры ? Поясню :

    при компиляции в 2008 и 2012 версиях дают ошибку :
    error C2470: Monster::monsterData: выглядит как определение функции, но без списка параметров; пропускается вероятное тело функции. И компилируются только так :

    Без '=' получается ошибка. Это опечатка или новый стантарт инициализации ? Подобные вещи встречаличь и в предыдущих уроках. Желательно упоминать, для каких версий С++ написаны примеры.

    1. Vlad:

      Скорее всего у вас используется стандарт до С++11. Это значит что поддержки uniform-инициализации нету. Например: