Инструменты пользователя

Инструменты сайта


blackbox:manual:loops

1.10 Введение в циклы

1. Понятие о цикле

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

2. Цикл с условием на входе

Такой цикл сочетает в себе и условие, и сам цикл. Поскольку условие находится на входе, называется этот цикл — цикл с условием на входе (и это, как можно догадаться один из нескольких видов циклов). Начало такого цикла описывается ключевым словом WHILE. Поскольку этот цикл — частью условие, после окончания описания условий указывается ключевое слово DO. Оно похоже на THEN, с той лишь разницей, что после THEN идёт однократное исполнение инструкций, а после DO — пока не будет выполнено условие выхода из цикла (для этого и было введено два разных слова, чтобы показать разницу, хотя возможно в будущих вариантах КП останется только THEN). В ранних версиях языков для окончания цикла WHILE использовалось ключевое слово LOOP. Но в Компонентном Паскале это слово посчитали лишним, и теперь этот цикл заканчивается по END;. Вот простой пример использования цикла WHILE:

WHILE цПерем1 < 50 DO
   INC(цПерем1)
END;

В примере на входе для переменной цПерем1 проверяется условие (должна быть меньше 50). И пока это условие выполняется производится наращивание цПерем1 на единицу через ключевое слово INC(инкремент)1). Также следует обратить внимание на то, что в цикле с условием на входе (как и в условиях) сложные выражения группируются в круглые скобки. Кроме того, шаг в таком цикле можно сделать любым. Например, 0.001. Или ещё меньше. В самом цикле переменные, от которых зависит выход из цикла — можно менять как угодно. Если из тела цикла в примере убрать инкремент переменой цПерем1 — такой цикл не закончится никогда. Иногда такие используются в программах. Но в 99,999% случаев в несистемном программировании такое зацикливание будет ошибкой программирования, и КП такие ошибки не анализирует.

3. Цикл с условием на выходе

Это второй вид цикла, в котором условия выхода могут быть сформированы каким угодно способом. Как следует из названия, проверка условия производится на выходе из цикла. И здесь есть одно важное следствие: даже если и будет произведён выход из цикла — проход по телу цикла будет гарантирован, по крайней мере — один раз. Пример такого цикла приведён ниже:

REPEAT
    цПерем1 := цПерем1 - 10;
UNTIL цПерем1 < - 100;

Цикл с условием на выходе начинается с ключевого слова REPEAT («повторить»). Выход из цикла предваряется ключевым словом UNTIL(«пока не…») — пока не будет выполнено условие выхода. Обратите внимание ещё раз — пока не будет выполнено условие из выхода! Т. е. если цикл с условием на входе требует истинности условия (ПОВТОРЯТЬ ПОКА ЕСТЬ), то цикл с условием на выходе требует отрицания на выходе (ПОВТОРЯТЬ ПОКА НЕ ЕСТЬ)! Если забыть про эту тонкость — ваш цикл не завершится никогда2).

4. Целочисленный цикл

Этот цикл выделен в отдельную структуру, так как имеет реализацию в командах процессора. Поскольку, для описания параметров целочисленного цикла используются целочисленные значения, то его очень удобно применять для обработки массивов с заранее известным размером. Почему нельзя использовать дробные числа? Да потому что не может быть элемента массива с порядковым номером «2,5». Либо «2», либо «3». Небольшой пример, показывающий использование целочисленного цикла:

Привет5.odc
MODULE КнигаПривет5;
	(* Этот пример показывает как использовать
	целочисленный цикл с массивами *)
 
	IMPORT мЛог := Log, Math;
 
	PROCEDURE Старт*;
 
		CONST
			_разм = 5;
 
		VAR
			лмцИзмер: ARRAY(_разм) OF INTEGER;
			лцИтер: INTEGER;
 
	BEGIN
		FOR лцИтер := 0 TO(_разм - 1) DO
			лмцИзмер[лцИтер] := лцИтер * 5
		END;
		FOR лцИтер := 0 TO(_разм - 1) DO
			мЛог.String('лмцИзмер['); мЛог.Int(лцИтер);
			мЛог.String(']='); мЛог.Int(лмцИзмер[лцИтер]);
			мЛог.Ln
		END;
		мЛог.Ln
	END Старт;
 
BEGIN
END КнигаПривет5.
 
(!)КнигаПривет5.Старт

