Вот мы и рассмотрели сердце этого туториала — объектно-ориентированное программирование в языке 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); // используем static, так как это значение нужно вычислить единожды // Равномерно распределяем вычисление значения из нашего диапазона 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); // используем static, так как это значение нужно вычислить единожды // Равномерно распределяем вычисление значения из нашего диапазона 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() и инициализируйте его 6-ю любыми именами на ваш выбор. Добавьте возможность выбора случайного имени из этого массива.
Следующий фрагмент должен скомпилироваться без ошибок:
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); // используем static, так как это значение нужно вычислить единожды // Равномерно распределяем вычисление значения из нашего диапазона 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); // используем static, так как это значение нужно вычислить единожды // Равномерно распределяем вычисление значения из нашего диапазона 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)); |
Следующий фрагмент должен cкомпилироваться без ошибок:
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); // используем static, так как это значение нужно вычислить единожды // Равномерно распределяем вычисление значения из нашего диапазона 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); // используем static, так как это значение нужно вычислить единожды // Равномерно распределяем вычисление значения из нашего диапазона 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); // используем static, так как это значение нужно вычислить единожды // Равномерно распределяем вычисление значения из нашего диапазона 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; } |
Ура!
Блин, я не понял почему мы используем статические методы (
В третьем задании мы создаем статический класс для того, чтобы при обращении к нему не нужно было создавать объект этого класса.
Чтобы из мейна можно было просто вызвать
По сути, это просто две глобальные функции, которые будут работать и так
А в мейне вызов через
В класс их заключили вероятно потому что они используются в паре для одной цели.
В четвертом задании не знаю. У нас есть 2 приватных статических метода, которые используются только
одним публичным методом shuffleDeck() один раз перед игрой, предварительно создав объект этого класса.
Здравствуйте! Не могли бы объяснить подробнее, почему мы используем static метод с функцией getRandomNumber( )?
И для чего в строке "const Card& dealCard( )" — указан "сonst" и ссылочный тип возвращения? Для оптимизации работы функции с классами?
Так и осталось непонятным почему статическая переменная static std::string s_name[6] обязательно должна быть объявлена в теле функции Monster generateMonster(), а не в теле класса MonsterGenerator. Если объявить эту переменную в теле класса (неважно Private или Public) то вылезет ошибка "E1592 элемент с инициализатором внутри класса должен быть постоянной".
Использую IDE Visual Studio.
Нужно ли для однотипных методов в разных классах давать уникальные имена?
Например:
При вызове метода от объекта получается:
Но конструкции:
Более читаемые. По тому от какого объекта они вызываются, итак понятно какому классу они принадлежат.
В чем тогда преимущество делать уникальные имена printCard() и printDeck()?
Думаю, уникальные имена для методов нужны. Они обеспечивают интуитивное понимание работы каждого метода.
У тебя приведен простой пример, где имена объектов отражают суть класса:
Методы не потеряют своей "понятности", если упростить имена методов:
Но если имена объектов будут неочевидными:
А с упрощенными именами методов это будет выглядеть так:
Здесь уже голову можно сломать.
Почему в ответе в функции перетасовки карт не используется цикл foreach? Я использовала именно его:
Можно и так, оба варианта занимают одинаковое время на выполнение.
Есть вопрос по выводу. Почему при m_rank == RANK_10 функция printCard() вместо 10 он выдаёт 12592 (я проверил, что по отдельности 1 и 0 функция printCard() выдаёт именно как 1 и 0)?
Функция printCard() возвращает тип данных char а он может хранить в себе только 1 символ, глава 2 урок 35. '10' — ошибка тут 2 символа выход за пределы диапазона char и преобразуются в int без расширения знака. Если вы желаете вывести 10 просто поменяйте кавычки на строковые "10".
В третьем задании выдает ошибку: "MonsterGenerator::m_names": статический элемент элемент данных с инициализатором внутри класса должен иметь неизменяемый целочисленный константный тип или должен быть указан как "inline". Почему? Код точно такой же как в уроке
У меня было то же самое, пока я не поместила массив s_names ВНУТРЬ функции generateMonster ()
Кто-нибудь может объяснить, почему в 3 задании для генерации монстров создаётся отдельный класс? Почему нельзя обе функции из него сделать в основном классе?
Насколько правильно или неправильно сделать playBlackjack методом класса Deck? Тоже самое для getPlayerChoice, поместил его в private.
Получается, что колода предназначена только для игры в Blackjack. Конечно так можно сделать, но ведь и другие игры есть.
Можно популярно, почему используется именно эта конструкция для генерирования случайного числа?
И (max-min+1) в VSC подчеркивается и говорит Arithmetic overflow С26451.
Это выведенная в предыдущих уроках прога, привязанная к системным часам для 100% рандома чисел. Именно поэтому в main добавили
и
Может кто-то разжевать как именно работает эта первая функция (не понимаю откуда формулы такие)
Про:
Здесь 1.0, по сути, будет делиться на 0.0(переполнение RAND_MAX, при помощи + 1.0, т.к RAND_MAX это int16_t и RAND_MAX всегда равно 32 767), что является небольшой хитростью плюсов, т.к. таким способом мы реализуем double бесконечность, ну а после, возвращаем уже готовую функцию rand() с нужным нам диапазоном(предварительно конвертируя её в int).
2-ое:
Если у вас происходит overflow попробуйте написать вместо:
Вот это:
Во-вторых, создайте конструктор, который не принимает никаких параметров и инициализирует каждый элемент m_deck случайной картой…
Подскажите пожалуйста, я так и не понял что значит случайной картой? Ведь в ответе инициализация происходит по-порядку… Или я где-то ошибаюсь?
Меня тоже это смутило. Походу опечатка.
Я это сделал, а потом удивился ответу… Ну, работаю сам в госучреждении, поэтому привык к правилу "Если дали ТЗ — делай по ТЗ. Даже если всё будет криво — можно отмазаться чётко поставленной задачей". Тем более нет возможности переспросить дающего задание о рациональности заполнять массив рандомными картами, учитывая, что среди них 100% попадутся одинаковые.
//main.cpp
//Monster.h