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

  Юрій  | 

  Оновл. 17 Січ 2021  | 

 112

Вітаю вас з подоланням найдовшого розділу цього туторіалу. Якщо у вас не було попереднього досвіду в програмуванні, то цей розділ, скоріш за все, був для вас найскладнішим з усіх попередніх. Однак, якщо ви дійшли до цього моменту, то все добре — ви впоралися! Так тримати!

Гарна новина полягає в тому, що наступний розділ буде легшим, і дуже скоро ми доберемося до самого серця цього туторіалу — об’єктно-орієнтованого програмування!

Теорія

Масиви дозволяють зберігати і отримувати доступ до багатьох змінних одного і того ж типу даних через один ідентифікатор. Доступ до елементів масиву здійснюється за допомогою оператора індексу []. Будьте обережні з діапазоном масиву, не допускайте індексації елементів поза діапазоном. Масиви можна ініціалізувати за допомогою списку ініціалізаторів або uniform-ініціалізації.

Фіксовані масиви повинні мати довжину, встановлену під час компіляції. Фіксовані масиви конвертуються у вказівники при передачі у функцію.

Цикли використовуються для ітерації по масиву. Остерігайтеся помилок «неврахованих одиниць». Цикли foreach корисні, коли масив не конвертується у вказівник.

Масиви можна зробити багатовимірними, використовуючи відразу декілька індексів.

Масиви використовуються при створенні рядків C-style. Уникайте використання рядків C-style, замість них використовуйте std::string.

Вказівники — це змінні, які зберігають адреси певних змінних в пам’яті. Оператор адресу (&) використовується для отримання адреси змінної. Оператор розіменування (*) використовується для отримання значення, на яке вказує вказівник.

Нульовий вказівник — це вказівник, який ні на що не вказує. Вказівник можна зробити нульовим, ініціалізувавши або присвоївши йому значення 0 (або nullptr в C++11). Уникайте використання макросу NULL. Розіменування нульового вказівника може призвести до несподіваних результатів (збоїв). При видаленні нульового вказівника нічого поганого не станеться.

Вказівник на масив не знає довжини масиву, на який він вказує. Це означає, що оператор sizeof і цикли foreach працювати з ним не можуть.

Оператори new і delete використовуються для динамічного виділення/звільнення пам’яті для вказівника, змінної або масиву. Хоча подібне трапляється вкрай рідко, оператор new може не спрацювати, якщо в операційній системі не залишиться вільної пам’яті, тому не забувайте виконувати перевірку того, чи повертає оператор new нульовий вказівник.

Обов’язково використовуйте оператор delete[] для видалення динамічно виділеного масиву. Вказівники, які вказують на звільнену пам’ять, називаються “висячими” вказівниками. Розіменування “висячого” вказівника не призведе ні до чого хорошого.

Неможливість звільнити динамічно виділену пам’ять призведе до витоку пам’яті, коли вказівник, який вказує на цю пам’ять, вийде з області видимості.

Для звичайних змінних пам’ять виділяється з обмеженого резервуара — стека. Пам’ять для динамічно виділених змінних виділяється із загального резервуара пам’яті — купи.

Вказівник на константне значення обробляє значення, на яке він вказує, як константне:

Константний вказівник — це вказівник, значення якого не може бути змінено після ініціалізації:

Посилання — це псевдонім для певної змінної. Посилання оголошуються з використанням амперсанда & (в даному контексті це не оператор адресу). Для константних посилань змінити їх значення після ініціалізації не можна. Посилання використовуються для запобігання копіювання даних при їх передачі в функцію або з функції.

Оператор вибору елемента (->) може використовуватися для вибору члена через вказівник на структуру. Він поєднує в собі як операцію розіменування, так і звичайний доступ до елементів (.).

Вказівник типу void — це вказівник, який може вказувати на будь-який тип даних. Він не може бути розіменований напряму. Ви можете використовувати оператор static_cast для конвертування його назад у вихідний тип вказівника. Але який вже це буде тип — вирішувати вам.

Вказівники на вказівники дозволяють створити вказівник, який вказує на інший вказівник.

std::array надає весь функціонал стандартних фіксованих масивів в C++, але які при цьому не будуть конвертуватися у вказівники при передачі. Рекомендується використовувати std::array замість стандартних фіксованих масивів.

std::vector надає весь функціонал динамічних масивів, але які при цьому можуть самостійно управляти виділеною їм пам’яттю і запам’ятовують свою довжину. Рекомендується використовувати std::vector замість стандартних динамічних масивів.

Тест

Завдання №1