В этом примере видно, что целочисленный цикл начинается с ключевого слова FOR. В качестве начала целочисленного счётчика цикла используется переменная лцИтер(локальное целое «итератор»), соответствующего типа. Эта переменная определена в секции VAR процедуры Старт (а это значит, что модуль КнигаПривет5 понятия не имеет о её существовании). Верхняя граница цикла устанавливается после ключевого слова TO в виде скорректированной константы _разм. Уменьшение этой константы на единицу меньше объяснимо тем, что индексация массива лмцИзмер начинается с нуля, а не с 1. Поэтому, последний номер элемента массива лмцИзмер будет 4, а не 5, как это объявлено в секции VAR с помощью константы _разм. Если такую корректировку верхней границы целочисленного цикла не провести, то в ходе исполнения программы будет преодолена верхняя граница массива, и программа «вылетит с ошибкой». Заканчивается целочисленный цикл FOR традиционно — ключевым словом END;3). Также необходимо обратить внимание на то, что константа начинается с символа подчёркивания (необязательно, зато наглядно).

Второй цикл FOR по своему объявлению полностью повторяет первый. Но содержание отличается. Так в первом цикле происходит заполнение массива целочисленных ячеек целочисленными значениями переменной лцИтер с шагом в 5. Во втором же цикле происходит вывод значений ячеек массива без их изменения. Если всё сделано правильно, то можно убедиться в том, что у каждой ячейки своё значение:

компилируется "КнигаПривет5"   140   0
лмцИзмер[ 0]= 0
лмцИзмер[ 1]= 5
лмцИзмер[ 2]= 10
лмцИзмер[ 3]= 15
лмцИзмер[ 4]= 20

Во втором цикле в строку скомбинирован вывод строк и целочисленных значений. При желании, можно выводить всё-что угодно и такой способ бывает удобен, чтобы оперативно посмотреть, какие значения принимают переменные в ходе выполнения программы. Остаётся один вопрос: переменная лцИтер ни в первом цикле, ни во втором — не меняется. Каким образом происходит её изменение? Правильный ответ состоит в том, что центральный процессор сам наращивает значение лцИтер. Об этом программисту беспокоиться не надо, и в целочисленном цикле: раньше или позже — но обязательно наступит завершение.

5. Целочисленный цикл с начальным произвольным шагом

Этот цикл является расширением предыдущего и почти от него не отличается. Пример представлен ниже:

Привет6.odc
MODULE КнигаПривет6;
	(* Этот пример показывает как использовать
	целочисленный цикл с массивами c произвольным
	шагом *)
 
	IMPORT мЛог := Log, Math;
 
	PROCEDURE Старт*;
 
		CONST
			_разм = 5;
 
		VAR
			лмцИзм: ARRAY(_разм) OF INTEGER;
			лцИтер: INTEGER;
 
	BEGIN
		FOR лцИтер := 0 TO(_разм - 1) BY 2 DO
			лмцИзм[лцИтер] := лцИтер * 5
		END;
		FOR лцИтер := 0 TO(_разм - 1) DO
			мЛог.String('лмцИзм['); мЛог.Int(лцИтер); мЛог.String(']=');
			мЛог.Int(лмцИзм[лцИтер]); мЛог.Ln
		END;
		мЛог.Ln
	END Старт;
 
BEGIN
END КнигаПривет6.
 
(!)КнигаПривет6.Старт

Чтобы задать шаг, необходимо после задания числа по окончанию цикла указать ключевое слово BY. В случае примера выше — это 2. С таким шагом всё нечётные ячейки массива будут пропущены, обратите внимание на вывод работы этой программы:

компилируется "КнигаПривет6"   140   0
лмцИзм[ 0]= 0
лмцИзм[ 1]= 1693148453
лмцИзм[ 2]= 10
лмцИзм[ 3]= 35749088
лмцИзм[ 4]= 20

Так и есть! В нечётных ячейках находится непонятно что! В ходе выполнения цикла значения им не присваивались. Отсюда следует правило: перед использованием числовых массивов их очень желательно обнулять.

6. Безусловный цикл

И этот цикл не зря называется так по экстремистски. Он действительно безусловный. Бывают такие программы, которые запускаются вместе с включением компьютера и завершают своё выполнение за одно мгновение до выключения4). В таких программах просто не нужен выход из цикла. А если всё-таки наступает условие, по которому надо бесконечный цикл прервать (и при этом нужно избежать тех инструкций, что идут далее) — происходит такой же безусловный выход, не терпящий возражений. Код ниже:

