Циклы в языке 1С: разбор операторов с примерами кода
Содержание
В языке 1С:Предприятие три оператора цикла: Для, Пока и Для Каждого. Выбор между ними зависит от одного вопроса — знаете ли вы заранее, сколько раз нужно повторить действие.
Самое частое заблуждение новичков — что у Для можно задать шаг, например «считать через два» или «идти в обратном порядке». Нельзя. Оператор Для всегда прибавляет к счётчику единицу и движется только вперёд. Любой другой шаг, обратный проход или повтор по условию делаются через Пока.
Разберём каждый оператор на коде, который реально встречается в модулях: перебор табличной части, чтение выборки запроса, обход соответствия. Заодно покажу, где циклы тормозят базу и как их отлаживать в конфигураторе.
Оператор «Для»: повтор по счётчику
Для применяют, когда число повторений известно заранее или вычисляется до входа в цикл. Счётчик автоматически увеличивается на 1 после каждой итерации.
Для НомерСтроки = 1 По 10 Цикл
Сообщить(СтрШаблон("Строка № %1", НомерСтроки));
КонецЦикла;
Границы (1 и 10) — это обычные выражения. Их можно задавать переменными:
Для Индекс = 0 По Товары.ВГраница() Цикл
ТекущийТовар = Товары[Индекс];
Сообщить(СтрШаблон("%1: %2", Индекс, ТекущийТовар));
КонецЦикла;
Здесь ВГраница() возвращает индекс последнего элемента массива, то есть Количество() - 1. Для массива из 5 элементов это 4, а перебор идёт по индексам 0, 1, 2, 3, 4. Если массив пустой, ВГраница() вернёт -1, и тело цикла не выполнится ни разу.
Верхняя граница вычисляется один раз при входе в цикл. Если внутри тела изменить Товары, число итераций от этого не поменяется — а вот индекс может выскочить за пределы коллекции. Поэтому удалять элементы прямым циклом Для нельзя, для этого есть приём с Пока (ниже).
Счётчик Для удобен, когда в теле нужен именно номер позиции: пронумеровать строки, собрать пары «предыдущий — текущий», обратиться к двум коллекциям по одному индексу.
Второй типовой сценарий — накопить итог. Переменную-накопитель обязательно задают до цикла, иначе первое же сложение упрётся в неопределённое значение:
СуммаДокумента = 0;
Для Индекс = 0 По Товары.ВГраница() Цикл
СуммаДокумента = СуммаДокумента + Товары[Индекс].Сумма;
КонецЦикла;
Счётчик внутри тела менять технически можно — это обычная переменная, — но полагаться на это не стоит: после каждой итерации платформа всё равно прибавит к нему 1, и логика становится непредсказуемой. Нужен нестандартный ход — берите Пока.
Оператор «Пока»: повтор по условию
Пока повторяет тело, пока условие истинно. Число повторов заранее неизвестно — оно зависит от данных. Условие проверяется перед каждой итерацией: если оно ложно сразу, тело не выполнится ни разу.
Самый частый случай — чтение выборки результата запроса:
Выборка = Результат.Выбрать();
Пока Выборка.Следующий() Цикл
Сообщить(Выборка.Номенклатура);
КонецЦикла;
Метод Следующий() переходит к очередной записи и возвращает Истина, пока записи есть. Когда они закончились — Ложь, и цикл завершается.
Через Пока делают и то, что недоступно Для: шаг больше единицы, обратный проход, сложное условие выхода.
// обратный проход с шагом 1
Индекс = Товары.ВГраница();
Пока Индекс >= 0 Цикл
ОбработатьСтроку(Товары[Индекс]);
Индекс = Индекс - 1;
КонецЦикла;
Именно так безопасно удаляют элементы из коллекции — идти с конца, тогда оставшиеся индексы не смещаются:
Индекс = Товары.Количество() - 1;
Пока Индекс >= 0 Цикл
Если Товары[Индекс].Количество = 0 Тогда
Товары.Удалить(Индекс);
КонецЕсли;
Индекс = Индекс - 1;
КонецЦикла;
Главная опасность Пока — зацикливание. Если забыть изменить переменную, от которой зависит условие (здесь — Индекс = Индекс - 1), цикл никогда не завершится, и 1С «повиснет». При отладке такой код приходится прерывать принудительно.
Оператор «Для Каждого»: обход коллекции
Для Каждого перебирает все элементы коллекции по очереди, без счётчика и границ. Это самый читаемый вариант, когда нужны именно элементы, а не их номера.
Для Каждого Строка Из Товары Цикл
Сообщить(СтрШаблон("%1 — %2 шт.", Строка.Номенклатура, Строка.Количество));
КонецЦикла;
Так обходят массивы, табличные части, таблицы значений, списки значений. Для соответствия на каждой итерации выдаётся объект «ключ и значение»:
Для Каждого КлючЗначение Из ОстаткиПоСкладам Цикл
Сообщить(СтрШаблон("%1: %2", КлючЗначение.Ключ, КлючЗначение.Значение));
КонецЦикла;
Переменная цикла (Строка, КлючЗначение) объявляется прямо в заголовке — отдельно её задавать не нужно.
Так же обходят и список значений, и фиксированный массив, и результат запроса, выбранный методом Выбрать(). У структуры на каждой итерации тоже доступны Ключ и Значение:
Для Каждого КлючЗначение Из ПараметрыОтчёта Цикл
Если КлючЗначение.Значение = Неопределено Тогда
Сообщить(СтрШаблон("Не заполнен параметр «%1»", КлючЗначение.Ключ));
КонецЕсли;
КонецЦикла;
Внутри Для Каждого нельзя изменять саму коллекцию — добавлять или удалять элементы. Платформа обходит её по внутреннему указателю, и изменение состава вызовет ошибку либо пропуск элементов. Нужно менять состав — переходите на Пока с индексом, как показано выше.
Если задача — найти один элемент, не перебирайте всё подряд. Для таблиц значений есть Найти() и НайтиСтроки(), для массивов — Найти(). Цикл нужен тогда, когда обработать надо каждый элемент.
Прервать и Продолжить: управление ходом цикла
Две команды меняют обычный ход любого из трёх циклов.
Прервать немедленно выходит из цикла. Остаток итераций не выполняется. Типичный сценарий — нашли нужное и дальше искать смысла нет:
Для Каждого Строка Из Товары Цикл
Если Строка.Номенклатура = ИскомыйТовар Тогда
НайденнаяСтрока = Строка;
Прервать;
КонецЕсли;
КонецЦикла;
Продолжить пропускает оставшуюся часть тела и переходит к следующей итерации. Удобно, чтобы отсеять лишние элементы без вложенного Если на весь блок:
Для Каждого Строка Из Товары Цикл
Если Строка.Количество = 0 Тогда
Продолжить;
КонецЕсли;
// сюда попадут только строки с ненулевым количеством
ОбработатьСтроку(Строка);
КонецЦикла;
Во вложенных циклах Прервать выходит только из того цикла, в котором стоит, — внешний продолжает работать. Это и используют, когда надо сопоставить две коллекции: внешний цикл идёт по одной таблице, внутренний ищет пару во второй и прерывается, как только нашёл.
Для Каждого СтрокаЗаказа Из Заказ.Товары Цикл
Для Каждого СтрокаПрайса Из Прайс.Товары Цикл
Если СтрокаПрайса.Номенклатура = СтрокаЗаказа.Номенклатура Тогда
СтрокаЗаказа.Цена = СтрокаПрайса.Цена;
Прервать; // выходим только из внутреннего цикла
КонецЕсли;
КонецЦикла;
КонецЦикла;
Чтобы прервать сразу оба цикла, заводят флаг-переменную и проверяют её во внешнем цикле, либо выносят вложенный цикл в отдельную функцию и выходят из неё через Возврат. Вложенный перебор двух таблиц, кстати, — тоже кандидат на замену запросом: соединение таблиц в запросе почти всегда быстрее двойного цикла.
Обе команды работают только внутри цикла. Прервать или Продолжить, поставленные вне цикла, — это синтаксическая ошибка, модуль не скомпилируется.
Какой цикл выбрать
Операторы не взаимозаменяемы — у каждого своя область. Короткая шпаргалка:
| Оператор | Когда применять | Шаг и порядок |
|---|---|---|
Для | Число повторов известно; нужен номер позиции | Только +1, вперёд |
Пока | Число повторов зависит от условия; чтение выборки; обратный проход; удаление из коллекции | Любой, задаёте сами |
Для Каждого | Перебрать все элементы коллекции по очереди | По порядку хранения, состав менять нельзя |
Для. Счётчик сам растёт на 1, границы задаёте выражениями.Для Каждого — самый читаемый вариант. Но не меняйте состав коллекции внутри.Пока. Следите, чтобы условие рано или поздно стало ложным.Пока с индексом, двигаясь от последнего элемента к первому.Как не затормозить базу циклом
Сам по себе цикл быстрый. Проблемы начинаются от того, что делают внутри него. Три приёма, которые отличают рабочий код от того, что кладёт базу под нагрузкой.
1. Никаких запросов в цикле. Это антипаттерн номер один. Код ниже выполнит отдельный запрос к базе на каждую строку заказа — на тысяче строк это тысяча обращений к СУБД:
// так делать нельзя
Для Каждого Строка Из Заказы Цикл
Запрос.УстановитьПараметр("Контрагент", Строка.Контрагент);
Результат = Запрос.Выполнить(); // запрос внутри цикла
КонецЦикла;
Правильно — получить все данные одним запросом до цикла, отобрав нужное через условие В (&СписокКонтрагентов) или соединение таблиц, а в цикле только разложить готовый результат.
2. Не обращайтесь к базе через точку внутри цикла. Выражение вида Строка.Номенклатура.ЕдиницаИзмерения.Наименование на каждой итерации — это скрытое обращение к базе данных. На больших коллекциях соберите реквизиты одним запросом заранее.
3. Кэшируйте Количество() в условии Пока. В цикле Для граница считается один раз, а вот в Пока Индекс < Массив.Количество() метод Количество() вызывается на каждой итерации. Если коллекция не меняется, сохраните значение в переменную до цикла.
Выносите за пределы цикла всё, что не зависит от итерации: создание объектов, чтение констант, подготовку запроса. Внутри оставляйте только то, что действительно меняется от шага к шагу.
Ошибки в циклах и как их отладить
Разберём сбои, которые не лечатся правилами из разделов выше, потому что проявляются только на конкретных данных.
- 🔴 Выход за границу коллекции. Классика — перебор
Для Индекс = 1 По Массив.Количество(), а внутри обращениеМассив[Индекс]. Индексы массива начинаются с 0, поэтому на последнем шаге индекс выйдет за предел. Решение: либоДля Индекс = 0 По Массив.ВГраница(), либо обращениеМассив[Индекс - 1]. - 🔴 Коллекция изменена во время обхода. Удаление или добавление элементов внутри
Для Каждогодаёт ошибку выполнения. Переходите наПокас движением от конца. - 🔴 Бесконечный цикл. В
Покаусловие никогда не становится ложным. Чаще всего забыли менять переменную-счётчик или перепутали знак сравнения. - 🔴 Накопление в неинициализированной переменной. Итог считается в переменной, которой не присвоили
0до цикла. Результат — ошибка «Значение не определено» на первом же сложении.
Чтобы увидеть, что происходит внутри цикла, пользуйтесь отладчиком конфигуратора:
- Поставьте точку останова на строке внутри цикла — клавиша
F9или щелчок на левом поле редактора. - Запустите
1С:Предприятиев режиме отладки (F5из конфигуратора) и повторите действие, вызывающее код. - Когда выполнение остановится, идите по строкам:
F11— «шагнуть в» (зайти в вызываемую процедуру),F10— «шагнуть через» (выполнить вызов целиком),Shift+F11— «шагнуть из». - Текущие значения переменных смотрите в окне Табло, а разовое вычисление — через
Shift+F9(«Вычислить выражение»).
Если цикл делает тысячи итераций, а сбой — на одной конкретной, не щёлкайте F11 вручную. Поставьте условную точку останова: в её свойствах задайте условие, например Индекс = 500, — отладчик остановится только на нужном шаге.
Можно ли в цикле `Для` задать шаг больше единицы?
Для всегда прибавляет к счётчику 1. Нужен другой шаг или обратный порядок — используйте Пока и меняйте переменную сами (например, Индекс = Индекс - 1 или Индекс = Индекс + 2).Чем `Прервать` отличается от `Возврат`?
Прервать выходит только из текущего цикла, а выполнение процедуры продолжается дальше. Возврат завершает всю процедуру или функцию целиком, независимо от того, сколько циклов вложено.Как выйти сразу из двух вложенных циклов?
Прервать выходит только из своего цикла. Заведите флаг-переменную и проверяйте её во внешнем цикле, либо вынесите вложенный цикл в отдельную функцию и выйдите через Возврат.Почему при удалении строк в `Для Каждого` часть строк пропускается?
Пока, двигаясь от последнего индекса к нулевому.Как перебрать соответствие по ключам и значениям?
Для Каждого КлючЗначение Из Соответствие Цикл. На каждой итерации доступны КлючЗначение.Ключ и КлючЗначение.Значение.Есть ли в 1С цикл «сделать хотя бы раз» (do-while)?
Пока Истина Цикл с выходом через Прервать по нужному условию внутри тела.