6 · Циклы
Обход массива FOR EACH
Когда нужно выполнить действие с каждым элементом массива, индексы и границы не нужны: достаточно назвать массив и переменную, которая получит очередное значение. Форма `FOR EACH` выполняет тело один раз на элемент и сама следит за порядком и границами.
Форма записи
`FOR EACH` выполняет тело для элементов массива в порядке индексов. Источник указывается выражением после `IN`; массив вычисляется один раз перед первым проходом. Имя после `NEXT` необязательно, а если оно указано, должно совпадать с именем переменной цикла.
Форма с копированием элемента:
FOR EACH ⟦BYVAL⟧ <переменная> ⟦AS <тип>⟧ IN <массив>[]
⟦<инструкции>⟧
NEXT ⟦<имя переменной>⟧Форма с адресацией элемента:
FOR EACH BYREF <переменная> ⟦AS <тип>⟧ IN <массив>[]
⟦<инструкции>⟧
NEXT ⟦<имя переменной>⟧Если модификатор пропущен, действует `BYVAL`. `EACH` и `IN` зарезервированы: их нельзя использовать как имена переменных, констант, процедур, функций, типов и полей; попытка даёт MB1002.
DIM fruits[] AS STRING = ["яблоко", "банан", "апельсин"]
FOR EACH fruit IN fruits[]
PRINT fruit
NEXT fruitПрограмма выводит:
яблоко
банан
апельсинСлова EACH и IN зарезервированы
`EACH` и `IN` — полноправные ключевые слова языка, как `FOR` и `NEXT`. Их нельзя использовать как имена переменных, констант, процедур, функций, типов или полей: такая попытка отклоняется при разборе с сообщением, что ключевое слово нельзя использовать как имя.
DIM each AS INTEGER = 3Эта программа не выполняется: `each` занято языком. Правило распространяется на любое место, где ожидается имя, включая позицию выражения.
Зарезервированы ровно эти два слова, а не любые имена, содержащие те же буквы. Имена вида `in2`, `eachItem` или `inside` остаются допустимыми, потому что совпадают с ключевым словом не целиком:
DIM in2 AS INTEGER = 3
DIM eachItem AS INTEGER = in2
PRINT eachItemПрограмма выводит 3. Числовой `FOR` с `=` и `TO` продолжает работать без изменений.
Переменная цикла и тип
Цикл объявляет переменную элемента только для своего тела. После `NEXT` это имя недоступно. Если `AS` пропущено, тип переменной совпадает с типом элементов источника.
DIM values[] AS INTEGER = [10, 20, 30]
DIM total AS INTEGER
FOR EACH value IN values[]
total = total + value
NEXT
PRINT totalПрограмма выводит 60. В форме `BYVAL` тип после `AS` может принимать расширяющее преобразование каждого элемента, например `INTEGER` в `DOUBLE`. Сужающее преобразование отклоняется с MB2063.
FOR EACH value AS DOUBLE IN values[]
PRINT value / 2
NEXTВ форме `BYREF` тип после `AS`, если он указан, должен точно совпадать с типом элементов; преобразование не выполняется:
FOR EACH BYREF value AS INTEGER IN values[]
value = value + 1
NEXT valueУказание `DOUBLE` для массива `INTEGER[]` в этой форме даёт MB2063.
Копия значения в режиме BYVAL
В режиме `BYVAL` цикл создаёт новую ячейку для каждой итерации и помещает в неё копию элемента. Присваивание переменной цикла не меняет массив:
DIM values[] AS INTEGER = [1, 2, 3]
FOR EACH BYVAL value IN values[]
value = 99
NEXT
PRINT values[1], values[2], values[3]Программа выводит 1 2 3. Новая ячейка сохраняет отдельное значение и при захвате замыканием, созданным вызванной процедурой с параметром `BYREF`. Поэтому несколько сохранённых замыканий удерживают значения своих итераций, а не значение последнего прохода. Элемент `TYPE` копируется по обычным правилам Axiom: вложенные массивы, словари, множества и поля TYPE также копируются независимо. Для кортежа действует то же правило: компоненты с вложенными данными копируются независимо.
Адрес элемента в режиме BYREF
`BYREF` связывает переменную цикла с текущим элементом массива. Присваивание, вызов метода `TYPE` и передача переменной в существующий параметр `BYREF` записывают в этот элемент. Адрес привязан к массиву и координатам итерации, поэтому замыкание, полученное через вызванную функцию с параметром `BYREF`, сохраняет адрес своего элемента.
DIM grades[] AS INTEGER = [3, 4]
FOR EACH BYREF grade IN grades[]
grade = grade + 1
NEXT grade
PRINT grades[1], grades[2]Программа выводит 4 5. `BYREF AS` требует точного совпадения типа элементов с указанным типом; несоответствие даёт MB2063. Для элемента фиксированной строки сохраняется её объявленная длина: присваиваемая строка нормализуется по тем же правилам, что и обычная запись в элемент массива.
Источником может быть действительное диапазонное представление массива. Запись через переменную цикла меняет соответствующие элементы корневого массива. Изменять размеры представления нельзя; изменение формы исходного массива делает адреса итераций недействительными.
Источник, порядок и границы
Источник вычисляется ровно один раз, до первого прохода. Поэтому вызов функции в заголовке выполняется однократно, даже если массив большой:
DIM calls AS INTEGER
FUNCTION Make() AS INTEGER[]
calls = calls + 1
DIM local[] AS INTEGER = [1, 2, 3]
RETURN ARRAYCLONE(local[])
END FUNCTION
FOR EACH item IN Make()
PRINT item
NEXT
PRINT callsПрограмма выводит числа 1, 2, 3 и затем 1: функция вызвана один раз.
Обход учитывает фактические границы, включая отрицательные и нулевые. Многомерный массив обходится целиком, причём последнее измерение изменяется быстрее остальных: для массива `[1 TO 2, 1 TO 3]` порядок такой — `11, 12, 13, 21, 22, 23`.
Выделенный пустой массив даёт ноль проходов, то есть тело не выполняется ни разу. Невыделенный массив, объявленный без границ и без значения, сообщает MB3005: обходить нечего.
Обход заканчивается корректно и на самой верхней границе допустимых индексов. Массив с единственным элементом на индексе 2147483647 проходит один раз и завершается без переполнения счётчика:
DIM values[2147483647 TO 2147483647] AS INTEGER
values[2147483647] = 7
FOR EACH item IN values[]
PRINT item
NEXTПрограмма выводит 7. Продвижение счётчика выполняется только тогда, когда он ещё не достиг верхней границы, поэтому значение за пределы диапазона INTEGER не выходит.
EXIT FOR и CONTINUE FOR
`CONTINUE FOR` проходит проверку продвижения и переходит к следующему элементу. `EXIT FOR` завершает обход без дальнейшего продвижения. `RETURN` также покидает процедуру без продолжения цикла.
DIM values[] AS INTEGER = [1, 2, 3, 4, 5]
FOR EACH value IN values[]
IF value = 3 THEN
CONTINUE FOR
END IF
IF value = 5 THEN
EXIT FOR
END IF
PRINT value
NEXTПрограмма выводит 1, 2 и 4: третий элемент пропущен, а пятый завершил обход.
Изменение значений элементов разрешено. `REDIM`, `ERASE` и операции изменения размера массива, например `ARRAYPUSH`, `ARRAYPOP`, `ARRAYINSERT` и `ARRAYREMOVE`, меняют его структурную версию. На следующем продвижении цикл выдаёт MB3014, в том числе после последнего элемента. Если программа читает или записывает переменную `BYREF` после такого изменения, возникает тот же MB3014. `EXIT FOR` и `RETURN` сами по себе не выполняют эту проверку продвижения.
Диагностика
MB1002 сообщает, что `EACH` или `IN` использовано как имя. MB2016 сообщает о конфликте имени переменной цикла с переменной в той же области. MB2048 возникает, когда имя после `NEXT` не совпадает с именем переменной цикла. MB2062 сообщает, что источник не является массивом. MB2063 сообщает о недопустимом типе элемента, сужающем преобразовании в `BYVAL AS` или несовпадении точного типа в `BYREF AS`. MB3005 возникает, когда источник не выделен. MB3014 сообщает, что источник изменил форму во время обхода либо адрес `BYREF` устарел.
Запускаемые примеры
each_byval.axm показывает копирование значения. each_byref.axm показывает запись через `BYREF`. each_iteration_captures.axm проверяет отдельные ячейки и адреса в сохранённых замыканиях. each_shape_change_error.axm получает MB3014 после изменения формы. each_multidimensional.axm показывает многомерный порядок и отрицательные границы. each_exit_continue.axm проверяет `EXIT FOR` и `CONTINUE FOR`. each_integer_max_bound.axm проверяет верхнюю границу INTEGER. each_unallocated_error.axm показывает MB3005.
См. также
FOR … NEXT · CONTINUE · EXIT FOR и EXIT DO · Одномерные массивы · Многомерные массивы