LOOP
    IF цПерем1 > 100 THEN
        EXIT
    END;
    IF цКоманда = _выход THEN
        EXIT
    END;
    INC(цПерем1)
END;

Безусловный цикл объявляется ключевым словом LOOP. Внутри него выполняются любые действия. Как только будет выполнено условие (цПерем1 > 1000), выход из цикла будет выполнен. Во втором условии выполняется сравнение целочисленной переменной цКоманда и если целое-команда будет иметь значение _выход, произойдёт выход из безусловного цикла. Веток, предусматривающих выход из цикла может быть множество. Хотелось бы обратить внимание ещё раз, что получить завешивание программы в таких конструкциях очень легко, и надо предусматривать возможность принудительного прерывания таких циклов (как во втором условии).

7. Заключение

Итак, циклы бывают трёх видов:

  1. С условием на входе, условием на выходе (универсальный)
  2. Целочисленный и целочисленный с произвольным шагом (для обработки массивов, может быть быстрее чем, с условием на входе/выходе)
  3. Безусловный цикл (для длительных процессов).

И важное напоминание: неаккуратное обращение с циклами (кроме целочисленного) может обернуться «зависанием» программы.

1)
В языке Си, (также в Java, python под влиянием Си) для этих целей используется оператор в форме цПерем1++ или ++цПерем1. А ещё возможна вот такая конструкция: —(цПерем1++)++. Попробуйте угадать, что это означает, и какой будет результат? (Далеко не все компиляторы такие примочки обрабатывают одинаково). А операции инкремента и декремента (INC и DEC) — это прямые команды процессора. Можете объяснить: зачем было вводить такие языковые конструкции? Подробнее можно посмотреть в Википедии.
2)
Если всё-таки вы с циклом попали в просак, и ваша программа не останавливается всё-таки ещё есть возможность остановить программу: необходимо одновременно нажать комбинацию клавиш <Ctrl+Break>. Это стандартная комбинация для остановки (или прерывания) программ и работает во многих приложениях. В BlackBox в том числе.
3)
В других языках часто используются ключевое слово Next (Visual Basic), отступы (python), или закрывающие фигурные скобки (Java). Использование Next в Visual Basic наименее оправдано, так как в цикле FOR итак используется несколько ключевых слов, зачем вводить новую сущность? Более того, если следовать логике, то NEXT(ДАЛЬШЕ) — предполагает, что цикл далее продолжится, а на самом деле с точность наоборот – это его окончание. Отступы в python заставляют писать аккуратный и правильный код, но представьте себе, если вложенных условий или циклов будет 10 уровней? Какой величины будут отступы? (а отступ в python принят 4 пробела — 40 пробелов подряд!). Фигурные скобки в Java, состоящие только из одного символа не цепляют взгляд и легко теряются на фоне множества таких же скобок. В Компонентном Паскале, как ориентированном на надёжность, принято не гнаться за краткостью (в ущерб пониманию), и не вводить излишних сущностей, если существующих хватает для описания структурной единицы.
4)
«Завершить работу за мгновение до выключения» это вовсе не метафора. Например, в песне из фильма «Семнадцать мгновений весны» есть строка: «они летят как пули у виска». Т. е. длительность мгновения определяется скоростью полёта пули. Современная пуля летит с такой скоростью, что для съёмки её полёта требуется «всего» 500 тыс. кадров в секунду. Для видеокамеры это очень много, но если переводить в частоту центрального процессора — то это будет всего 500 кГц. Чтобы понять на сколько это мало для компьютера, достаточно вспомнить, что современные центральные процессоры способны работать на частоте 5000000 кГц. Если учесть, что в ЦП может быть до 8 ядер, а конвеер ЦП может одновременно выполнять до 16 команд, то становится понятно, что за то время, что длится мгновение компьютер может успеть выполнить 960 тыс. операций. Согласитесь, это очень много. Кроме того, когда Нео в первой части фильма «Матрица» уклоняется от пуль, становится понятно, что либо Нео — читер, который воспользовался кодами, которые и замедлили работу Матрицы, либо все остальные в Зеоне были безграмотны и не умели использовать прерывания программы ).
blackbox/manual/loops.txt · Последнее изменение: 2020/10/29 07:08 (внешнее изменение)