Jaroshevskii

YouTube Channel Subscribers YouTube Channel Views

Discord

VSCode

Web Aplication development

This is emoji page 😜

This is 😎

What? HTML?


Snake

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

Страница на Ravesli.

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

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

Оглавление:

  1. Теория
  2. Задание №1
  3. Задание №2
  4. Задание №3
  5. Задание №4
  6. Задание №5
  7. Задание №6
  8. Задание №7

Теория

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

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

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

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

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

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

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

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

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

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

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

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

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

int value = 7;
const int *ptr = &value; // всё нормально, ptr указывает на "const int"

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

int value = 7;
int *const ptr = &value;

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

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

Указатели типа void — это указатели, которые могут указывать на любой тип данных. Они не могут быть разыменованы напрямую. Вы можете использовать оператор static_cast для преобразования их обратно в исходный тип указателя. Какой уже это будет тип — решать вам.

Указатели на указатели позволяют создать указатель, указывающий на другой указатель.

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

std::vector предоставляет весь функционал динамических массивов, но которые при этом могут самостоятельно управлять выделенной себе памятью и запоминают свою длину. Рекомендуется использовать std::vector вместо стандартных динамических массивов.

Тест

Задание №1

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

Задание №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 для сортировки вашего динамического массива. Если вы напишете сортировку массива отдельной функцией, то массив должен передаваться по адресу (как указатель).

Задание №3

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

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

Задание №4

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

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

Задание №5

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

a)

#include <iostream>
 
int main()
{
    int array[6] { 0, 2, 4, 7, 9 };
    for (int count = 0; count <= 6; ++count)
        std::cout << array[count] << " ";
 
    return 0;
}

b)

#include <iostream>
 
int main()
{
    int a = 4;
    int b = 6;
 
    const int *ptr = &a;
    std::cout << *ptr;
    *ptr = 7;
    std::cout << *ptr;
    ptr = &b;
    std::cout << *ptr;
 
    return 0;
}

c)

#include <iostream>
 
void printArray(int array[])
{
	for (const int &element : array)
		std::cout << element << ' ';
}
 
 
int main()
{
	int array[] { 8, 6, 4, 2, 0 };
	printArray(array);
 
	return 0;
}

d)

#include <iostream>
 
int main()
{
    double d(4.7);
    int *ptr = &d;
    std::cout << ptr;
 
    return 0;
}

Задание №6

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

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

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

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

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

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

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

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

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

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

Подсказки:

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

Задание №7

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

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

В нашей упрощенной версии «Blackjack» мы не будем отслеживать, какие конкретно карты были у игрока, а какие у дилера. Мы будем отслеживать только сумму значений карт, которые они получили. Так будет проще.

Начнем с кода, который у нас получился в задании №6. Создайте функцию playBlackjack(), которая возвращает true, если игрок побеждает, и false — если он проигрывает. Эта функция должна:

Подсказка: Самый простой способ раздачи карт из колоды — это заставить указатель указывать на следующую карту в колоде (которая будет раздаваться). Всякий раз, когда нам нужно будет раздать карту, мы получаем значение текущей карты, а затем заставляем указатель указывать на следующую карту. Это можно сделать в одной строке кода:

getCardValue(*cardPtr++);

Здесь возвращается значение текущей карты (которое затем может быть добавлено к общему результату игрока или дилера) и указатель cardPtr переходит на следующую карту.

Протестируйте выполнение одиночной игры «Блэкджек» в функции main().

Дополнительные задания

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

b) В реальном «Блэкджек», если у игрока и дилера равное количество очков, то результатом является ничья, и ни один из них не выигрывает. Опишите, как бы вы изменили программу, приведенную выше, для учета такого исхода игры.