Циклы

Начнем с того что с циклами вы уже по-любому сталкивались: это конечно же основной цикл любой программы loop(). Цикл это грубо говоря рамки, код внутри которых выполняется сверху вниз и повторяется с начала, когда достигает конца. Продолжается это дело до тех пор, пока выполняется какое то условие. Есть два основных цикла, с которыми мы будем работать, это for и while.

Цикл for


Цикл for, в простонародии счётчик, в различных видах этот цикл есть и в других языках программирования, но на C++ он имеет очень гибкую настройку. При создании цикл принимает три “значения” (настройки): инициализация, условие и изменение. Цикл for обычно содержит переменную, которая изменяется на протяжении работы цикла, мы можем пользоваться её меняющимся значением в своих целях. Переменная является локальной для цикла, если она создаётся при инициализации.

  • Инициализация – здесь обычно присваивают начальное значение переменной цикла. Например: int i = 0;
  • Условие – здесь задаётся условие, при котором выполняется цикл. Как только условие нарушается, цикл завершает работу. Например: i < 100;
  • Изменение – здесь указывается изменение переменной цикла на каждой итерации. Например: i++;

Объединим три предыдущих пункта в пример:

for (int i = 0; i < 100; i++) {
  // тело цикла
}

В теле цикла мы можем пользоваться значением переменной i, которая примет значения от 0 до 99 на протяжении работы цикла, после этого цикл завершается. Как это использовать? Вспомним предыдущий урок про массивы и рассмотрим следующий пример:

// создадим массив на 100 ячеек
byte myArray[100];

for (int i = 0; i < 100; i++) {
  // заполним все ячейки значением 25
  myArray[i] = 25;
}

Именно при помощи цикла for очень часто работают с массивами. Можно, например, сложить все элементы массива для поиска среднего арифметического:

// создадим массив данных
byte myVals[] = {5, 60, 214, 36, 98, 156};

// переменная для хранения суммы
// обязательно инициализируем в 0
int sum = 0;

for (int i = 0; i < 6; i++) {
  // суммируем весь массив в sum
  sum += myVals[i];
}

// разделим sum на количество элементов
// и получим среднее арифметическое!
sum /= 6;

// получилось 94!

Что касается особенностей использования for в языке C++: любая его настройка является необязательной, то есть её можно не указывать для каких-то особенных алгоритмов. Например вы не хотите создавать переменную цикла, а использовать для этого другую имеющуюся переменную. Пожалуйста! Но не забывайте, что разделители настроек (точка с запятой) обязательно должны присутствовать на своих местах, даже если настроек нет!

// есть у нас какая-то переменная
int index = 0;

for (; index < 60; index += 10) {
  // переменная index принимает значения
  // 0, 10, 20, 30, 40, 50
}

В цикле for можно сделать несколько счётчиков, несколько условий и несколько инкрементов, разделяя их при помощи оператора запятая, , смотрите пример:

// объявить i и j
// прибавлять i+1 и j+2
for (byte i = 0, j = 0; i < 10; i++, j += 2) {
  // тут i меняется от 0 до 9
  // и j меняется от 0 до 18
}

Также в цикле может вообще не быть настроек, и такой цикл можно считать вечным, замкнутым:

for (;;) {
  // выполняется вечно...
}

Использование замкнутых циклов не очень приветствуется, но иногда является очень удобным способом поймать какое-то значение, или дать программе “зависнуть” при наступлении ошибки. Но, как мы знаем, нет ничего вечного, поэтому из цикла таки можно выйти при помощи оператора break.

Цикл “for each” (New)


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

int vals[] = {10, 11, 12, 13, 14};
for (int i = 0; i < sizeof(vals); i++) {
  Serial.println(vals[i]);
}

Как это работает: мы завели цикл for с переменной-счётчиком i, который меняется от 0 до размера массива, который вы вычисляем через sizeof(). Внутри цикла мы используем счётчик как индекс массива, чтобы обратиться к каждой его ячейке как [i]. Но цикл for для работы с массивом можно записать по другому:

for (тип_данных_массива переменная : массив) {}

В нашем примере вывода это будет выглядеть так:

int vals[] = {10, 11, 12, 13, 14};
for (int val : vals) {
  Serial.println(val);
}

Как это работает: мы создаём переменную val такого же типа как массив, а также указываем имя массива через двоеточие. На каждой итерации цикла переменная val будет принимать значение ячейки массива в порядке от 0 до размера массива с шагом 1. Таким образом мы решили ту же задачу, но написали меньше кода. Важный момент: на каждой итерации цикла значение ячейки присваивается к переменной val, то есть фактически мы можем только прочитать значение (через буферную переменную). Для непосредственного доступа к элементам массива нужно создавать ссылку, то есть просто добавить оператор &

