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

  Юрий  | 

  Обновл. 18 Июн 2019  | 

 9444

 ǀ   21 

Вот мы и рассмотрели сердце этого туториала — объектно-ориентированное программирование на языке C++. Теперь пора закрепить полученные знания.

Теория

Классы позволяют создавать собственные типы данных, которые объединяют данные и функции, которые работают с этими данными. Данные и функции внутри класса называются членами. Доступ к членам класса осуществляется через оператор выбора членов . (или через ->, если вы получаете доступ к элементу через указатель).

Спецификаторы доступа позволяют указать, кто будет иметь доступ к членам класса. Доступ к открытым (public) членам класса имеют все. Доступ к закрытым (private) членам класса имеют только другие члены класса. Насчёт protected мы поговорим детальнее, когда будем рассматривать наследование. По умолчанию все члены класса являются private, а все члены структуры — public.

Инкапсуляция — это когда все переменные-члены вашего класса являются закрытыми, и доступ к ним напрямую невозможен. Это защищает ваш класс от неправильного/некорректного использования.

Конструктор — это специальный тип метода класса, который позволяет инициализировать объекты класса. Конструктор, который не принимает никаких параметров (или имеет все параметры по умолчанию), называется конструктором по умолчанию. Конструктор по умолчанию выполняется, если пользователем не предоставлены значения для инициализации. Вы всегда должны иметь по крайней мере один конструктор для выполнения в каждом из своих классов.

Список инициализации членов класса позволяет инициализировать переменные-члены из конструктора (вместо присваивания значений переменным-членам).

В C++11 инициализация нестатических членов класса позволяет напрямую указывать значения по умолчанию для переменных-членов при их объявлении.

До C++11 конструкторы не должны вызывать другие конструкторы (хоть это и скомпилируется, но будет работать не так, как вы ожидаете). В C++11 конструкторам разрешено вызывать другие конструкторы. Этот процесс называется делегированием конструкторов (или ещё «цепочкой конструкторов»).

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

Все методы имеют скрытый указатель *this, который указывает на текущий объект класса (который используется в данный момент). В большинстве случаев вам не нужно напрямую обращаться к этому указателю (но если нужно, то можно).

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

Методы класса могут (и должны) быть const, если они не изменяют данные класса. Константные объекты класса могут вызывать только константные методы класса.

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

Аналогично, статические методы класса — это методы, которые не имеют указателя *this. Они имеют доступ только к статическим переменным-членам класса.

Дружественные функции — это внешние функции, которые имеют доступ к закрытым членам класса. Дружественные классы — это классы, в которых все методы являются дружественными функциями.

Анонимные объекты создаются для обработки выражений или для передачи/возврата значений.

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

Тайминг кода осуществляется через библиотеку chrono и позволяет засечь время выполнения определённого фрагмента кода.

Тест


Задание №1

a) Напишите класс с именем Point. В Point-е должны быть две переменные-члены типа double: m_a и m_b, со значениями по умолчанию 0.0. Напишите конструктор для этого класса и функцию вывода print().

Следующая программа:

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

Point(0, 0)
Point(2, 5)

Ответ 1.а)

b) Теперь добавим метод distanceTo(), который будет принимать второй объект класса Point в качестве параметра и будет вычислять расстояние между двумя объектами. Учитывая две точки (a1, b1) и (a2, b2), расстояние между ними можно вычислить следующим образом: sqrt((a1 - a2) * (a1 - a2) + (b1 - b2) * (b1 - b2)). Функция sqrt находится в заголовочном файле cmath.

Следующая программа:

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

Point(0, 0)
Point(2, 5)
Distance between two points: 5.38516

Ответ 1.b)

c) Измените функцию distanceTo() из метода класса в дружественную функцию, которая будет принимать два объекта класса Point в качестве параметров. Переименуйте эту функцию на distanceFrom().

Следующая программа:

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

Point(0, 0)
Point(2, 5)
Distance between two points: 5.38516

