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

   ⁄ 

 Обновлено 21 Ноя 2017

  ⁄   

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

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

Теория

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

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

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

Массивы можно сделать многомерными, используя сразу несколько индексов.

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

Указатели — это переменные, которые хранят адреса памяти (указывают на) определенных переменных. Оператор адреса & используется для получения адреса переменной. Оператор разыменования * используется для получения значения, на которое указывает указатель.

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

Указатель на массив не знает длину массива, на который указывает. Это означает, что sizeof() и циклы foreach работать с ним не могут.

Операторы new и delete используются для динамического выделения памяти для указателя, переменной или массива. Хотя подобное случается крайне редко, оператор new может потерпеть крах, если в операционной системе не останется свободной памяти, поэтому не забывайте выполнять проверку того, возвращает ли new нулевой указатель.

Обязательно используйте оператор delete (delete []) при удалении динамически выделенного массива. Указатели, указывающие на освобожденную память, называются висячими указателями. Разыменование висячего указателя не приведет ни к чему хорошему.

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

Для обычных переменных память выделяется из ограниченного резервуара — стека. Память для динамически выделенных переменных выделяется из общего резервуара памяти – кучи.

Указатель на значение const обрабатывает значение, на которое он указывает, как константное.

Указатель const — это указатель, значение которого не может быть изменено после инициализации.

Ссылка — это псевдоним для определенной переменной. Ссылки объявляются с использованием амперсанда & (в этом контексте это не оператор адреса). Для константных ссылок изменить значение после инициализации нельзя. Ссылки используются для предотвращения копирования данных при их передаче в функцию или из функции.

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

Указатели типа 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() возможность перетасовки и вывода уже обновленной (перетасованной) колоды карт.

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

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

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

Ответ 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 (5 оценок, среднее: 4,60 из 5)
Загрузка...
Поделиться в:
Подписаться на обновления:

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *