Розділ №8. Підсумковий тест

  Юрій  | 

  Оновл. 15 Лют 2021  | 

 127

Ось ми і розглянули серце цього туторіалу — об’єктно-орієнтоване програмування в мові 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() і ініціалізуйте його шістьма будь-якими іменами на ваш вибір. Додайте можливість вибору випадкового імені з цього масиву.

Наступний фрагмент коду повинен скомпілюватися без помилок:

Відповідь №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, тому повинні перебувати всередині класу).

   По-друге, створіть закриті змінні-члени m_rank і m_suit для зберігання значень CardRank і CardSuit.

   По-третє, створіть відкритий конструктор класу 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(). У наступному рядку показується, як це зробити:

Наступний фрагмент коду повинен скомпілюватися без помилок:

Відповідь №4.b)

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

   По-перше, додайте в клас Deck цілочисельний член m_cardIndex і ініціалізуйте його значенням 0.

   По-друге, створіть відкритий метод dealCard(), який повертатиме константне посилання на поточну карту і збільшуватиме m_cardIndex.

   По-третє, метод shuffleDeck() також повинен бути оновлений для скидання m_cardIndex (так як після перетасовки колоди, роздається верхня карта).

Наступний фрагмент коду повинен скомпілюватися без помилок:

Відповідь №4.c)

d) Майже готово! Тепер трохи самостійності:

   Додайте в програму функції getPlayerChoice() і playBlackjack().

   Змініть функцію playBlackjack() у відповідність з уже наявними класом і методами.

   Видаліть зайве і додайте потрібне в функцію main() (див. повний код вище).

Відповідь №4.d)

Ура!

Оцінити статтю:

1 Зірка2 Зірки3 Зірки4 Зірки5 Зірок (1 оцінок, середня: 5,00 з 5)
Loading...

Залишити відповідь

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