Ответ 1.c)

Задание №2

Напишите деструктор для следующего класса:

Ответ №2

Задание №3

Давайте создадим генератор случайных монстров.

a) Сначала создайте перечисление MonsterType со следующими типами монстров: Dragon, Goblin, Ogre, Orc, Skeleton, Troll, Vampire и Zombie + добавьте MAX_MONSTER_TYPES, чтобы иметь возможность подсчитать общее количество всех перечислителей.

Ответ 3.а)

b) Теперь создайте класс Monster со следующими тремя атрибутами (переменными-членами): тип (MonsterType), имя (std::string) и количество здоровья (int).

Ответ 3.b)

c) Перечисление MonsterType специфичное для Monster, поэтому переместите его во внутрь класса под спецификатор доступа public.

Ответ 3.c)

d) Создайте конструктор, который позволит инициализировать все переменные-члены класса.

Следующий фрагмент кода должен скомпилироваться без ошибок:

Ответ 3.d)

e) Теперь нам нужно вывести информацию про нашего монстра. Для этого нужно конвертировать MonsterType в std::string. Добавьте функцию getTypeString(), которая будет выполнять конвертацию, и функцию вывода print().

Следующая программа:

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

Jack is the orc that has 90 health points.

Ответ 3.e)

f) Теперь мы уже можем создать сам генератор монстров. Для этого создайте статический класс MonsterGenerator и статический метод с именем generateMonster(), который будет возвращать случайного монстра. Пока что возвратом метода пускай будет анонимный объект: (Monster::Orc, "Jack", 90).

Следующая программа:

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

Jack is the orc that has 90 health points.

Ответ 3.f)

g) Теперь MonsterGenerator должен генерировать некоторые случайные атрибуты. Для этого нам понадобится генератор случайного числа. Воспользуйтесь следующей функцией:

Поскольку MonsterGenerator будет полагаться непосредственно на эту функцию, то поместите её во внутрь класса в качестве статического метода.

Ответ 3.g)

h) Теперь измените функцию generateMonster() для генерации случайного MonsterType (между 0 и Monster::MAX_MONSTER_TYPES-1) и случайного количества здоровья (от 1 до 100). Это должно быть просто. После того, как вы это сделаете, определите один статический фиксированный массив s_names размером 6 внутри функции generateMonster() и инициализируйте его 6 любыми именами на ваш выбор. Добавьте возможность выбора случайного имени из этого массива.

Следующий фрагмент должен скомпилироваться без ошибок:

Ответ 3.h)

i) Почему мы объявили массив s_names статическим?

Ответ 3.i)

Мы объявили s_names статическим, так как инициализировать его нужно только один раз. В противном случае, он бы повторно инициализировался бы каждый раз при вызове generateMonster().

Задание №4

Настало время для нашего и вашего любимого задания Blackjack. На этот раз мы перепишем игру Blackjack, которую написали ранее в итоговом тесте главы №6, но уже с использованием классов! Вот полный код без классов:

Нехило, правда? С чего же начинать? Для начала нам нужна стратегия. Программа Blackjack состоит из 4-ёх частей:

   Логика работы с картами.

   Логика работы с колодами карт.

   Логика раздачи карт из колоды.

   Логика игры.

Наша стратегия заключается в том, чтобы работать над каждой из этих частей индивидуально. Таким образом, вместо конвертации целой программы за один присест, мы сделаем это спокойно за 4 шага.

Скопируйте код выше в вашу IDE, а затем закомментируйте всё, кроме строчек #include.