Уявіть, що ви пишете гру, в якій гравець може мати 3 типи предметів: зілля здоров’я, факели і стріли. Створіть перерахування з цими типами предметів і фіксований масив для зберігання кількості кожного типу предметів, які має при собі гравець (використовуйте стандартні фіксовані масиви, а не std::array). У вашого гравця повинні бути при собі 3 зілля здоров’я, 6 факелів і 12 стріл. Напишіть функцію countTotalItems(), яка повертає загальну кількість предметів, які є у гравця. У функції main() виведіть результат виконання функції countTotalItems().

Відповідь №1

Завдання №2

Створіть структуру, яка містить ім’я і оцінку учня (за шкалою від 0 до 100). Запитайте у користувача, скільки учнів він хоче ввести. Динамічно виділіть масив для зберігання всіх студентів. Потім попросіть користувача ввести для кожного студента його ім’я і оцінку. Як тільки користувач ввів всі імена і оцінки, відсортуйте список оцінок студентів по спаданню (спочатку найвищий бал). Потім виведіть всі імена і оцінки у відсортованому вигляді.

Для наступного вводу:

Andre
74
Max
85
Anton
12
Josh
17
Sasha
90

Вивід повинен бути наступним:

Sasha got a grade of 90
Max got a grade of 85
Andre got a grade of 74
Josh got a grade of 17
Anton got a grade of 12

Підказка: Ви можете змінити алгоритм сортування масиву методом вибору для сортування вашого динамічного масиву. Якщо ви напишете сортування масиву окремою функцією, то масив повинен передаватися по адресу (як вказівник).

Відповідь №2

Завдання №3

Напишіть свою функцію, яка міняє місцями значення двох цілочисельних змінних. Перевірку здійснюйте в функції main().

Підказка: Використовуйте посилання в якості параметрів.

Відповідь №3

Завдання №4

Напишіть функцію для виведення рядка C-style символ за символом. Використовуйте вказівник для переходу і виведення кожного символу по черзі. Зупиніть вивід при зіткненні з нуль-термінатором. У функції main() протестуйте рядок Hello, world!.

Підказка: Використовуйте оператор ++ для переміщення вказівника на наступний символ.

Відповідь №4

Завдання №5

Що не так з кожним з наступних фрагментів коду, і як би ви їх виправили?

a)

Відповідь №5.a)

Цикл for має помилку «неврахованої одиниці» і намагається отримати доступ до елементу масиву під індексом 6, якого не існує. В умові циклу for потрібно використати оператор < замість оператора <=.

b)

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

ptr — це вказівник на const int. Ми не можемо присвоїти йому значення 7.

c)

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

array конвертується у вказівник при передачі у функцію printArray(). Цикл foreach не працює з вказівником на масив, так як вказівнику невідома довжина масиву, на який він вказує. Перше з рішень — додати параметр length у функцію printArray() і використовувати звичайний цикл for. Друге рішення — використовувати std::array замість стандартних фіксованих масивів.

d)

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

Ми не можемо присвоїти вказівнику типу int змінну не типу int. ptr повинен бути типу double*.

Завдання №6

Припустимо, що ми хочемо написати карткову гру.

a) У колоді карт знаходяться 52 унікальні карти: 13 старшинств (2, 3, 4, 5, 6, 7, 8, 9, 10, Валет, Дама, Король, Туз) і 4 масті (трефи, бубни, черви, піки). Створіть два перерахування: перше для масті, друге для старшинств.

Підказка: Додайте в кожне перерахування ще по одному елементу, який буде позначати довжину цього перерахування.

Відповідь №6.a)

b) Кожна карта повинна бути представлена структурою Card, в якій зберігається інформація про старшинство і масть карти (наприклад, 4 бубни, король трефи). Створіть цю структуру.

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

c) Створіть функцію printCard(), параметром якої буде константне посилання типу структури Card, яка буде виводити значення старшинства і масті певної карти у вигляді 2-літерного коду (наприклад, валет піки буде виводитися як VP).

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

d) Для представлення цілої колоди карт (52 карти) створіть масив deck (використовуючи std::array) і ініціалізуйте кожен елемент певною картою.

Підказка: Використовуйте оператор static_cast для конвертації цілочисельної змінної у тип перерахування.

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

е) Напишіть функцію printDeck(), яка в якості параметру приймає константне посилання на масив deck і виводить всі значення (карти). Використовуйте цикл foreach.

Відповідь №6.e)

f) Напишіть функцію swapCard(), яка приймає дві карти і міняє місцями їх значення.

Відповідь №6.f)

g) Напишіть функцію shuffleDeck() для перетасовки колоди карт. Для цього використайте цикл for з ітерацією по масиву. Перетасування карт повинно відбутися 52 рази. У циклі for виберіть випадкове число від 1 до 52 і викличте функцію swapCard(), параметрами якої будуть поточна карта і рандомно вибрана карта. Додайте в функцію main() можливість перетасовки і виведення вже оновленої (перетасованої) колоди карт.

