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

  Юрий  | 

  Обновл. 29 мая 2019  | 

 15704

 ǀ   40 

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

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

Теория

Массивы позволяют хранить и получать доступ ко многим переменным одного и того же типа данных через один идентификатор. Доступ к элементам массива осуществляется с помощью оператора индекса ([]). Будьте осторожны с диапазоном массива, не допускайте индексации элементов вне диапазона. Массивы можно инициализировать с помощью списка инициализаторов или uniform инициализации (в C++11).

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

Циклы используются для итераций по массиву. Остерегайтесь ошибок «неучтённых единиц». Циклы 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::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() возможность перетасовки и вывода уже обновлённой (перетасованной) колоды карт.

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

Подсказка №2: Не забудьте в начале функции main() вызвать srand().

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

Ответ g)

h) Напишите функцию getCardValue(), которая возвращает значение карты (например, 2 значит 2, 3 значит 3 и т.д., десять, валет, королева или король — это 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 в противном случае. Нужно обновить эту функцию, чтобы было три возможных исхода: победа дилера, победа игрока или ничья. Лучший способ это сделать — создать перечисление для этих трёх вариантов + чтобы функция возвращала соответствующее значение из этого перечисления:

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (60 оценок, среднее: 4,75 из 5)
Загрузка...

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

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

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

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

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

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

  2. Аватар Nikita:

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

  3. Аватар Mirovengil:

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

  4. Аватар Алексей:

    Слабо пока, что написать подобное.
    Слабо, практика нужна и много. Это и есть решение.

    Код понимаю, и тд и тп, но пол года может быть мало для всего этого.

  5. Аватар Александр:

    Скажите, у версии VS 2012 есть проблемы при обработке циклов, выводящих данные с массивов?

  6. Аватар Михаил:

    Задание №7. По-русски и с комментариями

  7. Аватар Владимир:

    Задание №7

  8. Аватар Владимир:

    Задание №4

  9. Аватар Владимир:

    Задание №3

  10. Аватар Владимир:

    Задание №2

    1. Аватар Владимир:

      Забыл добавить

  11. Аватар Владимир:

    Задание №1

  12. Аватар XHunter: