Сигнализация с журналом: двести показаний за ночь лежат в массиве, и надо узнать, было ли хоть одно превышение порога. Мой первый цикл честно дотопал по всем двумстам ячейкам даже тогда, когда тревожное значение стояло третьим, — печать выдавала верный номер, но меня коробило: ответ был известен через три шага, а код упорно продолжал считать. Соседний скетч — фильтр показаний с датчика — сваливался в делении на ноль на пустых ячейках журнала, и лечился я это многострочными условиями в заголовке цикла, от которых через неделю рябило в глазах.
Обе беды — из одной серии: циклы for и while мы освоили как «проверь всё подряд», а жизнь чаще ставит задачи «найди первое» и «работай только с чистым». Для первой в C++ есть break — выход из цикла, как только ответ получен; для второй — continue, пропуск витка с сохранением самого цикла. Это не оптимизация ради тактов: на 16 МГц двести итераций стоят копейки. Это способ записать замысел так, чтобы код читался как задача.
План: break и пара с флагом «найдено», continue и порядок «фильтр — работа», вечный while с честным выходом, и коварная ловушка вложенных циклов, на которой я потерял вечер. Циклы мы разбирали в уроке про for и while, журнал — в уроке про массивы: здесь те же герои, но сценарии у них взрослые.
break: ответ найден — можно не дотапывать
Механика в одну фразу: break; немедленно прекращает текущий цикл, и управление переходит на первую строку после его закрывающей скобки. Классическое применение — поиск первого совпадения: for идёт по журналу, if замечает превышение — печатаем номер, ставим флаг и выходим. Витки после находки не выполняются вовсе: не «проскочены», а просто не существуют.
Разумный вопрос: а чем break лучше просто вынести условие находки в заголовок — for (int i = 0; i < 8 && !naiden; i++)? Технически — ничем, оба варианта честные. Разница в читаемости: когда проверок в теле две-три, break ближе к месту события — читающий видит выход там, где он случается, а не в заголовке, до которого ещё дотопать глазами. Я держу правило: одно условие выхода — в заголовок, две и больше — в тело с break.
Флаг naiden при этом не украшение, а вторая половина решения. После цикла есть ровно два исхода — «нашли, вот номер» и «дошли до конца без находки», — и пара «break + флаг» различает их без всяких хитростей: если флаг не поднялся, значит цикл отработал целиком и совпадений нет. Печать обоих исходов — привычка, которая экономит часы: отчёт «проверены все восемь, превышений нет» — это результат, а не признание в неудаче.
const int PIN_LED = 12;const int POROG = 500;int zhurnal[8] = {0, 0, 0, 540, 0, 0, 0, 0}; // 0 — ячейка пустая void setup() { pinMode(PIN_LED, OUTPUT); Serial.begin(9600);} void loop() { bool naiden = false; int nomer = -1; for (int i = 0; i < 8; i++) { if (zhurnal[i] > POROG) { naiden = true; nomer = i; break; // ответ есть — остальные ячейки не смотрим } } digitalWrite(PIN_LED, naiden ? HIGH : LOW); if (naiden) { Serial.print("trevoga v yacheike "); Serial.println(nomer); } else { Serial.println("tishina: provereny vse 8"); } delay(1000);}Разбор по строкам. Флаг и номер объявлены до цикла — после break они должны остаться живыми, поэтому внутри for их не объявить. Условие «превышает порог» стоит в лоб, без составных конструкций: сначала найдём первое попадание, тонкости — потом. break лежит внутри if, и это нормально: if не перехватывает выход, break относится к ближайшему циклу — к нашему for. Светодиод на выходе отражает итог, а печать в ветке «тишина» честно говорит, что были проверены все восемь ячеек.
Обратите внимание на номер: он стартует с −1, а не с нуля, потому что ноль — легальный индекс первой ячейки. Число-сигнал «ещё не нашли» выбирают вне рабочего диапазона: −1 для индексов, 0 для длин, пустая строка для текста. Мелочь, но именно такие мелочи отличают отчёт от догадки.
continue и вечный while
continue; закрывает текущий виток и прыгает к следующему: сам цикл жив, а строки ниже continue в этом витке не исполняются. Первое применение — фильтр. Журнал после старта наполовину состоит из нулей, датчик выдаёт мусорные выбросы, о которых мы говорили в разборе медианы, — все они отсеиваются одной строкой вверху тела, до всяких расчётов. Второе применение — страховка: если делитель берётся из ячейки, continue до строки деления стоит дешевле, чем разбор мусорного результата после.
Порядок «фильтр — работа» я считаю правилом, а не вкусовщиной: сначала отбросить пустое и грязное, потом считать дорогое. Отчёт от этого только выигрывает — если печатать каждую отброшенную ячейку, сразу видно, что фильтр съедает ровно мусор и ничего лишнего. Число с условием: на журнале из шестисот ячеек разница между «считать по всем» и «считать по чистым» — не в тактах, а в том, чей мусор попал в среднее.
Вечный цикл — третья сцена. while (true) без выхода внутри — плата выглядит зависшей: Serial замолчал, кнопки перестали опрашиваться, и мы уже знаем это правило по уроку про циклы. Честный шаблон — while с двумя выходами внутри: основной if (uslovie) break; и лимит-подстраховка if (limit-- == 0) break;. Сам loop() при этом остаётся единственным честно бесконечным циклом скетча — у него роль такая. Грабля же, на которой я просидел вечер, сформулирована в одной строке: break выпрыгивает только из своего цикла. В двойном поиске break во внутреннем for оставляет внешний крутиться до конца — и тот продолжает перебирать строки, хотя ответ давно готов. Лечение: флаг в заголовке внешнего цикла — while (!naiden && ...) — или вынос поиска в функцию с return, который покидает все циклы разом.
Одна тонкость с continue в for, на которой я терял минуту: после continue выполняется и шаг i++ — заголовок цикла не забыт, инкремент случится. А вот в while с ручным счётчиком continue прыгает на проверку условия мимо строки i = i + 1, и цикл замирает на одной ячейке навсегда. Правило простое: в while инкремент ставьте первой строкой тела либо переходите на for, где шаг гарантирован грамматикой. И помните про вложенность: break выкидывает только внутренний цикл, внешний продолжает крутиться — для выхода из обоих нужен флаг рядом с условием.
| Инструмент | Что делает | Где берут |
|---|---|---|
| break | выходит из ближайшего цикла целиком | первое совпадение, аварийный выход, лимит подстраховки |
| continue | закрывает виток, цикл продолжает жить | фильтр пустых и мусорных значений до расчётов |
| Флаг в заголовке | while (!naiden && i < n) держит оба цикла | двойные проходы, где break хватает только внутренний |
| return в функции | выходит из всех циклов функции разом | поиск, оформленный функцией: нашёл — вернул номер |
В макетке все три инструмента работают в одном цикле: continue отсеивает пустые ячейки с отчётом в логе, break останавливает проход на первой находке, флаг naiden превращает два исхода в два печатных вердикта. Тот же скелет стоит за чтением Serial до конца строки, за ожиданием события в неблокирующих схемах и за любым фильтром, который чистит поток значений до расчётов.
И граница метода, как я её вижу: break и continue — инструменты смысла, а не скорости. Если без них условие в заголовке цикла превращается в монстра из трёх скобок — переносите сложность в тело с break; если наоборот, цикл с чистым заголовком читается лучше — оставляйте условие в заголовке. Спор «break или флаг» решается одинаково каждый раз: выбирайте тот вариант, который объяснит коллеге вслух без указки пальцем. И проверяйте себя на живом журнале: два нажатия записи, одно нажатие поиска — три строки в логе покажут весь маршрут цикла по ячейкам, включая пропуски и остановку.
- 1. Что делает break внутри цикла?
- 2. Цикл for дошёл до i = 2 и встретил continue. Что произойдёт?
- 3. В журнале 200 ячеек, тревожное значение стоит в ячейке 3. Сколько витков сделает поисковый цикл с break?
- 4. Двойной цикл, break стоит во внутреннем. Что сделает внешний?
- 5. while (true) без выхода внутри loop() — что почувствует пользователь?
- break — выход из ближайшего цикла при первом совпадении: задача «найти первое» читается прямо по коду.
- Пара «break + флаг naiden» различает два исхода: нашли с номером и проверили всё без находки.
- continue закрывает виток, цикл живёт: пустые и мусорные значения отсеиваются до тяжёлых расчётов.
- break выпрыгивает только из своего цикла: во вложенных — флаг в заголовке внешнего или return внутри функции.
- while (true) живёт только с выходами внутри: условие события и лимит-подстраховка; сам loop() — единственный честно вечный цикл.
Частые вопросы по теме урока
- Что делает break внутри цикла?
- break прекращает текущий цикл целиком, управление уходит на первую строку после его скобки. Следующий виток — это работа continue. немедленно выходит из ближайшего цикла
- Цикл for дошёл до i = 2 и встретил continue. Что произойдёт?
- continue закрывает текущий виток и прыгает к следующему значению счётчика. Сам цикл живёт до конца своего заголовка. строки ниже не выполнятся, следующий виток пойдёт с i = 3
- В журнале 200 ячеек, тревожное значение стоит в ячейке 3. Сколько витков сделает поисковый цикл с break?
- Витки с индексами 0, 1, 2 не совпали, на индексе 3 цикл нашёл значение и вышел: четыре витка вместо двухсот. Но смысл выигрыша — в замысле, а не в тактах. 4
- Двойной цикл, break стоит во внутреннем. Что сделает внешний?
- break выпрыгивает только из ближайшего цикла. Для выхода из вложенного нужен флаг в заголовке внешнего или return внутри функции. продолжит крутиться до конца — break его не касается
- while (true) без выхода внутри loop() — что почувствует пользователь?
- Управление не вернётся в loop до конца цикла. Честный шаблон — два выхода внутри: условие события и лимит-подстраховка. плата выглядит зависшей: Serial молчит, кнопки не опрашиваются