Найти тему
Алексей Кретов

for, while, do...while. Циклы в arduino

Оглавление

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

логотип ардуино похож на знак бесконечности, от части и потому, что в каждом проекте используется бесконечный ЦИКЛ loop()
логотип ардуино похож на знак бесконечности, от части и потому, что в каждом проекте используется бесконечный ЦИКЛ loop()

1. Цикл «while»

Цикл while имеет следующий синтаксис:

while (условие) {

// телом цикла

}

Операторы в теле цикла будут выполняться до тех пор, пока условие истинно.

Например, цикл ниже выводит i, пока i < 3:

int i = 0;

while (i < 3) { // выводит 0, затем 1, затем 2

Serial.println( i );

i++;

}

Одно выполнение тела цикла называется итерацией. Цикл в примере выше совершает три итерации.

Если бы строка i++ отсутствовала в примере выше, то цикл бы повторялся (в теории) вечно. На практике, конечно, до тех пор, пока контроллер не будет выключен.

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

int i = 2;

while ((i != 1) || (i ==5)) { // когда i будет равно 1 или 5, условие станет ложным, и цикл остановится

Serial.println( i );

i--;

}

Важная особенность цикла:

While работает с глобальными переменными и для того что бы вновь начать выполнение действий внутри цикла нам необходимо привести переменные в начальное значение!

Функции на базе цикла:

1. void loop()

На данном цикле построен цикл loop() в Arduino, который фактически является while (true)

while(true){

Serial.println("Hello World");

}

2. delay (time)

Функция задержки delay () является циклом, который ничего не делает определенное время, которое пересчитывается компилятором в определенное количество тактов работы контроллера исходя из его скорости (тактовой частоты).

2. Цикл «do…while»

do..while разновидность цикла while, аналогичная подходу: "Сначала говори, а потом думай". Для таких случаев проверку условия размещают под телом цикла:

do {

// тело цикла

} while (condition);

Цикл сначала выполнит тело, а затем проверит условие condition, и пока его значение равно true, он будет выполняться снова и снова.

Например:

int i = 0;

do {

Serial.println( i );

i++;

} while (i < 3);

Такая форма синтаксиса оправдана, если вы хотите, чтобы тело цикла выполнилось хотя бы один раз, даже если условие окажется ложным. На практике чаще используется форма с предусловием: while(…) {…}.

3. Цикл «for»

Более сложный, но при этом самый распространённый цикл — цикл for. Тут ситуация похожа на подход: "сначала всё обдумай, а потом расскажи всё по пунктам". отличие от предыдущих операторов в том, что все действия указываются сразу в скобках и не требуется дополнительных проверок, следить за сбросом переменной в начальное значение, не требуется создавать множество переменных с разными именами для каждого цикла, проверять, указали ли мы шаг изменения итератора. По этим причинам цикл и получил такое широкое распространение.

Выглядит он так:

for (начало; условие; шаг) {

// ... тело цикла ...

}

Давайте разберёмся, что означает каждая часть, на примере. Цикл ниже выполняет alert(i) для i от 0 до (но не включая) 3:

for (int i = 0; i < 3; i++) { // выведет 0, затем 1, затем 2

Serial.println( i );

}

Рассмотрим конструкцию for подробней:

начало i = 0

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

условие i < 3

  • Проверяется перед каждой итерацией цикла. Если оно вычислится в false, цикл остановится. Условия могут быть и составными и содержать множество логических И и ИЛИ

шаг ++i

  • Выполняется после тела цикла на каждой итерации перед проверкой условия. возможны любые изменения шага ++i, --i, i += 2, и так далее

тело {Serial.println( i ) }

  • Выполняется снова и снова, пока условие вычисляется в true. Внутри тела цикла и проводятся все действия

В целом, алгоритм работы цикла выглядит следующим образом:

Выполнить *начало*

→ (Если *условие* == true → Выполнить *тело*, Выполнить *шаг*)

→ (Если *условие* == true → Выполнить *тело*, Выполнить *шаг*)

→ (Если *условие* == true → Выполнить *тело*, Выполнить *шаг*)

→ ...

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

Вот в точности то, что происходит в нашем случае:

// for (int i = 0; i < 3; i++)Serial.ptintln(i);

// Выполнить начало

inti = 0;

