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

   | 

   | 

 Обновлено 10 Апр 2018  | 

 1998

 ǀ   9 

Вот мы и рассмотрели сердце этого туториала – объектно-ориентированное программирование на языке 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)

Ответ а)

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

Ответ b)

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

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

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

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

Ответ c)

Задание №2

Реализуйте деструктор для следующего класса:

Ответ 2

Задание №3

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

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

Ответ а)

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

Ответ b)

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

Ответ c)

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

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

Ответ d)

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

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

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

Jack is the orc that has 90 health points.

Ответ e)

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

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

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

Jack is the orc that has 90 health points.

Ответ f)

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

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

Ответ g)

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

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

Ответ h)

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

Ответ i)

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

Задание №4

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

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

  логика работы с картами;

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

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

  логика игры.

Наша стратегия заключается в том, чтобы работать над каждой из этих частей индивидуально. Таким образом, вместо конвертации целой программы за один присест, мы сделаем это спокойно за 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), где элементами являются объекты класса, класс должен иметь конструктор по умолчанию, чтобы элементы могли быть инициализированы разумными значениями по умолчанию. Если вы этого не сделаете, то получите ошибку о попытке ссылки на удаленную функцию.

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

Ответ а)

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(). В следующей строке показывается, как это сделать:

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

Ответ b)

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

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

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

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

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

Ответ c)

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

  добавьте в код функции getPlayerChoice() и playBlackjack();

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

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

Ответ d)

Ура!

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

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (12 оценок, среднее: 5,00 из 5)
Загрузка...
Подписаться на обновления:

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

  1. Shom:

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

  2. Сергей:

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

    1. Юрий Юрий:

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

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

  3. Андрей:

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

    1. Li4ik Li4ik:

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

  4. Герман:

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

    1. Li4ik Li4ik:

      Пожалуйста 🙂

  5. Герман:

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

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

    1. Li4ik Li4ik:

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

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

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

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

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

ВОЛШЕБНАЯ ТАБЛЕТКА ПО С++