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

  Юрий  | 

  Обновл. 17 Сен 2020  | 

 34070

 ǀ   56 

Поздравляем вас с преодолением самой длинной главы этого туториала. Если у вас не было предыдущего опыта в программировании, то эта глава, скорее всего, была для вас наиболее сложной из всех предыдущих. Однако, если вы дошли до этого момента, то всё хорошо — вы справились! Так держать!

Хорошая новость заключается в том, что следующая глава будет легче этой, и очень скоро мы доберемся до самого сердца этого туториала — объектно-ориентированного программирования!

Теория

Массивы позволяют хранить и получать доступ ко многим переменным одного и того же типа данных через один идентификатор. Доступ к элементам массива осуществляется с помощью оператора индекса []. Будьте осторожны с диапазоном массива, не допускайте индексации элементов вне диапазона. Массивы можно инициализировать с помощью списка инициализаторов или 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

Подсказка: Вы можете изменить алгоритм сортировки массива методом выбора из урока №77 для сортировки вашего динамического массива. Если вы напишете сортировку массива отдельной функцией, то массив должен передаваться по адресу (как указатель).

Ответ №2

Задание №3

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

Подсказка: Используйте ссылки в качестве параметров.

Ответ №3

Задание №4

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

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

Ответ №4

Задание №5

Что не так с каждым из следующих фрагментов кода, и как бы вы их исправили?

a)

Ответ a)

Цикл for имеет ошибку «неучтенной единицы» и пытается получить доступ к элементу массива под индексом 6, которого не существует. В условии цикла for нужно использовать оператор < вместо оператора <=.

b)

Ответ b)

ptr — это указатель на const int. Мы не можем присвоить ему значение 7.

c)

Ответ c)

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

d)

Ответ d)

Мы не можем присвоить указателю типа int переменную не типа int. ptr должен быть типа double*.

Задание №6

Предположим, что мы хотим написать карточную игру.

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

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

Ответ a)

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

Ответ b)

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

Ответ c)

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

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

Ответ d)

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

Ответ e)

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

Ответ f)

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

Подсказки:

   Для генерации случайных чисел смотрите урок №71.

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

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

Ответ g)

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

Ответ h)

Задание №7

Хорошо, настало время для серьезной игры! Давайте напишем упрощенную версию известной карточной игры Blackjack (русский аналог «Очко» или «21 очко»). Если вы не знакомы с этой игрой и её правилами, то вот ссылка на статью в Википедии о Блэкджеке.

Правила нашей версии Blackjack следующие:

   вначале дилер получает одну карту (в реальной жизни, дилер получает две карты, но одна лицевой стороной вниз, поэтому на данном этапе это не имеет значения);

   затем игрок получает две карты;

   игрок начинает;

   игрок может либо «взять» (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 (121 оценок, среднее: 4,79 из 5)
Загрузка...

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

  1. Аватар Аноним:

    Кому интересно, написал игру с азартом, настоящий симулятор, как в жизни))

  2. Аватар Rock:

    За примерно 12 часов удалось сделать полноценную игру

    Вот сама игра — https://pastebin.com/Bgc8wGk2

    И 2 подключаемых файла

    https://pastebin.com/JY35NBL9 — проверка корректности ввода

    https://pastebin.com/v9BTzbbu — Рандомайзер на вихре Марсена

    пользуйтесь кому нужно будет)

  3. Аватар Vlad:

  4. Аватар Sagynysh:

    Task 1

  5. Аватар Павел:

    Мой вариант решения задачи. Пошёл не стандартным путём)))

  6. Аватар Павел:

    Задача № 1:

    Не совсем понял задание. Автор может имеет смысл добавить результат работы программы, а то допёрло что от меня хотят только после просмотра ответа.

  7. Аватар cybersatori:

    закончил 7 задание стал карточным шулером и кулхацкером впридачу) клёво спасибо, единственное с наполнением массива картами запарился, громоздкий стейтмент — а так все сделал, работает.)

  8. Аватар Vlad:

    Делал всю программу целых 2 дня, но оно того стоило. Спасибо вам большое за всё.

  9. Аватар Тимур:

    Юрий, планируется ли обновление и дополнение материалов? На learncpp вышло несколько новых уроков, а также некоторые уроки были дополнены.

    1. Юрий Юрий:

      Планируется добавление новых 5 уроков, сейчас над этим как раз работаю)

  10. Аватар Константин:

    Автор, а в задании 5 в пункте С не ошибка ли?

    В коде, в котором нужно найти ошибку написан for, в ответе вы пишите: "Цикл foreach не работает с указателем на массив, так как указателю неизвестна длина массива, на который он указывает. Первое из решений — добавить параметр length в функцию printArray() и использовать обычный цикл for."
    Но ведь там уже for, хотя должно быть слово foreach, или я что-то не так понял?

    1. Юрий Юрий:

      Всё верно, в примере используется цикл foreach. Перечитайте урок №91, отличительной чертой цикла foreach является использование с перечислительными типами данных, где есть диапазон и при этом в цикле foreach используется то же ключевое слово for (а не foreach).

      1. Аватар Константин:

        Точно! Что-то я конкретно затупил с этим, мне казалось что там "foreach". Тогда извиняюсь за беспокойство, спасибо за ваши труды.

        1. Юрий Юрий:

          Ничего страшного, пожалуйста)

  11. Аватар Анастасия:

    Чтобы не занимать много места скину ссылку на мой github. Вот, в общем то, и задание 7.
    https://github.com/Luzinsan/Chapter_6/blob/master/Blackjack

    В моей версии Blackjack я отслеживаю каждую карту игрока и дилера. Причём, сначала выводится карта игрока, потом карта дилера, потом снова карта игрока(уже другая), и если сумма карт игрока меньше 21 то спрашивается, нужно ли ещё добрать карты. Также продумала случай при тузе. Если на руках найден туз и сумма превышает 21, то мы меняем найденный туз на модифицированный со значением 1. Есть ещё некоторые различия по правилам, но их долго расписывать).

    Спасибо автору learncpp и вам, Юрий, что доносите до нас такую полезную информацию, да ещё так предельно понятно. Буду продолжать заниматься, ведь благодаря этой игре энтузиазм к постижению с++ у меня только увеличился))).

  12. Аватар Анастасия Лузинсан:

    Хорошие задания.) 2-е задание:

  13. Аватар Андрей:

    Вот моя версия выполнения задания №7.

    Я разделил программу на два файла: card.h и main.cpp.
    В файле card.h находятся все реализованные задачи из задания №6.
    В файле main.cpp реализация игры блэкджек.

    Ссылка на card.h: https://pastebin.com/U2nKyS1U
    Ссылка на main.cpp: https://pastebin.com/zPwnMThL

    Заметка: писал программу в Qt Creator. В игре есть возможность начать сначала.

  14. Аватар Nikita:

    Мой вариант полноценной игры.
    С проработкой туза в 1 или 11, в зависимости от необходимости.

  15. Аватар Mirovengil:

    А я думал, что в седьмом задании надо выводить карты…