// Если условие == true → Выполнить тело, Выполнить шаг

if (i < 3) { Serial.ptintln(i) ; i++ }

// Если условие == true → Выполнить тело, Выполнить шаг

if (i < 3) { Serial.ptintln(i) ; i++ }

// Если условие == true → Выполнить тело, Выполнить шаг

if (i < 3) { Serial.ptintln(i) ); i++ }

// ...конец, выходим из цикла потому что теперь i == 3

Можно и не создавать локальную переменную, а использовать глобальную, как в стандартном примере servo -> sweep

Пременную в циклах принято называть i, как сокращение от слова iterration!

Встроенное объявление переменной

В примере переменная счётчика i была объявлена прямо в цикле. Это так называемое «встроенное» объявление переменной. Такие переменные существуют только внутри цикла.

for (int i = 0; i < 3; i++) {

Serial.ptintln(i) ; // 0, 1, 2

}

Serial.ptintln(i) ; // ошибка, нет такой переменной

Вместо объявления новой переменной мы можем использовать уже существующую:

int i = 0;

for (i = 0; i < 3; i++) { // используем существующую переменную

Serial.ptintln(i) ; // 0, 1, 2

}

Serial.ptintln(i) ; // 3, переменная доступна, т.к. была объявлена снаружи цикла

Пропуск частей «for»

Любая часть for может быть пропущена.

Для примера, мы можем пропустить начало если нам ничего не нужно делать перед стартом цикла.

Вот так:

int i = 0; // мы уже имеем объявленную i с присвоенным значением

for (; i < 3; i++) { // нет необходимости в "начале"

Serial.ptintln(i); // 0, 1, 2

}

Можно убрать и шаг:

inti = 0;

for (; i < 3;) {

Serial.ptintln(i++ );

}

Это сделает цикл аналогичным while (i < 3).

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

for (;;) {

// будет выполняться вечно

}

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

4. Прерывание цикла: «break»

Обычно цикл завершается при вычислении условия в false.

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

Например, следующий код подсчитывает сумму вводимых чисел до тех пор, пока посетитель их вводит, а затем – выдаёт:

int sum = 0;

while (true) {

int value = analogRead(A0);

if (!value) break; //(*)

sum += value;

}

Serial.ptintln(sum);

Директива break в строке (*) полностью прекращает выполнение цикла и передаёт управление на строку за его телом, то есть на Serial.ptintln.

5. Переход к следующей итерации: continue

Директива continue – «облегчённая версия» break. При её выполнении цикл не прерывается, а переходит к следующей итерации (если условие все ещё равно true).

Её используют, если понятно, что на текущем повторе цикла делать больше нечего.

Например, цикл ниже использует continue, чтобы выводить только нечётные значения:

for (int i = 0; i < 10; i++) {

// если true, пропустить оставшуюся часть тела цикла

if (i % 2 == 0) continue;

Serial.ptintln(i); // 1, затем 3, 5, 7, 9

}

Для чётных значений i, директива continue прекращает выполнение тела цикла и передаёт управление на следующую итерацию for (со следующим числом). Таким образом Serial.ptintln(i) вызывается только для нечётных значений. Фигурные скобки {} допустимо не указывать для случаев, когда после оператора стоит только одно действие.

Директива continue позволяет избегать вложенности

Цикл, который обрабатывает только нечётные значения, мог бы выглядеть так:

for (int i = 0; i < 10; i++) {

if (i % 2) {

Serial.ptintln(i);

}

}

С технической точки зрения он полностью идентичен. Действительно, вместо continue можно просто завернуть действия в блок if.

Однако мы получили дополнительный уровень вложенности фигурных скобок. Если код внутри if более длинный, то это ухудшает читаемость, в отличие от варианта с continue.

ИТОГИ

Мы рассмотрели 3 вида циклов:

  • while – Проверяет условие перед каждой итерацией.
  • do..while – Проверяет условие после каждой итерации.
  • for (;;) – Проверяет условие перед каждой итерацией, есть возможность задать дополнительные настройки.

Чтобы организовать бесконечный цикл, используют конструкцию while (true). При этом он, как и любой другой цикл, может быть прерван директивой break.

Если на данной итерации цикла делать больше ничего не надо, но полностью прекращать цикл не следует – используют директиву continue.