Компонентный Паскаль/Введение в циклы

Материал из Викиучебника — открытых книг для открытого мира

Понятие о цикле[править]

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

Цикл с условием на входе[править]

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

WHILE p1<50 DO
	INC(p1)
END;

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

Цикл с условием на выходе[править]

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

REPEAT
	p1 := p1 - 10;
UNTIL p1 < - 100;

Цикл с условием на выходе начинается с ключевого слова REPEAT("повторить"). Выход из цикла предваряется ключевым словом UNTIL("пока не...") -- пока не будет выполнено условие выхода. Обратите внимание ещё раз -- пока не будет выполнено условие из выхода! Т. е. если цикл с условием на входе требует истинности условия, то цикл с условием на выходе требует отрицания на выходе! Если забыть про эту тонкость -- ваш цикл не завершится никогда[2]

Целочисленный цикл[править]

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

Hello5.odc

MODULE TestHello05;
(* Этот пример показывает как использовать
целочисленный цикл с массивами *)
	IMPORT  Log, Math;
	
	PROCEDURE Start*;
	CONST
	  Num = 5;
	VAR
		a: ARRAY (Num) OF INTEGER;
		i: INTEGER;
	BEGIN
		FOR i:=0 TO (Num-1) DO
			a[i]:=i*5
		END;
		FOR i:=0 TO (Num-1) DO
			Log.String('a[');Log.Int(i);Log.String(']='); Log.Int(a[i]); Log.Ln
		END;
		Log.Ln
	END Start;	
BEGIN
END TestHello05.

В этом примере видно, что целочисленный цикл начинается с ключевого слова FOR. В качестве начала целочисленного счётчика цикла используется переменная "i", соответствующего типа. Эта переменная определена в секции VAR процедуры "Start" (а это значит, что модуль TestHello05 понятия не имеет о её существовании). Верхняя граница цикла устанавливается после ключевого слова TO в виде скорректированной константы "Num". Уменьшение это константы на единицу объяснимо тем, что индексация массива "а" начинается с нуля, а не с "1". Поэтому, последний номер элемента массива "а" будет "4", а не "5", как это объявлено в секции VAR с помощью константы "Num". Если такую корректировку верхней границы целочисленного цикла не провести, то в ходе исполнения программы будет преодолена верхняя граница массива, и программа "вылетит с ошибкой". Заканчивается целочисленный цикл FOR традиционно -- ключевым словом "END;"[3].

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

компилируется "TestHello05"   140   0
старый модуль TestHello05 выгружен
a[ 0]= 0
a[ 1]= 5
a[ 2]= 10
a[ 3]= 15
a[ 4]= 20

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

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

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

Hello6.odc

MODULE TestHello06;
(* Этот пример показывает как использовать
целочисленный цикл с массивами c произвольным
шагом *)

	IMPORT  Log, Math;
	
	PROCEDURE Start*;
	CONST
	  Num = 5;
	VAR
		a: ARRAY (Num) OF INTEGER;
		i: INTEGER;
	BEGIN
		FOR i:=0 TO (Num-1) BY 2 DO
			a[i]:=i*5
		END;
		FOR i:=0 TO (Num-1) DO
			Log.String('a[');Log.Int(i);Log.String(']='); Log.Int(a[i]); Log.Ln
		END;
		Log.Ln
	END Start;	
BEGIN
END TestHello06.

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

компилируется "TestHello06"   140   0
старый модуль TestHello06 выгружен
a[ 0]= 0
a[ 1]= 576
a[ 2]= 10
a[ 3]= 2680452
a[ 4]= 20

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

Безусловный цикл[править]

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

LOOP
	IF p1>100 THEN
		EXIT
	END;
	IF GetCommand()='Exit' THEN
		EXIT
	END;
	INC(p1)
END;

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

Заключение[править]

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

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

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

Примечания[править]

  1. В языке Си, (также в Java, python под влиянием Си) для этих целей используется оператор в форме"p1++" или "++p1". А ещё возможна вот такая конструкция: "--(р1++)++". Попробуйте угадать, что это означает, и какой будет результат? (Далеко не все компиляторы такие примочки обрабатывают одинаково) А операции инкремента и декремента (INC и DEC) -- это прямые команды процессора. Можете объяснить: зачем было вводить такие языковые конструкции? Подробнее можно посмотреть здесь
  2. Если всё-таки вы с циклом попали в просак, и ваша программа не останавливается всё-таки ещё есть возможность остановить программу: необходимо одновременно нажать комбинацию клавиш <Ctrl>+<Break>. Это стандартная комбинация для остановки (или прерывания) программ и работает во многих приложениях. В BlackBox Red в том числе.
  3. В других языках часто используются ключевое слово "Next" (Visual Basic), отступы (python), или закрывающие фигурные скобки (Java). Использование Next в Visual Basic наименее оправдано, так как в цикле FOR итак используется несколько ключевых слов, зачем вводить новую сущность. Отступы в python заставляют писать аккуратный и правильный код, но представьте себе, если вложенных условий или циклов будет 10 уровней? Какой величины будут отступы? (а отступ в python принят 4 пробела -- 40 пробелов подряд!). Фигурные скобки в Java, состоящие только из одного символа "не цепляют" взгляд и легко теряются на фоне множества таких же скобок. В Компонентном Паскале, как ориентированном на надёжность, принято не гнаться за краткостью (в ущерб пониманию), и не вводить излишних сущностей, если существующих хватает для описания структурной единицы.
  4. "Завершить работы за мгновение до выключения" это вовсе не метафора. Например,в фильме "Семнадцать мгновений весны" есть строка: "они летят как пули у виска". Т.е. длительность мгновения определяется скоростью полёта пули. Современная пуля летит с такой скоростью, что для съёмки её полёта требуется "всего" 500 тыс. кадров в секунду. Для видеокамеры это очень много, но если переводить в частоту центрального процессора -- то это будет всего 500 кГц. Чтобы понять на сколько это мало для компьютера, достаточно вспомнить, что современные центральные процессоры способны работать на частоте 5`000`000 кГц. Если учесть, что в ЦП может быть до 8 ядер, а конвеер ЦП может одновременно выполнять до 16 команд, то становится понятно, что за то время, что длится мгновение компьютер может успеть выполнить 960 тыс. операций. Согласитесь, это очень много. Кроме того, когда Нео в первой части "Матрицы" уклоняется от пуль, становится понятно, что либо Нео -- читер, который воспользовался отладчиком, который и замедлил работу Матрицы, либо все остальные в Зеоне не умели отлаживать программы ).