a) Начнём с того, что переделаем Card из структуры в класс. Хорошей новостью является то, что класс Card очень похож на класс Monster из предыдущего задания. Алгоритм действий следующий:

   Во-первых, переместите перечисления CardSuit и CardRank во внутрь класса Card под спецификатор доступа public (они неотъемлемо связаны с Card, поэтому должны находиться внутри класса).

   Во-вторых, создайте private переменные-члены m_rank и m_suit для хранения значений CardRank и CardSuit.

   В-третьих, создайте public конструктор класса Card с инициализацией карт (переменных-членов класса). Укажите параметры по умолчанию для конструктора (используйте MAX_RANKS и MAX_SUITS).

   Наконец, переместите функции printCard() и getCardValue() во внутрь класса под спецификатор доступа public (не забудьте сделать их const!).

Важное примечание: При использовании std::array (или std::vector), где элементами являются объекты класса, класс должен иметь конструктор по умолчанию, чтобы элементы могли быть инициализированы разумными значениями по умолчанию. Если вы этого не сделаете, то получите ошибку о попытке ссылки на удалённую функцию.

Следующий фрагмент должен скомпилироваться без ошибок:

Ответ 4.а)

b) Хорошо, теперь давайте работать над классом Deck:

   Во-первых, в Deck должно быть 52 карты, поэтому создайте private член m_deck, который будет фиксированным массивом с 52-мя элементами (используйте std::array).

   Во-вторых, создайте конструктор, который не принимает никаких параметров и инициализирует каждый элемент массива m_deck случайной картой (используйте код из функции main() с циклами for из примера выше, где полный код). Внутри циклов создайте анонимный объект Card и присваивайте его каждому элементу массива m_deck.

   В-третьих, переместите printDeck() в класс Deck под спецификатор доступа public (не забудьте о const).

   В-четвёртых, переместите getRandomNumber() и swapCard() в класс Deck в качестве закрытых статических членов.

   В-пятых, переместите shuffleDeck() в класс в качестве открытого члена.

Подсказка: Самой сложной частью здесь является инициализация колоды карт с использованием модифицированного кода из исходной функции main(). В следующей строке показывается, как это сделать:

Следующий фрагмент должен cкомпилироваться без ошибок:

Ответ 4.b)

c) Теперь нам нужен способ отследить то, какая карта будет раздаваться следующей (в исходной программе для этого используется cardptr):

   Во-первых, добавьте в класс Deck целочисленный член m_cardIndex и инициализируйте его нулём.

   Во-вторых, создайте открытый метод dealCard(), который будет возвращать константную ссылку на текущую карту и увеличивать m_cardIndex.

   В-третьих, shuffleDeck() также должен быть обновлён для сброса m_cardIndex (так как после перетасовки колоды, раздаётся карта, которая является верхней).

Следующий фрагмент должен скомпилироваться без ошибок:

Ответ 4.c)

d) Почти готово! Теперь немного самостоятельности:

   Добавьте в программу функции getPlayerChoice() и playBlackjack().

   Измените playBlackjack() в соответствие с уже имеющимся классом и методами.

   Удалите лишнее и добавьте нужное в функцию main() (смотрите полный код выше).

Ответ 4.d)