int vals[] = {10, 11, 12, 13, 14};
for (int &val : vals) {
  Serial.println(val);
  val = 0;
}

val в этом случае предоставляет полный доступ к элементу массива, то есть можно его читать/писать. Пример выше выведет значение каждого элемента, а затем обнулит его. После выполнения цикла весь массив будет забит нулями. Отсутствие индексации в цикле может быть неудобным для некоторых алгоритмов, но счётчик всегда можно добавить свой. Например забьём массив числами от 0 до 90 с шагом 10:

int vals[10];
int count = 0;
for (int &val : vals) {
  val = count * 10;
  count++;
}

И это будет всё ещё компактнее классического for.

Оператор break


Оператор break (англ. “ломать”) позволяет досрочно покинуть цикл, использовать его можно как по условию, так и как-угодно-удобно. Например давайте досрочно выйдем из цикла при достижении какого-то значения:

for (int i = 0; i < 100; i++) {
  // покинуть цикл при достижении 50
  if (i == 50) break;
}

Или вот такой абстрактный пример, покинем “вечный” цикл при нажатии на кнопку:

for (;;) {
  if (кнопка нажата) break;
}

Выход из цикла является не единственным интересным инструментом, ещё есть оператор пропуска – continue

Оператор continue


Оператор continue (англ. “продолжить”) досрочно завершает текущую итерацию цикла и переходит к следующей. Например давайте заполним массив, как делали выше, но пропустим один элемент:

// создадим массив на 100 ячеек
byte myArray[100];

for (int i = 0; i < 100; i++) {
  // если i равно 10, пропускаем
  if (i == 10) continue;
  
  // заполним все ячейки значением 25
  myArray[i] = 25;
}

Таким образом элемент под номером 10 не получит значения 25, итерация завершится до операции присваивания.

Цикл while


Цикл while (англ. “пока”), он же называется цикл с предусловием, выполняется до тех пор, пока верно указанное условие. Если условие сразу неверно, цикл даже не начнёт свою работу и будет полностью пропущен. Объявляется очень просто: ключевое слово while, далее условие в скобках, и вот уже тело цикла:

int i = 0;
while (i < 10) {
  i++;
}

Хммм, вам не кажется знакомым действие этого примера? Всё верно, это полный аналог цикла for с настройками (int i = 0; i < 10; i++) . Единственное отличие в том, что на последней итерации i примет значение 10, так как на значении 9 цикл разрешит выполнение. Ещё интересный вариант, который можно встретить на просторах чужого кода. Работает на основе того факта, что любое число кроме нуля обрабатывается логикой как true:

byte a = 5;
while (a--) {
  // выполнится 5 раз
}

Цикл while тоже удобно использовать как вечный цикл, например, ожидая наступление какого-либо события (нажатие кнопки):

// выполняется, пока не нажата кнопка
while (кнопка не нажата);

Пока условие не произойдёт, код не пойдёт дальше, застрянет на этом цикле. Как вы уже поняли, оператор if тут не нужен, нужно указывать именно логическое значение, можно даже вот так:

while (true);

Всё, вертимся здесь бесконечно! Если не предусмотрим break, конечно же… Помимо цикла с предусловием есть ещё цикл с постусловием, так называемый do while

Цикл do while


do while – “делать пока”, работа этого цикла полностью аналогична циклу while за тем исключением, что здесь условие задаётся после цикла, т.е. цикл выполнится один раз, затем проверит условие, а не наоборот. Пример:

do {
  // тело цикла
} while (условие);

Где применять? Да где угодно, по ходу написания собственных программ вы почувствуете, где удобнее использовать тот или иной цикл.

Видео


Важные страницы


  • Набор GyverKIT – большой стартовый набор Arduino моей разработки, продаётся в России
  • Каталог ссылок на дешёвые Ардуины, датчики, модули и прочие железки с AliExpress у проверенных продавцов
  • Подборка библиотек для Arduino, самых интересных и полезных, официальных и не очень
  • Полная документация по языку Ардуино, все встроенные функции и макро, все доступные типы данных
  • Сборник полезных алгоритмов для написания скетчей: структура кода, таймеры, фильтры, парсинг данных
  • Видео уроки по программированию Arduino с канала “Заметки Ардуинщика” – одни из самых подробных в рунете
  • Поддержать автора за работу над уроками
  • Обратная связь – сообщить об ошибке в уроке или предложить дополнение по тексту (alex@alexgyver.ru)
Назад Сравнения, условия и выбор
Вперёд Строки и массивы символов
guest
0 комментариев
Межтекстовые Отзывы
Посмотреть все комментарии