Создаём компьютерную игру
eng   рус

Циклы (loops) и массивы (arrays) в C++

Предыдущий урок: Условные операторы: if...else if...else, switch
Следующий урок: Указатели и ссылки

Теперь у нас на очереди циклы и массивы. Начнём с примера. Возьмём какую-нибудь растровую картинку. Слово растровый означает, что изображение состоит из отдельных точек - пикселей. Пусть размер нашей картинки будет 1920x1080 - это соответствует расширению Full HD. В этой картинке 2073600 пикселей. 1920 - ширина, 1080 - высота. Можно сказать, что картинка состоит из 1920 строк и 1080 столбцов (мы вернёмся к этому позднее). Каждый пиксель мы можем представить в памяти с помощью отдельной переменной типа int. С нашими текущими знаниями мы можем представить картинку в программе вот так:

int pixel1; int pixel2; int pixel3; // ... int pixel2073600;

Как видите, здесь есть небольшая проблема. Решить которую нам помогут массивы.

Массивы (arrays) в C++

Массив (array) в C++ - это участок памяти содержащий несколько элементов одного типа. В памяти элементы массива располагаются рядом друг с другом. Все элементы должны быть одного типа. Напоминаю, что каждый байт в памяти имеет свой адрес. Если первый элемент массива типа char хранится по адресу 0, то следующий будет храниться по адресу 1. Для типа int, второй элемент будет находиться по адресу 4.

Объявление (declaring) массивов

Объявление массивов очень похоже на объявление переменных. Давайте создадим массив, который будет хранить нашу картинку:

int image[2073600];

Имя массива - обычный идентификатор. После имени массива нужно поставить квадратные скобки, а в скобках указать количество элементов в массиве. Компьютер зарезервирует необходимое количество памяти.

Каждый элемент массива имеет порядковый номер - индекс. Нумерация элементов начинается с нуля:

image[0] = 0; // первый элемент image[1] = 0; // второй элемент image[2073599] = 0; // последний элемент

Обратите внимание, что последний индекс всегда на единицу меньше количества элементов в массиве. При объявлении массива в его элементах содержится мусор.

Инициализация массива

Рассмотрим меньший массив:

char units[3] = { 'a', 'b', 'c'};

Здесь мы создали массив типа char, содержащий три элемента. Первый элемент (с индексом ноль) будет иметь значение 'a', последний (с индексом 2) - 'c'.

При инициализации можно задавать значения не всем элементам. Это позволяет нам очень легко инициализировать большие массивы:

int image[2073600] = {0};

В фигурных скобках можно указать только одно значение. Оставшиеся элементы будут проинициализированы нулями.

Многомерные (multidimensional) массивы

Многомерные массивы позволяют более наглядно представить информацию. Вернёмся к примеру с картинкой. В одномерном массиве сложно ориентироваться - какой пиксель находится слева посередине? С помощью двухмерного массива мы можем легко решить эту проблему:

int image[1080][1920] = {0}; int image[539][0]; // пиксель находящийся слева посередине

Обратите внимание, что сначала мы указываем строку, а затем столбец. Мы можем сделать и наоборот:

int image[1920][1080] = {0}; int image[0][539]; // пиксель находящийся слева посередине

Представление - это наш выбор. Вопрос в том, как нам интерпретировать данные. Первый вариант подходит для представления таблицы (строк, столбцов). Второй для представления координат (x,y). Помните, что значение данным придаёте вы - для компьютера это всего лишь цифры. А модель и интерпретация - ваша задача.

Циклы (loops) в C++

Циклы позволяют выполнить один и тот же кусок кода много раз. С помощью цикла мы можем легко обрабатывать все элементы массива. C++ предоставляет следующие циклы: for, while, do-while и цикл for с диапазоном (в других языках это цикл foreach).

Цикл for в C++

Например, мы хотим "перекрасить" все пиксели в чёрный цвет:

int image[2073560]; for (int i = 0; i < 2073600; i++) { image[i] = 0; }

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

Цикл работает следующим образом. Первым делом выполняется первое выражение - инициализация счётчика. Это делается только один раз. Затем идёт проверка условия, если условие истинное, то выполняется тело цикла. Потом исполняется третье выражение - изменение счётчика. Затем снова условие, тело цикла, изменение счётчика... Этот цикл происходит до тех пор пока условие не станет ложным. Тогда процессор перейдёт на следующую строку после цикла.

Мы можем использовать вложенные циклы и иметь больше одного счётчика. Рассмотрим пример:

int image1[1080][1920]; int image2[2073600] for (int i = 0, pixel = 0; i < 1080; i++) { for (int j = 0; j < 1920; j++, pixel++) { image1[i][j] = 0; image2[pixel] = 0; } }

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

Цикл while и do-while

Все циклы взаимозаменяемы. Т.е. мы можем написать код с помощью for или с помощью while - разница будет лишь в синтаксисе, а действия будут одинаковыми. Просто циклы, если можно так сказать, имеют свою специализацию. Рассмотрим код:

int image[2073599]; int i = 0; while (i < 2073599) { image[i] = 0; i++; }

Этот код идентичен первому циклу for. Счётчик мы инициализируем до цикла. После ключевого слова while в круглых скобках мы указываем условие продолжения цикла - если оно верно, выполняется тело цикла. В конце тела цикла мы меняем счётчик. В цикле for эти выражения собраны вместе и для подобных циклов лучше использовать for. Теперь цикл do-while:

int image[2073599]; int i = 0; do { image[i] = 0; i++; } while (i < 2073599)

Единственная разница с циклом while - тело цикла do-while будет выполнено хотя бы один раз. Т.е. сначала выполняется тело цикла, потом идёт проверка условия.

Бесконечные циклы и break/continue

Очень часто встречается использование цикла while в качестве бесконечного цикла:

while (true) { }

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

while (true) { If(/*какое-то условие*/){ break; } if (/*какое-то условие*/) { continue; } // Другой код }

Ключевое слово break принудительно заканчивает цикл. Слово continue пропускает оставшуюся часть тела цикла и переходит к проверке условия. break и continue можно использовать и в цикле for.

Циклы с диапазоном (range-based)

С версии C++11 в языке появляется аналог цикла foreach из других языков - цикл for на основе диапазона (range-based for loop). В данном цикле не нужно создавать счётчики, он будет выполнен для всех элементов массива. Давайте посмотрим на пример:

int image[2073599]; for (int& pixel: image) { pixel = 0; }

Цикл пробегает по всем элементам массива image и "помещает" их в переменную pixel. Внутри тела цикла мы обращаемся к переменной pixel, которая в разные итерации указывает на разные элементы массива. Тип переменной можно заменить на auto. Компилятор сам решит какой тип использовать основываясь на типе массива.

int image[2073599]; for (auto& pixel: image) { pixel = 0; }

Обратите внимание на использование символа &. Он необязателен, но позволяет изменять значения массива. & указывает что данная переменная является ссылкой (reference). Подробнее про ссылки мы будем говорить в следующем уроке.

Заключение

Пожалуй, закончу данный урок самым важным: массивы позволяют работать с большим количеством информации, а циклы позволяют обрабатывать их.

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

No comments yet