Цикл
Существуют 4 конструкции цикла
1. While...WEnd
2. Do...Until
3. For...To...Next
4. For...In...Next
While...WEnd и Do...Until почти одинаковы. Разница только в том что первый проверяет условие перед входом в цикл, второй после первого шага цикла.
Чаще всего While...WEnd и Do...Until делаются в виде бесконечного цикла, а по множественным условиям внутри цикла задаётся выход из него.
; Пример бесконечного цикла While...WEnd
While 1
If MsgBox(4, 'Сообщение', 'Выйти из цикла') = 6 Then ExitLoop
WEnd
; Пример бесконечного цикла Do...Until
Do
If MsgBox(4, 'Сообщение', 'Выйти из цикла') = 6 Then ExitLoop
Until 0
Особенность For...To...Next - возможность задать пределы цикла. Если первые 2 могут бесконечно работать, то For...To...Next совершает заданное количество шагов. Это полезно для обработки массивов, которые всегда имеют определённый размер, конкретное количество ячеек. В цикле обрабатывается каждая ячейка массива.
В цикле можно задать собственный счётчик итераций. Это часто необходимо в случаях перезаполнения массива валидными данными, отсеивая не валидные. Например:
#include <Array.au3>
Local $array[5] = [4, 'помидор', 'огурец', 'морковка', 'капуста'] ; Создаём массив
_ArrayDisplay($array, 'До цикла') ; Смотрим массив
$n = 0 ; Инициализируем счётчик
For $i = 1 To $array[0]
If StringInStr($array[$i], 'к') Then ; Если ячейка массива содержит буквы "к", тогда
$n += 1 ; Увеличиваем счётчик
$array[$n] = $array[$i] ; Записываем текущий элемент массива в ячейку определяемую счётчиком
EndIf
Next
$array[0] = $n ; Обновляем ячейку определяющую число элементов массива
ReDim $array[$n + 1] ; Обновляем размерность массива
_ArrayDisplay($array, 'После цикла') ; Смотрим массив
Если необходимо оставить исходный массив без изменения, то нужно создать второй массив того же размера и заполнять его. В качестве размера берётся размер исходного массива.
Local $array_dst[$array[0] + 1]
Циклы While...WEnd и Do...Until также можно сделать со счётчиком. Например:
; While...WEnd
Local $test
$i = 0
While $i <= 5
$test &= $i & @LF
$i += 1
WEnd
MsgBox(0, 'Сообщение', $test)
; Do...Until
Local $test = ''
$i = 0
Do
$test &= $i & @LF
$i += 1
Until $i > 5
MsgBox(0, 'Сообщение', $test)
Цикл For...To...Next тоже можно сделать бесконечным влияя на счётчик, но этот случай не практичный.
$n = 0
For $i = 1 To 5
$i -= 1
$n += 1
If $n = 10 Then ExitLoop
Next
MsgBox(0, 'Сообщение', '$n = ' & $n)
Чаще всего влияние на счётчик цикла For...To...Next является ошибкой программиста, из-за чего результат получается непредсказуемый (испорченный результат или зависание в цикле).
; Неправильно
$n = 0
$d = 0
For $i = 1 To 5
$n += 1
If $n = 1000 Then ExitLoop
For $i = 0 To 2
$d += 1
Next
Next
MsgBox(0, 'Сообщение', '$n = ' & $n & @LF & '$d = ' & $d)
; Правильно
$n = 0
$d = 0
For $i = 1 To 5
$n += 1
If $n = 1000 Then ExitLoop
For $j = 0 To 2 ; Имя счётчика $j отличается от $i
$d += 1
Next
Next
MsgBox(0, 'Сообщение', '$n = ' & $n & @LF & '$d = ' & $d)
Причина возникновения ошибки в следующем: Вложенный цикл заново инициализирует счётчик приравнивая его к 0. При выходе из цикла счётчик всегда на единицу больше, чем его верхний заказной предел. В данном случае если верхний предел итерации 2, то счётчик при завершении цикла равен 3. Далее родительский цикл увеличенает счётчик на 1 и он равен 4. Каждый последующий шаг цикла обнуляет счётчик и к началу шага родительского цикла делает его равным 4. Для завершения цикла счётчик должен быть равным 5, но этого никогда не происходит. Цикл зависает в бесконечности. Для выхода из цикла добавлен дополнительный независимый счётчик показывающий, что количество итераций превышает максимальное допустимое 5 * 3 = 15, которое равно произведению диапазонов цикла.
Цикл достаточно быстро выполняет операции, используя всю мощь процессора. Мой двух-ядерный процессор делает 4 миллиона итераций в секунду. Поэтому если вы мониторите какую либо величину, то следует делать паузу, чтобы ограничить число шагов цикла в течении определённого промежутка времени. Например:
While FileExists('C:\1.txt')
Sleep(1000)
WEnd
Здесь осуществляется проверка существования файла, но запрос осуществляется 1 раз в секунду. Пауза ни в коем случае не должна восприниматься как обязательный атрибут цикла. В большинстве случаев важно максимально быстро выполнить. Но задача мониторинга - длительное время следить за объектом, который возможно изменится в течении часа и проверять объект слишком часто не имеет смысла. Задача программиста определить, на сколько быстро требуется реагировать на изменение и выбрать оптимальную задержку в цикле.
При обработке данных (массива), где итогом является получить результат как можно быстрее, вставка паузы ничего полезного не приносит.
Коротко о For...In...Next. Цикл в основном предназначен для работы с объектами. Для обработки массива часто не удобен из за отсутствия индекса обрабатываемого элемента. Но если индекс не нужен, то этот цикл даже проще, в том что не требуется получать/определять размер массива. Это всё происходит за ширмой самим интерпретатором, который вычисляет размер и возвращает в объявленную переменную значения элементов массива начиная от 0 на первом шаге цикла, заканчивая последним, на последнем шаге цикла.
Local $array[4] = ['помидор', 'огурец', 'капуста', 'морковка']
For $item In $array
MsgBox(0, 'Сообщение', $item)
Next
Оптимизация цикла
Пример 1
Оптимизация задачи с помощью исключения условия из тела цикла. Допустим нужно в зависимости от триггера сделать соответствующее действие. Если проверка триггера делается внутри цикла, то она выполняется столько раз, сколько итераций выполняет цикл. Если исключить проверку из тела цикла, а вместо этого использовать два цикла, каждый из которых выполняется в зависимости от условия, то это может выполнится гораздо бстрее
$Trg = 1
; Не оптимизированно
For $i = 1 To 100000
If $Trg Then
$s = 1
Else
$s = 2
EndIf
Next
; Оптимизированно
If $Trg Then
For $i = 1 To 100000
$s = 1
Next
Else
For $i = 1 To 100000
$s = 2
Next
EndIf
Но здесь не следует слишком очаровываться, так как в большинстве случаев ресурсоёмкость исполняемого блока под условием в разы больше, чем ресурсоёмкость проверки триггера. В связи с этим практический эффект реже доступен для использования, чем хотелось бы.
Пример 2
Оптимизация задачи с помощью двух последовательных циклов. В качестве примера используем предыдущий вариант обработки массива.
#include <Array.au3>
Local $array[5] = [4, 'помидор', 'огурец', 'капуста', 'морковка'] ; Создаём массив
_ArrayDisplay($array, 'До цикла') ; Смотрим массив
$n = $array[0]
For $i = 1 To $array[0]
If Not StringInStr($array[$i], 'о') Then ; Если ячейка массива НЕ содержит буквы "о", тогда
$n = $i ; Записываем счётчик для следующего диапазона обработки
ExitLoop
EndIf
Next
For $i = $n + 1 To $array[0] ; $n + 1 - позиция, от которой требуется проверка данных
If StringInStr($array[$i], 'о') Then ; Если ячейка массива содержит буквы "о", тогда
$array[$n] = $array[$i] ; Записываем текущий элемент массива в ячейку определяемую счётчиком
$n += 1 ; Увеличиваем счётчик
EndIf
Next
$array[0] = $n - 1 ; Обновляем ячейку определяющую число элементов массива
ReDim $array[$n] ; Обновляем размерность массива
_ArrayDisplay($array, 'После цикла') ; Смотрим массив
Представим, что необходимо обрабатывать массив из нескольких тысяч ячеек, а по некоторым заведомо известным условиям начало сдвига ячеек в массиве приходится на любую часть массива в равной степени, т.е. в среднем как если бы это был центр массива. Если начать переписывать ячейки именно со средины, то это на значительный процент увеличило бы скорость алгоритма. Итак, в примере выше первый цикл делает поиск ячейки, от который необходимо производить сдвиг, а второй цикл собственно производит перезапись ячеек со сдвигом.
Пример 3
Вывести за пределы цикла все ресурсоёмкие функции, значения которых являются константами во время выполнения цикла.
; Не оптимизированно
For $i = 1 To $array[0]
If Number(IniRead($Ini, 'section', 'key', '0')) > $array[$i] Then $array[$i] -= Number(IniRead($Ini, 'section', 'key', '0'))
If BitAND(GUICtrlRead($iCh1),$GUI_CHECKED) Then $array[$i] -= 15
Next
; Оптимизированно
$value1 = Number(IniRead($Ini, 'section', 'key', '0'))
$value2 = BitAND(GUICtrlRead($iCh1),$GUI_CHECKED)
For $i = 1 To $array[0]
If $value1 > $array[$i] Then $array[$i] -= $value1
If $value2 Then $array[$i] -= 15
Next
В этом примере не оптимизированный вариант выполняет количество считываний ini-файла 18 раз (9 * 2), а считывание и проверка чекбокса 9 раз. Оптимизированный вариант получает значения вне цикла, а внутри цикла только манипулирует значениями содержащимися в переменных.
|