Підказки:

   Для генерації випадкових чисел дивіться урок №74.

   Не забудьте в початку функції main() викликати функцію srand().

   Якщо ви використовуєте Visual Studio, то не забудьте перед генерацією випадкового числа викликати один раз функцію rand().

Відповідь №6.g)

h) Напишіть функцію getCardValue(), яка повертає значення карти (наприклад, 2 означає 2, 3 означає 3 і т.д., 10, валет, королева або король — це 10, туз — це 11).

Відповідь №6.h)

Завдання №7

Добре, настав час для серйозної гри! Давайте напишемо спрощену версію відомої карткової гри “Blackjack” (російський аналог «Очко» або «21 очко»). Якщо ви не знайомі з цією грою і її правилами, то ось посилання на статтю у Вікіпедії про Блекджек.

Правила нашої версії гри “Блекджек” наступні:

   спочатку дилер отримує одну карту (в реальності дилер отримує дві карти, але одна лицьовою стороною вниз, тому на даному етапі це не має значення);

   потім гравець отримує дві карти;

   гравець починає;

   гравець може або “взяти” (hit), або “втриматися” (stand);

   якщо гравець “втримався”, то його хід завершено, і його результат підраховується на основі карт, які у нього є;

   якщо гравець “бере”, то він отримує другу карту, і значення цієї карти додається до його вже існуючого результату;

   туз зазвичай рахується як 1 або 11. Щоб було простіше, ми будем рахувати його як 11;

   якщо у гравця в результаті виходить більше 21, то він програв;

   хід дилера виконується після ходу гравця;

   дилер бере карти до тих пір, поки його загальний результат не досягне 17 або більше очок. Як тільки ця межа досягнута — дилер карт вже не бере;

   якщо у дилера більше 21, то дилер програв, а гравець виграв;

   якщо ж у дилера і у гравця до 21 очка, то виграв той, у кого більший результат.

У нашій спрощеній версії гри “Blackjack” ми не будемо відслідковувати, які конкретно карти були у гравця, а які у дилера. Ми будемо відслідковувати тільки суму значень карт, які вони отримали. Так буде простіше.

Почнемо з коду, який у нас вийшов в завданні №6. Створіть функцію playBlackjack(), яка повертає true, якщо гравець перемагає, і false — якщо гравець програє. Ця функція повинна:

   Приймати перетасовану колоду карт (deck) в якості параметру.

   Ініціалізувати вказівник на першу карту (ім’я вказівника — cardPtr). Це буде використовуватися для роздачі карт з колоди.

   Мати дві цілочисельні змінні для зберігання результату гравця і дилера.

   Відповідати правилам, наведеним вище.

Підказка: Найпростіший спосіб роздачі карт з колоди — це змусити вказівник вказувати на наступну карту в колоді (яка буде роздаватися). Всякий раз, коли нам потрібно буде роздати карту, ми отримуємо значення поточної карти, а потім змушуємо вказівник вказувати на наступну карту. Це можна зробити наступним рядком коду:

Тут повертається значення поточної карти (яке потім може бути додано до загального результату гравця або дилера) і вказівник cardPtr переходить до наступної карти.

Протестуйте виконання одиночної гри «Блекджек» у функції main().

Відповідь №7

Додаткові завдання

a) Час для критичного мислення. Опишіть, як би ви могли модифікувати вищенаведену програму для обробки випадків, коли туз може дорівнювати 1 очку або 11 очкам.

Відповідь a)

Можна було б відстежувати в окремій цілочисельній змінній-лічильнику, скільки тузів гравець і дилер отримали. Якщо у гравця або дилера результат перевищив 21 очко, і його лічильник тузів більше нуля, то тоді зменшується результат гравця або дилера на 10 (конвертуємо туз з 11 очків в 1) і видаляється 1 з лічильника тузів. Триватиме це до тих пір, поки лічильник тузів не досягне нуля.

b) У реальному “Блекджек”-у, якщо у гравця і дилера порівну очків, то результатом є нічия, і жоден з них не виграв. Опишіть, як би ви змінили вищенаведену програму з урахуванням даної умови.

Відповідь b)

Функція playBlackjack() зараз повертає true, якщо гравець виграє, і false — якщо програє. Потрібно оновити цю функцію, щоб було три можливих варіанти: перемога дилера, перемога гравця або нічия. Кращий спосіб це зробити — створити перерахування для цих 3-х варіантів + щоб функція повертала відповідне значення з цього перерахування:

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

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

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

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