Страница на Ravesli.
Поздравляю вас с преодолением самой длинной главы этого туториала! Если у вас не было предыдущего опыта в программировании, то эта глава, скорее всего, была для вас наиболее сложной из всех предыдущих. Однако, если вы дошли до этого момента, то всё хорошо — вы справились! Так держать!
Хорошая новость заключается в том, что следующая глава будет легче этой, и очень скоро мы доберемся до самого сердца этого туториала — объектно-ориентированного программирования!
Оглавление:
Массивы позволяют хранить и получать доступ ко многим переменным одного и того же типа данных через один идентификатор. Доступ к элементам массива осуществляется с помощью оператора индекса []
. Будьте осторожны с диапазоном массива, не допускайте индексации элементов вне диапазона. Массивы можно инициализировать с помощью списка инициализаторов или 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 вместо стандартных динамических массивов.
Представьте, что вы пишете игру, в которой игрок может иметь 3 типа предметов: зелья здоровья, факелы и стрелы. Создайте перечисление с этими типами предметов и фиксированный массив для хранения количества каждого типа предметов, которое имеет при себе игрок (используйте стандартные фиксированные массивы, а не std::array). У вашего игрока должны быть при себе 3 зелья здоровья, 6 факелов и 12 стрел. Напишите функцию countTotalItems(), которая возвращает общее количество предметов, которые есть у игрока. В функции main() выведите результат работы функции countTotalItems().
Создайте структуру, содержащую имя и оценку учащегося (по шкале от 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 для сортировки вашего динамического массива. Если вы напишете сортировку массива отдельной функцией, то массив должен передаваться по адресу (как указатель).
Напишите свою функцию, которая меняет местами значения двух целочисленных переменных. Проверку осуществляйте в функции main().
Подсказка: Используйте ссылки в качестве параметров.
Напишите функцию для вывода строки C-style символ за символом. Используйте указатель для перехода и вывода каждого символа поочерёдно. Остановите вывод при столкновении с нуль-терминатором. В функции main() протестируйте строку Hello, world!
.
Подсказка: Используйте оператор ++ для перевода указателя на следующий символ.
Что не так с каждым из следующих фрагментов кода, и как бы вы их исправили?
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;
}
Предположим, что мы хотим написать карточную игру.
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() возможность перетасовки и вывода уже обновленной (перетасованной) колоды карт.
Подсказки:
- Для генерации случайных чисел смотрите урок №71.
- Не забудьте в начале функции main() вызвать функцию srand().
- Если вы используете Visual Studio, то не забудьте перед генерацией случайного числа вызвать один раз функцию rand().
h) Напишите функцию getCardValue(), которая возвращает значение карты (например, 2 значит 2, 3 значит 3 и т.д., 10, валет, королева или король — это 10, туз — это 11).
Хорошо, настало время для серьезной игры! Давайте напишем упрощенную версию известной карточной игры «Blackjack» (русский аналог «Очко» или «21 очко»). Если вы не знакомы с этой игрой и её правилами, то вот ссылка на статью в Википедии о «Блэкджек».
Правила нашей версии «Blackjack» следующие:
hit
), либо «удержаться» (stand
);В нашей упрощенной версии «Blackjack» мы не будем отслеживать, какие конкретно карты были у игрока, а какие у дилера. Мы будем отслеживать только сумму значений карт, которые они получили. Так будет проще.
Начнем с кода, который у нас получился в задании №6. Создайте функцию playBlackjack(), которая возвращает true
, если игрок побеждает, и false
— если он проигрывает. Эта функция должна:
deck
) в качестве параметра.cardPtr
). Это будет использоваться для раздачи карт из колоды.Подсказка: Самый простой способ раздачи карт из колоды — это заставить указатель указывать на следующую карту в колоде (которая будет раздаваться). Всякий раз, когда нам нужно будет раздать карту, мы получаем значение текущей карты, а затем заставляем указатель указывать на следующую карту. Это можно сделать в одной строке кода:
getCardValue(*cardPtr++);
Здесь возвращается значение текущей карты (которое затем может быть добавлено к общему результату игрока или дилера) и указатель cardPtr переходит на следующую карту.
Протестируйте выполнение одиночной игры «Блэкджек» в функции main().
a) Время для критического мышления. Опишите, как бы вы могли модифицировать программу, приведенную выше, для обработки случаев, когда стоимость тузов может равняться 1 очку или 11 очкам.
b) В реальном «Блэкджек», если у игрока и дилера равное количество очков, то результатом является ничья, и ни один из них не выигрывает. Опишите, как бы вы изменили программу, приведенную выше, для учета такого исхода игры.