Эх, раз, ещё раз, Ещё много-много раз!
Цыганская песня о циклах
Терпенье и труд всё перетрут.
Пословица
Очень часто в программах встречаются ситуации, когда одни и те же действия нужно повторить многократно. Для таких случаев в языке Delphiприпасены аж три оператора циклов - for, whileи repeat.
Примеры циклов мы можем найти и в повседневной жизни: времена года (циклически сменяют друг друга весна - лето - осень - зима), время суток (утро - день - вечер - ночь), фазы Луны, вращение планет вокруг Солнца и Солнечной системы вокруг центра Галактики, спортивные состязания, учебный год, режим дня, дыхание и кровообращение, часы, биоритмы активности человека - можно даже утверждать, что уникальны именно неповторяющиеся события, а не циклические.
Оператор FOR
Цикл forвстречается в программах чаще других. Его используют, когда число повторений оператора заранее известно.
Цикл forзаписывается в одной из форм:
forпеременная:= начальное значение to конечное значение doоператор;
forпеременная:= начальное значение downtoконечное значение doоператор;
Оператор (или составной оператор) называется телом цикла. Число повторений (они также называются итерациями) определяется переменной (управляющей переменной, счётчиком цикла, параметром цикла), которая автоматически увеличивается или уменьшается при каждом выполнении тела цикла.
Переменная цикла - это локальная переменная порядкового типа (обычно целого). Она не должна изменяться в теле цикла (да вы и не сможете этого сделать - Delphiне позволит!), но её значение может использоваться в выражениях внутри цикла.
Первая форма действует так:
1.Переменной цикла присваивается начальное значение.
2.Текущее значение переменной цикла сравнивается с её конечным значением. Если оно меньше или равно конечному значению, то выполняется тело цикла - оператор(ы). Если больше, то выполнение цикла заканчивается и управление переходит к следующему за структурой forоператору.
3.Если цикл не завершён в п.2, то значение переменной цикла увеличивается на 1 и цикл возвращается к п.2.
Понятно, что выполнение цикла forрано или поздно закончится. Более того, он может вообще не выполняться ни разу, если начальное значение сразу больше конечного, либо будет выполнен только 1 раз, если начальное значение равно конечному.
Важно помнить, что после завершения цикла значение переменной цикла не определено, то есть её нельзя использовать в выражениях!
Вторая форма действует аналогично, за исключением того, что в п.3 значение переменной цикла уменьшается на 1. Оператор forс downtoне будет выполнен ни разу, если начальное значение переменной цикла меньше конечного.
Рассмотрим простой цикл, считающий до пяти:
procedure 5;
var i: integer;
begin
for i:= 1 to 5 do
//^ Заголовок цикла
print(inttostr(i)
); //^ Тело цикла
end;
Здесь i- переменная цикла (их обычно именуют буквами i, j, к, l, m, n).
Начальное значение переменной цикла равно 1, конечное 5.
Таким образом, цикл будет выполнен 5 раз, при этом на экране будут напечатаны числа 1 2 3 4 5
Оператор FOR... IN
В последних версиях Delphiпоявилась новая разновидность оператора цикла For. Оператор For... inперебирает все элементы коллекции - это могут быть символы строки, элементы массивов и записей.
for переменная in коллекция do оператор;
В языке C# подобный оператор называется foreach.
Переменная цикла должна быть объявлена в том же блоке, что и оператор цикла, а её тип должен быть совместим с типом элементов в коллекции.
Изменять значение переменной цикла внутри цикла нельзя!
Установите на форме текстовое поле Edit1, щёлкните по кнопке Button1 и напишите код в методе Button1Click:
procedure TForm2.Button1Click(Sender: TObject); var
s: string; ch: char; begin
s:= Edit1.Text; for ch in s do
ListBox1.Items.Add(ch);
end;
Здесь мы считываем строку, которую ввёл пользователь в текстовое поле, в цикле for .inперебираем все символы этой строки и добавляем их в список
И для этого нам даже не нужно знать длину строки - оператор for...inумеет работать самостоятельно!
Очень часто в программах приходится перебирать и элементы массива. Дополним нашу программу целочисленным массивом arr, в который поместим восемь первых простых чисел:
var
Form2: TForm2;
arr: array[1..8] of integer = (2,3, 5, 7, 11, 13, 17, 19);
implementation
{$R *.dfm}
procedure TForm2.Button1Click(Sender:
TObject);
var
s: string;
ch: char;
i: integer;
begin
s:= Editl.Text;
if s <> 'Editl' then
for ch in s do
ListBoxl.Items.Add(ch)
else
for i in arr do
end;
В метод Button1Clickдобавим проверку строки в текстовом поле. Если пользователь не ввёл строку, то мы печатаем элементы массива (Рис. У8.3), в противном случае - строку.
Оператор WHILE
Любовь - кольцо, а у кольца Начала нет и нет конца
Песня о зацикливании
Цикл while называется также циклом с предусловием и имеет следующий вид:
while выражение do оператор;
Выражение должно быть логического типа (boolean) и называется также условием выполнения цикла.
Работает он так:
1. Вычисляется логическое выражение.
2. Если оно ложно (значение равно FALSE), то выполнение цикла заканчивается.
3. Если оно истинно (значение равно TRUE), то выполняется оператор и управление передаётся в п.1.
Поскольку условие проверяется до выполнения тела цикла, то операторы могут быть вообще не выполнены ни одного раза - если выражение с самого начала ложно.
Как мы видим, цикл заканчивается, когда условие станет ложным. А почему изменяется значение выражения? - Да потому, что изменились значения переменных, входящих в это условие. И это может произойти только в теле цикла. Отсюда грустный вывод: цикл whileможет вообще никогда не закончиться, и программа зациклится навечно, что очень часто и бывает!
Пример 1. Найдём все числа Фибоначчи, которые не больше некоторого заданного числа n.
Результаты вычислений мы будем выводить в список lstProtocol задавать нужное нам число от 2 до 99999 в компоненте speNUM а понуждать программу к этому - с помощью кнопки sbtGo:
Вводим в однострочныйй редактор число и попадаем в процедуру обработки:
procedure TfrmMain.sbtGoClick(Sender: TObject);
var n: integer;
begin
n:= speNum.Value; fibo(n); end;
Здесь вызывается процедура, которая и проводит нужные нам расчёты:
//РЕШИТЬ ЗАДАЧУ procedure fibo(n: integer);
var f,f1,f2: integer; begin f:=0; f1:=1; f2:=1;
frmMain.lstProtokol.Clear; while f2 <= n do begin
frmMain.lstProtokol.Items.add(inttostr(f2)); f2:= f1 + f; f:=f1; f1:=f2;
end;
end;
Чтобы понять, как действует в этом примере оператор while, уделим немного времени числам Фибоначчи (а эти числа очень интересны!). Нетрудно догадаться, что их открыл Фибоначчи (он же Леонардо Пизанский), средневековый математик, внимательно наблюдавший за размножением кроликов.
Первые два числа равняются 1:
f1:=1;
f2:=1;
а все последующие равны сумме двух предыдущих, то есть третье число 1 + 1 = 2, четвёртое 1 + 2= 3, пятое 2 + 3 = 5 и так далее, до бесконечности:
f2:= f1+f;
f:=f1;
f1:=f2;
А цикл while f2 <= n do только проверяет, не достигло ли очередное число Фибоначчи заданного нами предела.
Исходный код программы находится в папке fibonacci.
Оператор REPEAT
Цикл repeatназывается также циклом с постусловием и имеет следующий вид:
repeat
оператор untilвыражение;
Выражение (условие выполнения цикла) должно быть логического типа.
Оператор repeatдействует почти так же, как while, но с одним важным отличием: условие продолжение цикла проверяется после выполнения оператора. Это значит, что цикл обязательно будет исполнен по крайней мере 1 раз, даже если логическое выражение истинно с самого начала.
По записи цикла видно, что зарезервированные слова repeatи untilиграют роль операторных скобок, поэтому в теле цикла можно просто перечислять операторы - использовать составной оператор нет необходимости (но это и не запрещено). По этой же причине перед untilточку с запятой разрешается не ставить.
Работает оператор repeat так:
- Выполняется оператор в теле цикла.
- Вычисляется логическое выражение. Если оно ложно (значение равно FALSE), то управление передаётся в п.1. Если оно истинно (значение равно TRUE), то выполнение цикла заканчивается.
Теперь мы можем найти и второе различие циклов whileи repeat: первый заканчивается, когда условие не выполняется (значение выражения становится равным FALSE), второй - когда условие выполняется (значение выражения становится равным TRUE).
Пример 2. Найдём все числа Фибоначчи, которые не больше некоторого заданного числа n.
Внешне («интерфейсно») программа ничем не отличается от первого примера, но теперь мы найдём числа Фибоначчи с помощью оператора repeat:
//РЕШИТЬ ЗАДАЧУ
procedure fibo(n: integer); var f,f1,f2: integer; begin f:=0; f1:=1; f2:=1;
frmMain.lstProtokol.Clear;
repeat
frmMain.lstProtokol.Items.add(inttostr(f2)); f2:= f1 + f; f:=f1; f1:=f2; until f2 >= n;
end;
Легко заметить, что нам пришлось изменить только небольшую часть кода: заменить один оператор цикла другим и «инвертировать» условие продолжения цикла. Как говорится, лёгким движением руки whileпревращается в элегантный repeat!
Исходный код программы находится в папке fibonacci2.
Вложенные циклы
Довольно часто внутри одного цикла нужно выполнить другой цикл, например, при поиске нужного элемента в двумерном массиве. Хорошим примером вложенных циклов может быть русская матрёшка: каждая меньшая по размерам кукла целиком посещается внутри большей. При известном мастерстве удаётся создать глубоко вложенные матрёшки:
for . . . //начало первого цикла
begin
for . . . //начало второго цикла begin
for . . . //начало третьего цикла
begin
end; //конец третьего цикла
end; //конец второго цикла
Как и всегда, отступами выделяйте отдельные циклы! Вложенных циклов может быть любое количество и это могут быть не только циклы for, но и whileи repeatв любых сочетаниях.
Пример. В своё время мне пришлось на городской олимпиаде по математике решать такую задачу: Подсчитать, сколько раз пятёрка входит в представление чисел от 1 до 1000 в виде произведения простых чисел: 2, 3, 5, 7, 11,. (например, 24 = 2 * 2 * 2 * 3; 25 = 5 * 5). Мы не на олимпиаде, поэтому пусть задачу решает компьютер, а мы составим для него простенькую программу:
//РЕШИТЬ ЗАДАЧУ
procedure TfrmMain.sbtGoClick(Sender: TObject);
var i, n, n5: integer; begin n5:=0;
for i:= 1 to 1000 do begin //проверяем все заданные числа n:= i;
while TRUE do begin
if n mod 5 = 0 then begin n:= n div 5; inc(n5); end
else break; end; end;
frmMain.lstProtokol.Items.add(inttostr(n5));
end;
Здесь мы легко найдём цикл for, в котором перебираются все заданные числа, и вложенный в него бесконечный цикл while, который и подсчитывает их делители. Если число делится нацело на 5, то увеличиваем счётчик на 1, иначе переходим к проверке следующего числа. Олимпиадный «подвох» этой задачи заключается в том, что полученное после деления на 5 число опять может быть кратно 5, то есть его опять нужно проверить. Если это обстоятельство не учесть, то число пятёрок можно было бы подсчитать мгновенно: 1000 : 5 = 200. Таким образом, в первой тысяче чисел ровно 200 делятся на 5. Ещё 40 делятся на 25 (5*5), 8 - на 125 (5*5*5) и одно - на 625 (5*5*5*5). Зная ответ, и задачу легко решить, а я на олимпиаде намаялся