#7 Зацикливаемся

Вступление

Здраствуйте, те кто читает эту рассылку!
Имеем счастье доложить - программа минимум уже выполнена! Читата из первого сообщения в гостевой книге нашего сайта гласит: " Я знаю много рассылок по программированию. И большенство из них умирали не доходя до циклов... " Этот выпуск посвящён циклам. Так что назначайте теперь программу максимум :)
Написать её можно на мыло или на сайте http://www.ibp7.narod.ru - там же можно взять предыдущие выпуски рассылки.
В прошлый раз было голосование - результаты в том же разделе.

Теория

Сегодня мы поговорим о циклах. Что это такое? Давайте представим, что вам необходимо вывести на экран слово ПРИВЕТ! двадцать раз. Писать: writeLn ('ПРИВЕТ'); 20 раз !!! А если нужно сто раз? А если заранее неизвестное число раз?? Как быть? Вот тут и нужны циклы.
Цикл - кусок кода, который повторяется определённое число раз (бесконость - это тоже определённое число раз!).

Цикл FOR

Этот цикл наиболее часто используется в программах ввиду его чрезвычайной удобности. Итак знакомтесь, цикл for!
Давайте рассмотрим его использованние на примере с выводом слова привет на экран. Да давайте оговоримся, что будем опускать стандартные куски программы типа Program, var, begin, end. И ещё одно соглашение об именах переменных: обычно именами i и j дают переменным целого типа и их объявление мы тоже будем опускать. Итак пример:

for i := 1 to 20 do
     writeLn ('Привет!');

Цикл записывается так for переменная_цикла := начальное_значение to конечное_значение do операторы;

В нашем примере мы взяли в качестве переменной цикла взята переменная i, начальное значение 1, конечное - 20. Что это значит? А это значит, что цикл будет выполняться для i = 1, 2, 3, 4, 5 ... 19, 20. Т.е. 20 раз. Каждый раз мы прибавляем к i еденицу. При этом начальное и конечное значение переменной цикла может задаваться численным выражением (например for i := 1 to 3*65 do ....) или выражением с переменной (например for i := X + Y to X * Y / Z + 45 *j do ....). При этом эти значения вычисляются компилятором один раз перед выполнением цикла. Например, результат такой программы:

Program   Test;

var  
   i : integer;
   x : integer;
begin  
     x := 10;
     for i := 0 to х do  
     begin  
       writeLn (i, ' ', x);
       x := x + 2
     end  
end.

Будет таким :
0 10
1 12
2 14
3 16
4 18
5 20
6 22
7 24
8 26
9 28
10 30

Как видите предел i не поменялся, хотя х мы меняем! Использую цикл for нужно соблюдать следующее правило: не нужно менять переменную цикла (т.е. ту переменную, по которой идёт цикл, у нас это была i) внутри него.
Есть ещё один альтернативный вариант цикла for, который используется, когда считать нужно не "снизу вверх" а "сверху вниз". Например мы хотим изменять i не от 1 до 20, а наоборот от 20 до 1. Тогда наш цикл примет вид:

for i := 20 downto 1 do
     writeLn ('Привет!');

Теперь i = 20, 19, ... 2, 1. А так всё аннологично. Основное неудобство состоит в том, что мы не можем задавать закон изменения переменной цикла. Однако и это не так страшно, как кажется на первый взгляд.

Цикл WHILE

Цикл номер два: while! Этот цикл называется циклом с предусловием. Записывается он так:
while условие do оператор;
Такой цикл выполняется пока условие истинно. И прекращается в противном случае. Например такой цикл:

while 1 do
	writeLn ('Привет!');

Приведёт к "зависанию" компьютера, т.к. выражение 1 всегда истинно (не могут же быть числа ложными). выйдти из этого цикла можно нажатием клавиши Ctrl+Break (Ctrl+C) - стандартный выход из ДОС программ. Проверка истистинности условия проводится как и в операторе if.
Давайте покажем, как оператором While можно заменить for:

i := 1;
x := 10;
while i  <=  x do
begin
   writeLn (i, ' ', x);
   x := x + 1;
   i := i + 1
end;

