Ось ми і розглянули серце цього туторіалу — об’єктно-орієнтоване програмування в мові 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().
Наступна програма:
|
1 2 3 4 5 6 7 8 9 10 11 |
#include <iostream> int main() { Point first; Point second(2.0, 5.0); first.print(); second.print(); return 0; } |
Повинна видавати наступний результат:
Point(0, 0)
Point(2, 5)
Відповідь №1.а)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
#include <iostream> class Point { private: double m_a; double m_b; public: Point(double a = 0.0, double b = 0.0) : m_a(a), m_b(b) { } void print() const { std::cout << "Point(" << m_a << ", " << m_b << ")\n"; } }; int main() { Point first; Point second(2.0, 5.0); first.print(); second.print(); return 0; } |
b) Тепер додамо метод distanceTo(), який прийматиме другий об’єкт класу Point в якості параметра і обчислюватиме відстань між двома об’єктами. Враховуючи дві точки (a1, b1) і (a2, b2), відстань між ними можна обчислити наступним чином: sqrt((a1 - a2) * (a1 - a2) + (b1 - b2) * (b1 - b2)). Функція sqrt() знаходиться в заголовку cmath.
Наступна програма:
|
1 2 3 4 5 6 7 8 9 10 |
int main() { Point first; Point second(2.0, 5.0); first.print(); second.print(); std::cout << "Distance between two points: " << first.distanceTo(second) << '\n'; return 0; } |
Повинна видавати наступний результат:
Point(0, 0)
Point(2, 5)
Distance between two points: 5.38516
Відповідь №1.b)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
#include <iostream> #include <cmath> class Point { private: double m_a; double m_b; public: Point(double a = 0.0, double b = 0.0) : m_a(a), m_b(b) { } void print() const { std::cout << "Point(" << m_a << ", " << m_b << ")\n"; } double distanceTo(const Point & other) const { return sqrt((m_a - other.m_a)*(m_a - other.m_a) + (m_b - other.m_b)*(m_b - other.m_b)); } }; int main() { Point first; Point second(2.0, 5.0); first.print(); second.print(); std::cout << "Distance between two points: " << first.distanceTo(second) << '\n'; return 0; } |
c) Змініть функцію distanceTo() з методу класу в дружню функцію, яка прийматиме два об’єкти класу Point в якості параметрів. Перейменуйте цю функцію на distanceFrom().
Наступна програма:
|
1 2 3 4 5 6 7 8 9 10 |
int main() { Point first; Point second(2.0, 5.0); first.print(); second.print(); std::cout << "Distance between two points: " << distanceFrom(first, second) << '\n'; return 0; } |
Повинна видавати наступний результат:
Point(0, 0)
Point(2, 5)
Distance between two points: 5.38516
Відповідь №1.c)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
#include <iostream> #include <cmath> class Point { private: double m_a; double m_b; public: Point(double a = 0.0, double b = 0.0) : m_a(a), m_b(b) { } void print() const { std::cout << "Point(" << m_a << ", " << m_b << ")\n"; } friend double distanceFrom(const Point &a, const Point &b); }; double distanceFrom(const Point &a, const Point &b) { return sqrt((a.m_a - b.m_a)*(a.m_a - b.m_a) + (a.m_b - b.m_b)*(a.m_b - b.m_b)); } int main() { Point first; Point second(2.0, 5.0); first.print(); second.print(); std::cout << "Distance between two points: " << distanceFrom(first, second) << '\n'; return 0; } |
Завдання №2
Напишіть деструктор для наступного класу:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
#include <iostream> class Welcome { private: char *m_data; public: Welcome() { m_data = new char[14]; const char *init = "Hello, World!"; for (int i = 0; i < 14; ++i) m_data[i] = init[i]; } ~Welcome() { // Реалізація деструктора } void print() const { std::cout << m_data; } }; int main() { Welcome hello; hello.print(); return 0; } |
Відповідь №2
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
#include <iostream> class Welcome { private: char *m_data; public: Welcome() { m_data = new char[14]; const char *init = "Hello, World!"; for (int i = 0; i < 14; ++i) m_data[i] = init[i]; } ~Welcome() { delete[] m_data; } void print() const { std::cout << m_data; } }; int main() { Welcome hello; hello.print(); return 0; } |
Завдання №3
Давайте створимо генератор випадкових монстрів.
a) Спочатку створіть перерахування MonsterType з наступними типами монстрів: Dragon, Goblin, Ogre, Orc, Skeleton, Troll, Vampire і Zombie + додайте MAX_MONSTER_TYPES, щоб мати можливість підрахувати загальну кількість всіх енумераторів.
Відповідь №3.а)
|
1 2 3 4 5 6 7 8 9 10 11 12 |
enum MonsterType { Dragon, Goblin, Ogre, Orc, Skeleton, Troll, Vampire, Zombie, MAX_MONSTER_TYPES }; |
b) Тепер створіть клас Monster з наступними трьома атрибутами (змінними-членами): тип (MonsterType), ім’я (std::string) і кількість здоров’я (int).
Відповідь №3.b)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#include <string> enum MonsterType { Dragon, Goblin, Ogre, Orc, Skeleton, Troll, Vampire, Zombie, MAX_MONSTER_TYPES }; class Monster { private: MonsterType m_type; std::string m_name; int m_health; }; |
c) Перерахування MonsterType є специфічним для Monster, тому перемістіть його всередину класу під специфікатор доступу public.
Відповідь №3.c)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#include <string> class Monster { public: enum MonsterType { Dragon, Goblin, Ogre, Orc, Skeleton, Troll, Vampire, Zombie, MAX_MONSTER_TYPES }; private: MonsterType m_type; std::string m_name; int m_health; }; |
d) Створіть конструктор, який дозволить ініціалізувати всі змінні-члени класу.
Наступний фрагмент коду повинен скомпілюватися без помилок:
|
1 2 3 4 5 6 |
int main() { Monster jack(Monster::Orc, "Jack", 90); return 0; } |
Відповідь №3.d)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
#include <string> class Monster { public: enum MonsterType { Dragon, Goblin, Ogre, Orc, Skeleton, Troll, Vampire, Zombie, MAX_MONSTER_TYPES }; private: MonsterType m_type; std::string m_name; int m_health; public: Monster(MonsterType type, std::string name, int health) : m_type(type), m_name(name), m_health(health) { } }; int main() { Monster jack(Monster::Orc, "Jack", 90); return 0; } |
e) Тепер нам потрібно вивести інформацію про нашого монстра. Для цього потрібно конвертувати MonsterType в std::string. Додайте функцію getTypeString(), яка виконуватиме конвертацію, і функцію виводу print().
Наступна програма:
|
1 2 3 4 5 6 7 |
int main() { Monster jack(Monster::Orc, "Jack", 90); jack.print(); return 0; } |
Повинна видавати наступний результат:
Jack is the orc that has 90 health points.
Відповідь №3.e)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
#include <iostream> #include <string> class Monster { public: enum MonsterType { Dragon, Goblin, Ogre, Orc, Skeleton, Troll, Vampire, Zombie, MAX_MONSTER_TYPES }; private: MonsterType m_type; std::string m_name; int m_health; public: Monster(MonsterType type, std::string name, int health) : m_type(type), m_name(name), m_health(health) { } std::string getTypeString() const { switch (m_type) { case Dragon: return "dragon"; case Goblin: return "goblin"; case Ogre: return "ogre"; case Orc: return "orc"; case Skeleton: return "skeleton"; case Troll: return "troll"; case Vampire: return "vampire"; case Zombie: return "zombie"; } return "Error!"; } void print() const { std::cout << m_name << " is the " << getTypeString() << " that has " << m_health << " health points."<< '\n'; } }; int main() { Monster jack(Monster::Orc, "Jack", 90); jack.print(); return 0; } |
f) Тепер ми вже можемо створити сам генератор монстрів. Для цього створіть статичний клас MonsterGenerator і статичний метод з ім’ям generateMonster(), який повертатиме випадкового монстра. Поки що метод нехай повертає анонімний об’єкт: (Monster::Orc, "Jack", 90).
Наступна програма:
|
1 2 3 4 5 6 7 |
int main() { Monster m = MonsterGenerator::generateMonster(); m.print(); return 0; } |
Повинна видавати наступний результат:
Jack is the orc that has 90 health points.
Відповідь №3.f)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
#include <iostream> #include <string> class Monster { public: enum MonsterType { Dragon, Goblin, Ogre, Orc, Skeleton, Troll, Vampire, Zombie, MAX_MONSTER_TYPES }; private: MonsterType m_type; std::string m_name; int m_health; public: Monster(MonsterType type, std::string name, int health) : m_type(type), m_name(name), m_health(health) { } std::string getTypeString() const { switch (m_type) { case Dragon: return "dragon"; case Goblin: return "goblin"; case Ogre: return "ogre"; case Orc: return "orc"; case Skeleton: return "skeleton"; case Troll: return "troll"; case Vampire: return "vampire"; case Zombie: return "zombie"; } return "Error!"; } void print() const { std::cout << m_name << " is the " << getTypeString() << " that has " << m_health << " health points." << '\n'; } }; class MonsterGenerator { public: static Monster generateMonster() { return Monster(Monster::Orc, "Jack", 90); } }; int main() { Monster m = MonsterGenerator::generateMonster(); m.print(); return 0; } |
g) Тепер MonsterGenerator повинен генерувати деякі випадкові атрибути. Для цього нам знадобиться генератор випадкового числа. Скористайтеся наступною функцією:
|
1 2 3 4 5 6 7 8 |
// Генеруємо випадкове число між min і max (включно). // Припускається, що srand() уже викликали int getRandomNumber(int min, int max) { static const double fraction = 1.0 / (static_cast<double>(RAND_MAX) + 1.0); // Рівномірно розподіляємо рандомне число в нашому діапазоні return static_cast<int>(rand() * fraction * (max - min + 1) + min); } |
Оскільки MonsterGenerator покладатиметься безпосередньо на цю функцію, то помістіть її всередину класу в якості статичного методу.
Відповідь №3.g)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class MonsterGenerator { public: // Генеруємо випадкове число між min і max (включно). // Припускається, що srand() вже викликали static int getRandomNumber(int min, int max) { static const double fraction = 1.0 / (static_cast<double>(RAND_MAX) + 1.0); // Рівномірно розподіляємо рандомне число в нашому діапазоні return static_cast<int>(rand() * fraction * (max - min + 1) + min); } static Monster generateMonster() { return Monster(Monster::Orc, "Jack", 90); } }; |
h) Тепер змініть функцію generateMonster() для генерації випадкового MonsterType (між 0 і Monster::MAX_MONSTER_TYPES-1) і випадкової кількості здоров’я (від 1 до 100). Це має бути просто. Після того, як ви це зробите, визначте один статичний фіксований масив s_names розміром 6 елементів всередині функції generateMonster() і ініціалізуйте його шістьма будь-якими іменами на ваш вибір. Додайте можливість вибору випадкового імені з цього масиву.
Наступний фрагмент коду повинен скомпілюватися без помилок:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#include <ctime> // для time() #include <cstdlib> // для rand() і srand() int main() { srand(static_cast<unsigned int>(time(0))); // використовуємо системний годинник в якості стартового значення rand(); // користувачам Visual Studio: скидаємо перше згенероване (рандомне) число Monster m = MonsterGenerator::generateMonster(); m.print(); return 0; } |
Відповідь №3.h)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
#include <iostream> #include <ctime> // для time() #include <cstdlib> // для rand() і srand() #include <string> class Monster { public: enum MonsterType { Dragon, Goblin, Ogre, Orc, Skeleton, Troll, Vampire, Zombie, MAX_MONSTER_TYPES }; private: MonsterType m_type; std::string m_name; int m_health; public: Monster(MonsterType type, std::string name, int health) : m_type(type), m_name(name), m_health(health) { } std::string getTypeString() const { switch (m_type) { case Dragon: return "dragon"; case Goblin: return "goblin"; case Ogre: return "ogre"; case Orc: return "orc"; case Skeleton: return "skeleton"; case Troll: return "troll"; case Vampire: return "vampire"; case Zombie: return "zombie"; } return "Error!"; } void print() const { std::cout << m_name << " is the " << getTypeString() << " that has " << m_health << " health points." << '\n'; } }; class MonsterGenerator { public: // Генеруємо випадкове число між min і max (включно). // Припускається, что srand() вже викликали static int getRandomNumber(int min, int max) { static const double fraction = 1.0 / (static_cast<double>(RAND_MAX) + 1.0); // Рівномірно розподіляємо рандомне число в нашому діапазоні return static_cast<int>(rand() * fraction * (max - min + 1) + min); } static Monster generateMonster() { Monster::MonsterType type = static_cast<Monster::MonsterType>(getRandomNumber(0, Monster::MAX_MONSTER_TYPES - 1)); int health = getRandomNumber(1, 100); static std::string s_names[6]{ "John", "Brad", "Alex", "Thor", "Hulk", "Asnee" }; return Monster(type, s_names[getRandomNumber(0, 5)], health); } }; int main() { srand(static_cast<unsigned int>(time(0))); // використовуємо системний годинник в якості стартового значення rand(); // користувачам Visual Studio: скидаємо перше згенероване (рандомне) число Monster m = MonsterGenerator::generateMonster(); m.print(); return 0; } |
i) Чому ми оголосили масив s_names статичним?
Відповідь №3.i)
Ми оголосили s_names статичним, так як ініціалізувати його потрібно один раз. В протилежному випадку, він повторно ініціалізувався б кожен раз при виклику generateMonster().
Завдання №4
Настав час для нашого і вашого улюбленого завдання “Blackjack”. На цей раз ми перепишемо гру “Blackjack”, яку написали раніше в підсумковому тесті розділу №6, але вже з використанням класів! Ось повний код без класів:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 |
#include <iostream> #include <array> #include <ctime> // для time() #include <cstdlib> // для rand() і srand() enum CardSuit { SUIT_CLUB, SUIT_DIAMOND, SUIT_HEART, SUIT_SPADE, MAX_SUITS }; enum CardRank { RANK_2, RANK_3, RANK_4, RANK_5, RANK_6, RANK_7, RANK_8, RANK_9, RANK_10, RANK_JACK, RANK_QUEEN, RANK_KING, RANK_ACE, MAX_RANKS }; struct Card { CardRank rank; CardSuit suit; }; void printCard(const Card &card) { switch (card.rank) { case RANK_2: std::cout << '2'; break; case RANK_3: std::cout << '3'; break; case RANK_4: std::cout << '4'; break; case RANK_5: std::cout << '5'; break; case RANK_6: std::cout << '6'; break; case RANK_7: std::cout << '7'; break; case RANK_8: std::cout << '8'; break; case RANK_9: std::cout << '9'; break; case RANK_10: std::cout << 'T'; break; case RANK_JACK: std::cout << 'J'; break; case RANK_QUEEN: std::cout << 'Q'; break; case RANK_KING: std::cout << 'K'; break; case RANK_ACE: std::cout << 'A'; break; } switch (card.suit) { case SUIT_CLUB: std::cout << 'C'; break; case SUIT_DIAMOND: std::cout << 'D'; break; case SUIT_HEART: std::cout << 'H'; break; case SUIT_SPADE: std::cout << 'S'; break; } } void printDeck(const std::array<Card, 52> deck) { for (const auto &card : deck) { printCard(card); std::cout << ' '; } std::cout << '\n'; } void swapCard(Card &a, Card &b) { Card temp = a; a = b; b = temp; } // Генеруємо випадкове число між min і max (включно). // Припускається, що srand() вже викликали int getRandomNumber(int min, int max) { static const double fraction = 1.0 / (static_cast<double>(RAND_MAX) + 1.0); // Рівномірно розподіляємо рандомне число в нашому діапазоні return static_cast<int>(rand() * fraction * (max - min + 1) + min); } void shuffleDeck(std::array<Card, 52> &deck) { // Перебираємо кожну карту в колоді for (int index = 0; index < 52; ++index) { // Вибираємо будь-яку випадкову карту int swapIndex = getRandomNumber(0, 51); // Міняємо місцями з нашою поточною картою swapCard(deck[index], deck[swapIndex]); } } int getCardValue(const Card &card) { switch (card.rank) { case RANK_2: return 2; case RANK_3: return 3; case RANK_4: return 4; case RANK_5: return 5; case RANK_6: return 6; case RANK_7: return 7; case RANK_8: return 8; case RANK_9: return 9; case RANK_10: return 10; case RANK_JACK: return 10; case RANK_QUEEN: return 10; case RANK_KING: return 10; case RANK_ACE: return 11; } return 0; } char getPlayerChoice() { std::cout << "(h) to hit, or (s) to stand: "; char choice; do { std::cin >> choice; } while (choice != 'h' && choice != 's'); return choice; } bool playBlackjack(const std::array<Card, 52> deck) { const Card *cardPtr = &deck[0]; int playerTotal = 0; int dealerTotal = 0; // Дилер отримує одну карту dealerTotal += getCardValue(*cardPtr++); std::cout << "The dealer is showing: " << dealerTotal << '\n'; // Гравець отримує дві карти playerTotal += getCardValue(*cardPtr++); playerTotal += getCardValue(*cardPtr++); // Гравець починає while (1) { std::cout << "You have: " << playerTotal << '\n'; char choice = getPlayerChoice(); if (choice == 's') break; playerTotal += getCardValue(*cardPtr++); // Дивимося, чи гравець не програв if (playerTotal > 21) return false; } // Якщо гравець не програв (у нього не більше 21 очка), тоді дилер отримує карти до тих пір, поки у нього в підсумку буде не менше 17 очків while (dealerTotal < 17) { dealerTotal += getCardValue(*cardPtr++); std::cout << "The dealer now has: " << dealerTotal << '\n'; } // Якщо у дилера більше 21 очка, то він програв, а гравець виграв if (dealerTotal > 21) return true; return (playerTotal > dealerTotal); } int main() { srand(static_cast<unsigned int>(time(0))); // використовуємо системний годинник в якості стартового значення rand(); // користувачам Visual Studio: скидаємо перше згенероване (випадкове) число std::array<Card, 52> deck; // Звичайно, можна було б ініціалізувати кожну карту окремо, але навіщо? Адже є цикли! int card = 0; for (int suit = 0; suit < MAX_SUITS; ++suit) for (int rank = 0; rank < MAX_RANKS; ++rank) { deck[card].suit = static_cast<CardSuit>(suit); deck[card].rank = static_cast<CardRank>(rank); ++card; } shuffleDeck(deck); if (playBlackjack(deck)) std::cout << "You win!\n"; else std::cout << "You lose!\n"; return 0; } |
Непогано, правда? З чого ж починати? Для початку нам потрібна стратегія. Програма “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), де елементами є об’єкти класу, клас повинен мати конструктор за замовчуванням, щоб елементи могли бути ініціалізовані розумними значеннями за замовчуванням. Якщо ви цього не зробите, то отримаєте помилку спроби посилатися на видалену функцію.
Наступний фрагмент коду повинен скомпілюватися без помилок:
|
1 2 3 4 5 6 7 8 9 10 |
#include <iostream> int main() { const Card cardQueenHearts(Card::RANK_QUEEN, Card::SUIT_HEART); cardQueenHearts.printCard(); std::cout << " has the value " << cardQueenHearts.getCardValue() << '\n'; return 0; } |
Відповідь №4.а)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
#include <iostream> class Card { public: enum CardSuit { SUIT_CLUB, SUIT_DIAMOND, SUIT_HEART, SUIT_SPADE, MAX_SUITS }; enum CardRank { RANK_2, RANK_3, RANK_4, RANK_5, RANK_6, RANK_7, RANK_8, RANK_9, RANK_10, RANK_JACK, RANK_QUEEN, RANK_KING, RANK_ACE, MAX_RANKS }; private: CardRank m_rank; CardSuit m_suit; public: Card(CardRank rank=MAX_RANKS, CardSuit suit=MAX_SUITS) : m_rank(rank), m_suit(suit) { } void printCard() const { switch (m_rank) { case RANK_2: std::cout << '2'; break; case RANK_3: std::cout << '3'; break; case RANK_4: std::cout << '4'; break; case RANK_5: std::cout << '5'; break; case RANK_6: std::cout << '6'; break; case RANK_7: std::cout << '7'; break; case RANK_8: std::cout << '8'; break; case RANK_9: std::cout << '9'; break; case RANK_10: std::cout << 'T'; break; case RANK_JACK: std::cout << 'J'; break; case RANK_QUEEN: std::cout << 'Q'; break; case RANK_KING: std::cout << 'K'; break; case RANK_ACE: std::cout << 'A'; break; } switch (m_suit) { case SUIT_CLUB: std::cout << 'C'; break; case SUIT_DIAMOND: std::cout << 'D'; break; case SUIT_HEART: std::cout << 'H'; break; case SUIT_SPADE: std::cout << 'S'; break; } } int getCardValue() const { switch (m_rank) { case RANK_2: return 2; case RANK_3: return 3; case RANK_4: return 4; case RANK_5: return 5; case RANK_6: return 6; case RANK_7: return 7; case RANK_8: return 8; case RANK_9: return 9; case RANK_10: return 10; case RANK_JACK: return 10; case RANK_QUEEN: return 10; case RANK_KING: return 10; case RANK_ACE: return 11; } return 0; } }; int main() { const Card cardQueenHearts(Card::RANK_QUEEN, Card::SUIT_HEART); cardQueenHearts.printCard(); std::cout << " has the value " << cardQueenHearts.getCardValue() << '\n'; return 0; } |
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(). У наступному рядку показується, як це зробити:
|
1 |
m_deck[card] = Card(static_cast<Card::CardRank>(rank), static_cast<Card::CardSuit>(suit)); |
Наступний фрагмент коду повинен скомпілюватися без помилок:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include <iostream> #include <ctime> // для time() #include <cstdlib> // для rand() і srand() int main() { srand(static_cast<unsigned int>(time(0))); // використовуємо системний годинник в якості стартового значення rand(); // користувачам Visual Studio: скидаємо перше згенероване (рандомне) число Deck deck; deck.printDeck(); deck.shuffleDeck(); deck.printDeck(); return 0; } |
Відповідь №4.b)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
#include <iostream> #include <array> #include <ctime> // для time() #include <cstdlib> // для rand() і srand() class Card { public: enum CardSuit { SUIT_CLUB, SUIT_DIAMOND, SUIT_HEART, SUIT_SPADE, MAX_SUITS }; enum CardRank { RANK_2, RANK_3, RANK_4, RANK_5, RANK_6, RANK_7, RANK_8, RANK_9, RANK_10, RANK_JACK, RANK_QUEEN, RANK_KING, RANK_ACE, MAX_RANKS }; private: CardRank m_rank; CardSuit m_suit; public: Card(CardRank rank=MAX_RANKS, CardSuit suit=MAX_SUITS) : m_rank(rank), m_suit(suit) { } void printCard() const { switch (m_rank) { case RANK_2: std::cout << '2'; break; case RANK_3: std::cout << '3'; break; case RANK_4: std::cout << '4'; break; case RANK_5: std::cout << '5'; break; case RANK_6: std::cout << '6'; break; case RANK_7: std::cout << '7'; break; case RANK_8: std::cout << '8'; break; case RANK_9: std::cout << '9'; break; case RANK_10: std::cout << 'T'; break; case RANK_JACK: std::cout << 'J'; break; case RANK_QUEEN: std::cout << 'Q'; break; case RANK_KING: std::cout << 'K'; break; case RANK_ACE: std::cout << 'A'; break; } switch (m_suit) { case SUIT_CLUB: std::cout << 'C'; break; case SUIT_DIAMOND: std::cout << 'D'; break; case SUIT_HEART: std::cout << 'H'; break; case SUIT_SPADE: std::cout << 'S'; break; } } int getCardValue() const { switch (m_rank) { case RANK_2: return 2; case RANK_3: return 3; case RANK_4: return 4; case RANK_5: return 5; case RANK_6: return 6; case RANK_7: return 7; case RANK_8: return 8; case RANK_9: return 9; case RANK_10: return 10; case RANK_JACK: return 10; case RANK_QUEEN: return 10; case RANK_KING: return 10; case RANK_ACE: return 11; } return 0; } }; class Deck { private: std::array<Card, 52> m_deck; // Генеруємо випадкове число між min і max (включно). // Припускається, що srand() вже викликали static int getRandomNumber(int min, int max) { static const double fraction = 1.0 / (static_cast<double>(RAND_MAX) + 1.0); // Рівномірно розподіляємо рандомне число в нашому діапазоні return static_cast<int>(rand() * fraction * (max - min + 1) + min); } static void swapCard(Card &a, Card &b) { Card temp = a; a = b; b = temp; } public: Deck() { int card = 0; for (int suit = 0; suit < Card::MAX_SUITS; ++suit) for (int rank = 0; rank < Card::MAX_RANKS; ++rank) { m_deck[card] = Card(static_cast<Card::CardRank>(rank), static_cast<Card::CardSuit>(suit)); ++card; } } void printDeck() const { for (const auto &card : m_deck) { card.printCard(); std::cout << ' '; } std::cout << '\n'; } void shuffleDeck() { // Перебираємо кожну карту в колоді for (int index = 0; index < 52; ++index) { // Вибираємо будь-яку випадкову карту int swapIndex = getRandomNumber(0, 51); // Міняємо місцями з нашою поточною картою swapCard(m_deck[index], m_deck[swapIndex]); } } }; int main() { srand(static_cast<unsigned int>(time(0))); // використовуємо системний годинник в якості стартового значення rand(); // користувачам Visual Studio: скидаємо перше згенероване (рандомне) число Deck deck; deck.printDeck(); deck.shuffleDeck(); deck.printDeck(); return 0; } |
c) Тепер нам потрібен спосіб відстежити, яка карта роздаватиметься наступною (у вихідній програмі для цього використовується cardptr):
По-перше, додайте в клас Deck цілочисельний член m_cardIndex і ініціалізуйте його значенням 0.
По-друге, створіть відкритий метод dealCard(), який повертатиме константне посилання на поточну карту і збільшуватиме m_cardIndex.
По-третє, метод shuffleDeck() також повинен бути оновлений для скидання m_cardIndex (тому що після перетасовки колоди роздається верхня карта).
Наступний фрагмент коду повинен скомпілюватися без помилок:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
int main() { srand(static_cast<unsigned int>(time(0))); // використовуємо системний годинник в якості стартового значення rand(); // користувачам Visual Studio: скидаємо перше згенероване (рандомне) число Deck deck; deck.shuffleDeck(); deck.printDeck(); std::cout << "The first card has value: " << deck.dealCard().getCardValue() << '\n'; std::cout << "The second card has value: " << deck.dealCard().getCardValue() << '\n'; return 0; } |
Відповідь №4.c)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 |
#include <iostream> #include <array> #include <ctime> // для time() #include <cstdlib> // для rand() і srand() #include <cassert> // для assert() class Card { public: enum CardSuit { SUIT_CLUB, SUIT_DIAMOND, SUIT_HEART, SUIT_SPADE, MAX_SUITS }; enum CardRank { RANK_2, RANK_3, RANK_4, RANK_5, RANK_6, RANK_7, RANK_8, RANK_9, RANK_10, RANK_JACK, RANK_QUEEN, RANK_KING, RANK_ACE, MAX_RANKS }; private: CardRank m_rank; CardSuit m_suit; public: Card(CardRank rank=MAX_RANKS, CardSuit suit=MAX_SUITS) : m_rank(rank), m_suit(suit) { } void printCard() const { switch (m_rank) { case RANK_2: std::cout << '2'; break; case RANK_3: std::cout << '3'; break; case RANK_4: std::cout << '4'; break; case RANK_5: std::cout << '5'; break; case RANK_6: std::cout << '6'; break; case RANK_7: std::cout << '7'; break; case RANK_8: std::cout << '8'; break; case RANK_9: std::cout << '9'; break; case RANK_10: std::cout << 'T'; break; case RANK_JACK: std::cout << 'J'; break; case RANK_QUEEN: std::cout << 'Q'; break; case RANK_KING: std::cout << 'K'; break; case RANK_ACE: std::cout << 'A'; break; } switch (m_suit) { case SUIT_CLUB: std::cout << 'C'; break; case SUIT_DIAMOND: std::cout << 'D'; break; case SUIT_HEART: std::cout << 'H'; break; case SUIT_SPADE: std::cout << 'S'; break; } } int getCardValue() const { switch (m_rank) { case RANK_2: return 2; case RANK_3: return 3; case RANK_4: return 4; case RANK_5: return 5; case RANK_6: return 6; case RANK_7: return 7; case RANK_8: return 8; case RANK_9: return 9; case RANK_10: return 10; case RANK_JACK: return 10; case RANK_QUEEN: return 10; case RANK_KING: return 10; case RANK_ACE: return 11; } return 0; } }; class Deck { private: std::array<Card, 52> m_deck; int m_cardIndex = 0; // Генеруємо випадкове число між min і max (включно). // Припускається, що srand() вже викликали static int getRandomNumber(int min, int max) { static const double fraction = 1.0 / (static_cast<double>(RAND_MAX) + 1.0); // Рівномірно розподіляємо рандомне число в нашому діапазоні return static_cast<int>(rand() * fraction * (max - min + 1) + min); } static void swapCard(Card &a, Card &b) { Card temp = a; a = b; b = temp; } public: Deck() { int card = 0; for (int suit = 0; suit < Card::MAX_SUITS; ++suit) for (int rank = 0; rank < Card::MAX_RANKS; ++rank) { m_deck[card] = Card(static_cast<Card::CardRank>(rank), static_cast<Card::CardSuit>(suit)); ++card; } } void printDeck() const { for (const auto &card : m_deck) { card.printCard(); std::cout << ' '; } std::cout << '\n'; } void shuffleDeck() { // Перебираємо кожну карту в колоді for (int index = 0; index < 52; ++index) { // Вибираємо будь-яку випадкову карту int swapIndex = getRandomNumber(0, 51); // Міняємо місцями з нашою поточною картою swapCard(m_deck[index], m_deck[swapIndex]); } m_cardIndex = 0; // починаємо нову роздачу карт } const Card& dealCard() { assert (m_cardIndex < 52); return m_deck[m_cardIndex++]; } }; int main() { srand(static_cast<unsigned int>(time(0))); // використовуємо системний годинник в якості стартового значення rand(); // користувачам Visual Studio: скидаємо перше згенероване (рандомне) число Deck deck; deck.shuffleDeck(); deck.printDeck(); std::cout << "The first card has value: " << deck.dealCard().getCardValue() << '\n'; std::cout << "The second card has value: " << deck.dealCard().getCardValue() << '\n'; return 0; } |
d) Майже готово! Тепер трохи самостійності:
Додайте в програму функції getPlayerChoice() і playBlackjack().
Змініть функцію playBlackjack() у відповідність з уже наявними класом і методами.
Видаліть зайве і додайте потрібне в функцію main() (див. повний код вище).
Відповідь №4.d)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 |
#include <iostream> #include <array> #include <ctime> // для time() #include <cstdlib> // для rand() і srand() #include <cassert> // для assert() class Card { public: enum CardSuit { SUIT_CLUB, SUIT_DIAMOND, SUIT_HEART, SUIT_SPADE, MAX_SUITS }; enum CardRank { RANK_2, RANK_3, RANK_4, RANK_5, RANK_6, RANK_7, RANK_8, RANK_9, RANK_10, RANK_JACK, RANK_QUEEN, RANK_KING, RANK_ACE, MAX_RANKS }; private: CardRank m_rank; CardSuit m_suit; public: Card(CardRank rank=MAX_RANKS, CardSuit suit=MAX_SUITS) : m_rank(rank), m_suit(suit) { } void printCard() const { switch (m_rank) { case RANK_2: std::cout << '2'; break; case RANK_3: std::cout << '3'; break; case RANK_4: std::cout << '4'; break; case RANK_5: std::cout << '5'; break; case RANK_6: std::cout << '6'; break; case RANK_7: std::cout << '7'; break; case RANK_8: std::cout << '8'; break; case RANK_9: std::cout << '9'; break; case RANK_10: std::cout << 'T'; break; case RANK_JACK: std::cout << 'J'; break; case RANK_QUEEN: std::cout << 'Q'; break; case RANK_KING: std::cout << 'K'; break; case RANK_ACE: std::cout << 'A'; break; } switch (m_suit) { case SUIT_CLUB: std::cout << 'C'; break; case SUIT_DIAMOND: std::cout << 'D'; break; case SUIT_HEART: std::cout << 'H'; break; case SUIT_SPADE: std::cout << 'S'; break; } } int getCardValue() const { switch (m_rank) { case RANK_2: return 2; case RANK_3: return 3; case RANK_4: return 4; case RANK_5: return 5; case RANK_6: return 6; case RANK_7: return 7; case RANK_8: return 8; case RANK_9: return 9; case RANK_10: return 10; case RANK_JACK: return 10; case RANK_QUEEN: return 10; case RANK_KING: return 10; case RANK_ACE: return 11; } return 0; } }; class Deck { private: std::array<Card, 52> m_deck; int m_cardIndex = 0; // Генеруємо випадкове число між min і max (включно). // Припускається, що srand() вже викликали static int getRandomNumber(int min, int max) { static const double fraction = 1.0 / (static_cast<double>(RAND_MAX) + 1.0); // Рівномірно розподіляємо рандомне число в нашому діапазоні return static_cast<int>(rand() * fraction * (max - min + 1) + min); } static void swapCard(Card &a, Card &b) { Card temp = a; a = b; b = temp; } public: Deck() { int card = 0; for (int suit = 0; suit < Card::MAX_SUITS; ++suit) for (int rank = 0; rank < Card::MAX_RANKS; ++rank) { m_deck[card] = Card(static_cast<Card::CardRank>(rank), static_cast<Card::CardSuit>(suit)); ++card; } } void printDeck() const { for (const auto &card : m_deck) { card.printCard(); std::cout << ' '; } std::cout << '\n'; } void shuffleDeck() { // Перебираємо кожну карту в колоді for (int index = 0; index < 52; ++index) { // Вибираємо будь-яку випадкову карту int swapIndex = getRandomNumber(0, 51); // Міняємо місцями з нашою поточною картою swapCard(m_deck[index], m_deck[swapIndex]); } m_cardIndex = 0; // починаємо нову роздачу карт } const Card& dealCard() { assert (m_cardIndex < 52); return m_deck[m_cardIndex++]; } }; char getPlayerChoice() { std::cout << "(h) to hit, or (s) to stand: "; char choice; do { std::cin >> choice; } while (choice != 'h' && choice != 's'); return choice; } bool playBlackjack(Deck &deck) { int playerTotal = 0; int dealerTotal = 0; // Дилер отримує одну карту dealerTotal += deck.dealCard().getCardValue(); std::cout << "The dealer is showing: " << dealerTotal << '\n'; // Гравець отримує дві карти playerTotal += deck.dealCard().getCardValue(); playerTotal += deck.dealCard().getCardValue(); // Гравець починає while (1) { std::cout << "You have: " << playerTotal << '\n'; char choice = getPlayerChoice(); if (choice == 's') break; playerTotal += deck.dealCard().getCardValue(); // Дивимося, чи гравець не програв if (playerTotal > 21) return false; } // Якщо гравець не програв (у нього не більше 21 очка), тоді дилер отримує карти до тих пір, поки у нього буде не менше 17 очків while (dealerTotal < 17) { dealerTotal += deck.dealCard().getCardValue(); std::cout << "The dealer now has: " << dealerTotal << '\n'; } // Якщо дилер програв, то гравець виграв if (dealerTotal > 21) return true; return (playerTotal > dealerTotal); } int main() { srand(static_cast<unsigned int>(time(0))); // використовуємо системний годинник в якості стартового значення rand(); // користувачам Visual Studio: скидаємо перше згенероване (рандомне) число Deck deck; deck.shuffleDeck(); if (playBlackjack(deck)) std::cout << "You win!\n"; else std::cout << "You lose!\n"; return 0; } |
Ура!

(45 оцінок, середня: 4,91 з 5)