Ура!

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

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

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

  1. Аватар Александр:

    Есть вопрос по выводу. Почему при m_rank == RANK_10 функция printCard() вместо 10 он выдаёт 12592 (я проверил, что по отдельности 1 и 0 функция printCard() выдаёт именно как 1 и 0)?

  2. Аватар Андрей:

    В третьем задании выдает ошибку: "MonsterGenerator::m_names": статический элемент элемент данных с инициализатором внутри класса должен иметь неизменяемый целочисленный константный тип или должен быть указан как "inline". Почему? Код точно такой же как в уроке

  3. Аватар Анастасия:

    Кто-нибудь может объяснить, почему в 3 задании для генерации монстров создаётся отдельный класс? Почему нельзя обе функции из него сделать в основном классе?

  4. Аватар Иван:

    Насколько правильно или неправильно сделать playBlackjack методом класса Deck? Тоже самое для getPlayerChoice, поместил его в private.

  5. Аватар Анатолий:

    Можно популярно, почему используется именно эта конструкция для генерирования случайного числа?

    И (max-min+1) в VSC подчеркивается и говорит Arithmetic overflow С26451.

    1. Аватар Алексей:

      Это выведенная в предыдущих уроках прога, привязанная к системным часам для 100% рандома чисел. Именно поэтому в main добавили

      и

    2. Аватар Сергей:

      Может кто-то разжевать как именно работает эта первая функция (не понимаю откуда формулы такие)

      1. Аватар Алексей:

        Про:

        Здесь 1.0, по сути, будет делиться на 0.0(переполнение RAND_MAX, при помощи + 1.0, т.к RAND_MAX это int16_t и RAND_MAX всегда равно 32 767), что является небольшой хитростью плюсов, т.к. таким способом мы реализуем double бесконечность, ну а после, возвращаем уже готовую функцию rand() с нужным нам диапазоном(предварительно конвертируя её в int).

        2-ое:
        Если у вас происходит overflow попробуйте написать вместо:

        Вот это:

  6. Аватар Дмитрий:

    Во-вторых, создайте конструктор, который не принимает никаких параметров и инициализирует каждый элемент m_deck случайной картой…

    Подскажите пожалуйста, я так и не понял что значит случайной картой? Ведь в ответе инициализация происходит по-порядку… Или я где-то ошибаюсь?

    1. Аватар kmish:

      Меня тоже это смутило. Походу опечатка.

    2. Аватар Алекс:

      Я это сделал, а потом удивился ответу… Ну, работаю сам в госучреждении, поэтому привык к правилу "Если дали ТЗ — делай по ТЗ. Даже если всё будет криво — можно отмазаться чётко поставленной задачей". Тем более нет возможности переспросить дающего задание о рациональности заполнять массив рандомными картами, учитывая, что среди них 100% попадутся одинаковые.

  7. Аватар Владимир:

    //main.cpp

    //Monster.h

    //Monster.cpp

  8. Аватар Shom:

    "Во-вторых, создайте конструктор, который не принимает никаких параметров и инициализирует каждый элемент m_deck случайной картой…" — долго соображал, как это можно сделать, но так и не осилил. В итоге инициализировал картами по порядку. Потом, в Ответе увидел, что так-же решено : )

  9. Аватар Сергей:

    возникла проблема при создании класса Deck.
    Так как std::array не имеет конструктора по умолчанию, то при попытке создать таковой для класса компилятор ругается. Подскажите, как быть.
    Пы.Сы. Класс вынесен в отдельный хедер.

    1. Юрий Юрий:

      Во-первых, если вы не создаете конструктор по умолчанию для своего класса, то он создается автоматически компилятором. Во-вторых, std::array как раз имеет конструктор по умолчанию в своей реализации и ваша ошибка может быть из-за того, что вы пытаетесь переопределить конструктор, который уже есть.

      Ваша ошибка скорее всего не из-за конструктора по умолчанию.

  10. Аватар Андрей:

    В финальном коде 2 ошибки:
    return (playerTotal > dealerTotal);
    1)gt не объявлен
    2) ";" между playerTotal &gt и dealerTotal

    1. Юрий Юрий:

      &gt — это код символа >, как-то попало, не усмотрел. Спасибо, исправил.

  11. Аватар Герман:

    Действительно все просто, но только после Ваших объяснений! Спасибо!!!!

    1. Юрий Юрий:

      Пожалуйста 🙂

  12. Аватар Герман:

    Уважаемый автор! В 3 задании не совсем понятна 85 строка!

    Нет ли возможности дать пояснения?

    1. Юрий Юрий:

      Есть два отдельных класса: Monster и MonsterGenerator. В строчке

      Вызывается статический метод generateMonster() класса MonsterGenerator, который возвращает случайного монстра в виде возвращаемого анонимного объекта, который затем и присваивается объекту m класса Monster.

      С помощью метода print() класса Monster сгенерированный монстер выводится на экран.

Добавить комментарий

Ваш E-mail не будет опубликован. Обязательные поля помечены *