Попробуйте этот код... ну как эффект не тот? :) Давайте разберёмся в чём дело. А вся проблемма в том, что условие (максимальное значение) для цикла for считается зарание один раз заранее! А у нас оно всё время меняется. Ок. Теперь посмотрите результат программы: она останавливается при i = 32 757. Давайте посмотрим ещё один вариант:

i := 1;
x := 10;
while i  <>  x do
begin
   writeLn (i, ' ', x);
   x := x + 1;
   i := i + 1
end;

Запустите и подождите немного.... СТОП! А откуда там отрицательные числа?? Ведь мы прибавляем каждый раз по 1 и 1, т.е. i и х растут ???

Давайте разберёмся по подробнее. Что из себя представляет числовая прямая? Это окружность максимального радиуса, т.е. радиуса бесконечность. Такая окружность вырождается в прямую. Однако для целых чисел (типа integer) эта "бесконечность" известна - 32767 (помните выпуск Типы данных?). Счечик i можно представить, как точку, которая движется по этой окружности. Когда i доходит до максимума, то она становится отрицательной, т.е. -32767!!

А помните предыдущий пример, где всё останавливалось при x = 32 767. Почему так вышло? Прибавляя к х = 32767 один что мы получаем: x + 1 = -32767 !!!! А условие цикла у нас стоит i <= x т.е. когда х = 32 767 i = 32 757, далее х + 1 = -32 767 i + 1 = 32 758 т.е. условие i <= x не выполняется! И поэтому мы выходим из цикла!
Поэтому аккуратнее с максимальным значение integer, оно не такое большое, как может показаться!

Теперь напишем нормальный вариант цикла for через цикл while:

i := 1;
x := 10;
Temp := x;
while i  <= Temp do
begin
   writeLn (i, ' ', x);
   x := x + 1;
   i := i + 1
end;

Здесь мы заранее посчиталь верхнюю границу цикла в переменную temp и тем самым всё работает правильно.

Ну и напоследок...

Ну вот мы подошли к последнему циклу. Он называется цикл с постусловием, цикл: repeat-until ! Этот цикл в общем и целом анологичен while, но есть два отличия. Вот как он записывается: repeat оператор until условие;

Итак его отличия от while:

  1. самое важное: проверка условия совершается после выполнения оператора. Таким образом этот цикл обязательно выполнится хотя бы один раз. В то время как while, может и не выполняться ни разу.
  2. не самое важное, но очень нужно запомнить, что критерием прекращения цикла является тот случай, когда условие истинно, а если оно ложно, то цикл продолжится! В то время как в while абсолютно противоположная ситуация.

Давайте же рассмотрим какой-нить пример:

i := 10;
repeat
     writeLn (i);
	 i := i - 1
until i = 0;

Этот цикл выполняется пока i не равно нулю. Т.е. для значений i = 10, 9, 8 ..... 2, 1. Обратите внимание, что мы не используем операторы begin-end т.к. этот цикл доспукает использовать в своём теле сколько хочешь операторов в отличие от while и for.

Вот написал и вспомнил о том, что не сказал, что такое тело цикла :( Вот дырявая башка! Итак тело цикла - это та последовательность операторов, которая выполниется. Т.е. например все операторы между repeat и until - это и есть тело цикла.

Программа

Итак сегодня мы продолжим изучать Отладчик и напишем ещё одну программу.
Наша новая программа - факториал! Что такое факториал? По определению факториал числа n (обозначается n! ) n! = 1 * 2 * 3 *....* (n-1) * n - т.е. перемножение чисел от 1 до n. Итак вот программа:

Program Factorial;

uses CRT;

var
   n, i : integer;
   Result : longint;
begin
     ClrScr;

     Write ('Какое значение? ');
     ReadLn (n);

     Result := n;

     for i := 0 to n do
     begin
         Result := Result * i
     end;	

     writeLn ('Результат: ', Result);
	 ReadLn
end.

Давайте же помотрим результат!!!! Запускаем вводим число... ЧТО ЭТО ЗА НАФИГ??? Почему 0? ... запускаем вновь ... опять 0! Досада. Видимо в программу залезла логическая ошибка! Ну вот теперь мы познаем всю мощь отладчика!
Итак строки:

begin
     ClrScr;

     Write ('Какое значение? ');
     ReadLn (n);

Выглядят довольно понятно и ошибка явно не в них. Так зачем же нам тратить наше драгоценное время на их пошаговое исполнение? Давайте сразу же перейдем к следующей строке. Наведите на неё курсор и нажмите Ctrl+F8 (меню Debug - > Add breakpoint) Она подсветится красным цветом. Что же такого чудесного мы сделали? А вот что: мы добавили breakpoint (по-русски: брякпоинт, бряк:) - точку остановки. Когда исполнение программы дойдёт до этой строчки, то мы сразуже перейдём в режим по-шагового исполнения. Увидите список бряков можно Debug -> Breakpoints.
Итак бряк есть, теперь нам нужно всё время смотреть, что же у нас в переменной Result. Этого можно добится двумя путями.

  1. Добавить внутрь цикла строчку типа: writeLn (Result);
  2. Использовать отладчик.

Второе на мой взгляд несколько предпочтительнее :) Давайте сделаем вот что: выберем Debug -> Add Watch или Ctrl+F7 - в появившемся диалоге укажем имя нашей переменной: Result (в поле Watch expression) Появится окно watches (если не появилось, то Debug -> Watch). В нём вы увидите значение переменной Result. Так же можно для практики добавить туда и переменные i и n. Так тепрь мы можем наблюдать, что же там внутри и как меняются переменные.

Ну вот, теперь запускаем программу (Ctrl+F9), вводим для примера 5 и сразу же оказываемся внутри программы.
Заметьте, что у нас такие значения: Result = 0, i = 0, n = 5. Давим на F8 (не забыли что это такое:) Result изменился с 0 на 5 (делаем вывод: оператор := работает правильно, ошибка не в нём :))))

Входим в цикл (давим F8)... Заметьте, что я специально добавил в цикл конструкцию begin-end (в неё нет надобности). Я это сделал, что бы при отладке внутри цикла у нас подсветка двигалась :) а иначе она просто будет висеть на строчке: Result := Result * i; и создаётся впечатление остановки программы.

Итак проходим первый этап цикла (i = 0) опа!! Вот оно! Result сразу же изменился на 0 ! Давайте посмотрим, что же будет дальше... а дальше он так и останется 0. Можно заметить, что мы умножаем Result на i, но ведь в начале цикла i = 0 т.е. мы умножаем на 0 и поэтому Result всегда 0! УРА! Нашли ошибку! Прервём исполнение программы (Ctrl+F2). Для устранения ошибки мы сделаем следующее: в цикле i должно изменяться от 1 до n. Я надеюсь, что переписать программу для вас не составит труда. Итак исправим, уберём бряк (на той строчке Ctrl+F8)

Запустим программу... ВОТ ЧЁРТ! Теперь Result не 0 но очень уж большой!!!! В чём же дело?? Давайте повторим всё заново: поставим бряк и запустим её снова... введём 5... Постойте ка а почему это у нас Result в начале равен 5?? Ведь факториал это произведение от 1 до n ??? Да вот же в чём дело! Мы вначале присваиваем Result := n; А надо так: Result := 1; Теперь всё нормально!
Давайте удалим всю отладочную информацию: Очистим все бряки (Debug -> Breakpoints . кнопка Clear All) Удалим все просмоторщики (в окне watches правая кнопка мыши из меню Clear All) и выполним программу заново.

Ещё одна мелочь, которая не сразу бросается в глаза: переменная Result у нас типа longint, однако и этого не достаточно, что бы вместить факториалы чисел больше 31! Как подсчитать факториал для любых чисел мы поговорим попозже. Обязательно поговорим!

Голосование

Итак в прошлый раз нам было интересно узнать, что вы думаете по поводу нового дизайна рассылки. Так вот только после выхода выяснилось, что в почтовой программе the Bat! некоторые места отображаются некорректно. Поэтому лучше чиать странику в Opere'e или ещё лучше в Internet Explorer'e. Соответственно дизайн рассылки некоторое время останется неизменным, а потом мы чего-нибудь придумаем получше. Кстате голосование доступно до 5 ноября по этой ссылке.

Послесловие

За сим позвольте откланятся.


[Назад] [Содержание] [Дальше]
Hosted by uCoz