Назад | Вперед

Тема 6. Команды  управления последовательностью выполнения программы

Безусловный оператор перехода
Условный оператор (условные вычисления)
Структурные операторы Паскаля
Повторяющиеся вычисления (операторы цикла)

Безусловный оператор перехода

Обычно операторы  в  программе выполняются в том порядке,  в каком они записаны. Оператор перехода прерывает естественный порядок выполнения программы и указывает,  что дальнейшее выполнение должно продолжаться, начиная с оператора, помеченного меткой, указанной в операторе перехода. Пример записи оператора перехода: go to N: .


Наверх

Условный оператор (условные вычисления)

С помощью одного оператора присваивания можно создавать достаточно сложные расчетные программы, однако реализовать абсолютное большинство алгоритмов, просто последовательно выполняя операторы присваивания, невозможно. Посто­янно приходится изменять порядок выполнения последовательности вычислений в зависимости от определенных условий. Эти условия записываются в виде логичес­ких выражений и всегда принимают одно из двух значений — true или false (истинно или ложно). При этом происходит разветвление программы — выполнение в даль­нейшем может продолжиться с разных операторов.
Синтаксис условного оператора примерно одинаков во всех языках программиро­вания — он представляет собой конструкцию:
если условие истинно
то выполнить оператор-1
иначе выполнить оператор-2
После ключевого слова IF (если) следует условие, и если оно истинно, то выполняется оператор или блок операторов, следующих за ключевым словом THEN (то), если же оно ложно, то выполняется оператор или блок операторов, следующих за ключе­вым словом ELSE (иначе).
Синтаксис условного оператора


Бейсик

Паскаль

Си++

IF условие THEN
Оператор-1
ELSE оператор-2
END IF

if условие then
оператор-1
else
оператор-2;

if (условие)
оператор-1
else
оператор-2;

Примеры.
Бейсик:

  IF A <> 0 THEN
    А = О
  ELSE
    А = -1
  END IF
Паскаль:
  if  а <> 0  then а := О
  else a := -1;
Си++:
  if( а <> 0 ) а = О
    else а - -1;
Вторую часть условного оператора, выполняющуюся в случае, если условие ложно, всегда можно опускать.
Бейсик:
 IF х < 0 THEN
      у = х / 2
      х = 1
 END IF
Паскаль:
  if х < 0 then
   begin
    y := х / 2;
    x := 1;
   end
Си++:
  if( x < 0 )
   {
    у = x / 2;
    x = 1;
   };


Наверх

Структурные операторы Паскаля

Структурные операторы строятся из специальных зарезервированных слов, логических выражений и других операторов. Каждый такой оператор явно или неявно содержит одну или несколько логических проверок.

Оператор if...then...

Оператор if..then... называется условным оператором и имеет вид
  if expression1 then statewntl;
где выражение expression1 является логическим. 
Логическое выражение принимает одни из двух (возможных значений — True (истина) или False (ложь), Часто в роли логического выражения выступает какое-то условие, которое может выполняться либо нет. В первом случае его значение — «истина», и во втором — «ложъ». Программирование логических выражений мы будем разбирать позже. Если логическое выражение expressionl принимает значение «истина, то выполняется оператор statement1. В противном случае выполняться будет оператор, следующий за данным логическим оператором.
Следует отметить, что, согласно формальным правилам языка, в условном опе­раторе после then допускается применение только одного оператора. Но в прак­тике программирования чаще возникают ситуации, когда при выполнении условия в логическом выражении expression1 следует выполнить нескольку операторов языка. Решается эта проблема, как уже были сказано, применением составного оператора. Операторы if...then... можно вкладывать друг в друга, так как конструкция
  if expression2 then  statement2;
также является оператором и может заместить оператор statement1:
  if expression1 then if expression2 then statement2;
 Пример условного оператора:
  if Centigrade = 0 then Write('Температура замерзания   воды');

Оператор if...then...else...

Этот оператор является полной версией условного оператора и имеет вид
  if expression then statement1 else statement2;
Выполняется данный оператор следующим образом: если выражение expres­sion принимает значение «истина», то управление передается на оператор sta­tement1, если же нет, то на оператор statement2. Приведем ошибочный вариант данного оператора:
  if expression ther statementl else; statement2;
Здесь первая точка с запятой завершает оператор if...then...e1se, не выполняя никаких действий в случае else,  а затем (в любом случае) выполняется оператор statement2.
Оператор
 if  expression1  then
 if  expression2 then
  statement2
 else
  statement1;
допускает двоякую интерпретацию. Первый вариант соответствует последовательности операторов
 if  expression1  then begin
 if   expression2   then
  statement2
 else
  statementl;
 end;
Второй вариант:
 if  expression1  then begin
 if   expression2   then
  statement2
 end;
  else
  statementl;
Компилятор Паскаля всегда выбирает первый из приведенных вариантов -каждому else соответствует ближайший предшествующий if. Если требуется реализация второго варианта, можно использовать операторные скобки begin...end. В общем случае, чтобы четко определить, что чему подчинено, ис­пользуйте begin...end аналогично круглым скобкам в арифметических выраже­ниях.
Пример условного оператора;
 if Two = 2 then
  Writeln(‘Два  равно   2’)
 else
  Writeln(' В чем  дело? ');

Вложенные операторы if..then...else...

Как уже отмечалось, условные операторы можно вкладывать друг в друга, программируя таким образом сложные ветвления. Рассмотрим следующий оператор:
 if expression_1  then
  statement_l;
 else
  if expression_2  then
   statement_2
  else if expression_3  then
   statement3

 else if expression_n  then
  statement_n;
Вначале вычисляется значение логического выражения expression_l. Если оно истинно, выполняется  оператор statement_1, если же это значение ложно, вычисляется значение выражения expression_2. В том случае, когда полученное значение истинно, будет выполняться оператор statement_2, при значении «ложь» будет вычисляться выражение expression_3 и т. д.
Если выражения expression_i независимы, то есть вычисление их значений в любом порядке дает одни и тот же результат для каждого из них, имеет смысл располагать их в таком порядке, чтобы выражение с наибольшей вероятностью, принимающее значение «истина», стояло на первом месте, выражение, принимающее значение «истина» с меньшей вероятностью, — на втором и т. д.  Это уменьшит время выполнения данного фрагмента программы, особенно если вложенный оператор появляется в цикле, который выполняется много­кратно.
Пример  вложенных условных операторов:
if Two = 2 then
 if One = 1 then
  Writeln(‘Единица равна 1’)
 else
  Writeln(‘Единица не равна 1’)
else
 if Three = 3 then
  Writeln(‘Три равно 3’)
 else
  Writeln ('Три не равно 3');

Оператор case...of...end

Для ситуаций, где имеется несколько (три и более) альтернатив, больше подходит оператор case. Этот оператор называется оператором выбора и имеет следующий вид:
 case   expression  of
  values_l:  statement_l;
  values_2: statement_2;
  values_n: statemen_n;
 else  statement;
 end;
Рассмотрим элементы этой конструкции. Во-первых, это три зарезервирован­ных слова: case, of и end. Между case и of находится выражение expression,    принимающее значение, которое, возможно, имеется в одном из списков зна­чений, находящихся слева от двоеточий. Данное выражение называется селек­тором оператора case. Каждый оператор, идущий за двоеточием, отделяется от следующего списка значений точкой с запятой. Ветвь else, отвечающая всем не перечисленным значениям выражения expression, необязательна. При вы­полнении данного оператора вначале вычисляется значение селектора. Затем выбирается тот список значений, которому принадлежит полученное значе­ние, и выполняется соответствующий оператор.
В списках значений оператора case допустимыми являются типы переменных, называемые скалярными (они будут обсуждаться позже), включая целые и исключая вещественные типы. Любое заданное значение селектора может вхо­дить в список значений неоднократно, но выполняться будет лишь первая подходящая ветвь. Замечу, что «стилистически» такая конструкция выглядит не очень изящно. Если значение селектора отсутствует в списках значений, ни одна из альтернатив выполняться не будет. В этом случае выполняется ветвь else оператора case или (если эта ветвь отсутствует) следующий за case опе­ратор.
Поясним применение данного оператора следующим примером. Пусть необхо­димо преобразовать целое число N в зависимости от величины остатка от его деления на 17 следующим образом:
  если N mod 17 = 0, то N :=  0;
  если N mod 17 = 1 или 6, то N := -. N;
  если N mod 17 = 2, 3 или 5, то N := 2xN;
  если N mod 17 = 4, то N := 3x N;
во всех прочих случаях N : 5xN.
Решение этой задачи на Паскале выглядит следующим образом:

case N mod 17 of 
  0: N  := 0;
  1,6: N  := -N;
  2,3,5: N := 2*N;
  4: N  := 3*N;     '
else N := 5*N;
end;
В данном примере селектором является выражение N mod 17. Кроме того, име­ются 4 списка значений и ветвь else.


Наверх

Повторяющиеся вычисления (операторы цикла)

С помощью условных операторов и операторов присваивания теоретически можно реализовать сколь угодно сложный алгоритм. Однако на практике при необходи­мости организовать обработку тысяч элементов массива (например, присвоить каждому элементу начальное значение) вручную набирать тысячу операторов при­сваивания крайне тяжело.
Поэтому в языках программирования имеются средства для организации повторных вычислений, называемые операторами цикла. Они бывают двух видов: с фиксиро­ванным числом повторений и условные операторы цикла.
Каждый оператор цикла состоит из заголовка цикла, определяющего число повто­рений, и тела цикла — повторяемого оператора или блока операторов.
Первый вид оператора цикла (цикл с предусловием)
При решении задачи примерно в половине случаев заранее известно, сколько раз понадобится выполнить тело цикла. Так бывает, как правило, при обработке мас­сивов, размер которых всегда или известен заранее, или легко определяется.
Заголовок такого оператора состоит из трех частей — инициализации переменной-счетчика или параметра цикла (присваивания ей начального значения), определе­ния конечного значения счетчика, по достижении которою тело цикла надо выполнить в последний раз, и приращения счетчика, определяющего, на сколько будет меняться значение счетчика после каждого выполнения тела цикла.


Синтаксис оператора цикла

Бейсик

FOR счетчик = начальное_значение ТО конечное_ значение STEP приращение
тело цикла - группа операторов
NEXT
Если приращение не указывать, то считается, что оно равно 1

Паскаль

for счетчик := начальное_значение to конечное_значение do
оператор или блок операторов;
(приращение всегда равно 1)

Си++

for( счетчик – начальное_значение; условие_завершения; счетчик = счетчик + приращение)
оператор или блок операторов;

Примеры инициализации тысячи элементов массива а. Бейсик:

FOR I = 1 ТО 1000
  A(I) = О 
NEXT
Паскаль:
for i := 1 to 1000 do
  a[i] := 0;
Си++:
for( i = 0; i <  1000; i = i + 1 )
  a[i] = 0;
В последнем примере счетчик будет принимать значения от 0 до 999, потому что нумерация элементов массива в Си++ начинается с нуля.
Второй вид оператора цикла (цикл с постусловием)
Не менее часто встречаются ситуации, когда число повторений заранее неизвестно — надо выполнять цикл, пока не произойдет некоторое событие (пользователь нажмет на кнопку, точность вычислений уложится в заданный порог и т. д.). В таких ситуа­циях заголовок цикла упрощается. В нем указывается только условие (логическое выражение) — пока его значение равно true, цикл будет выполняться.

Синтаксис оператора цикла

Бейсик

Паскаль

Си++

DO WHILE условие группа операторов LOOP

while условие do
оператор или группа операторов;

while( условие )
оператор или группа операторов;

Бейсик:

DO WHILE A > В
  А - А - 0.01
LOOP
Паскаль:
while a > Ь do
  а := а - 0.01;
Си++:
while ( a > b )
  а = а - 0.01;

Зацикливание

При использовании условных операторов цикла программиста подстерегает одна опасность. Как показывает практика, достаточно легко сделать ошибку и неверно задать условие окончания цикла, которое всегда будет истинным, — при этом тело цикла станет выполняться бесконечно. Подобная ситуация называется зацикливанием.
Например:
 а = 0; b = 1;
 while( a < b )
  a = a - 0.01;
Так как исходное значение переменной а меньше, чем значение переменной b, и это значение будет только уменьшаться, то подобный цикл никогда не закончится.
В некоторых случаях программисты специально применяют подобный трюк, чтобы организовать бесконечный цикл, в котором будут приниматься и обрабатываться внешние сообщения (события). Тогда использование условного оператора цикла может выглядеть так:
 while true do
 begin / / тело цикла
 end;
Контроль за выходом из цикла при наступлении определенного события при этом полностью возлагается на программиста.
В Бейсике есть специальная форма оператора цикла, позволяющая явно описывать такие бесконечные циклы:
 DO
  тело цикла
 LOOP

Оператор цикла while...do...

Оператор цикла является важнейшим оператором и имеется и большинстве со­временных языков программирования (а сама идея цикла возникла еще в XIX веке!). Цикл позволяет многократно выполнить некоторое множество действий, задаваемых операторами, составляющими его тело. В Паскале име­ется несколько разновидностей оператора цикла. Начнем с оператора цикла с предусловием. Данный оператор имеет вид
  while expression do statement;
При выполнении этого оператора вначале вычисляется значение логического выражения expression. Если это значение истинно, выполняется оператор sta­tement, затем значение выражения проверяется вновь и т. д., до тех пор, пока выражение не примет значение «ложь». Если выражение принимает значение «ложь» при первой же проверке, то оператор statement не выполняется вооб­ще. Особо отмечу частный случай:
  while True do statement;
Здесь оператор statement будет выполняться бесконечно. Пример оператора цикла с предусловием:
 while Counter < 10 do begin
  Write('Значение счетчика равно', Counter);
  Wnteln;
  Counter := Counter + 2;
 end;

Оператор цикла repeat...until...

Оператор цикла с постусловием имеет вид
repeat statement until expession;
Здесь вначале выполняется оператор statement, а затем вычисляется значение логического выражения expression. Процесс повторяется, пока выражение expression принимает значение «ложь». Как только это значение станет истин­ным, выполнение цикла прекращается. Оператор statement может быть любым, в том числе и составным оператором:
begin
  statement_l;
  statement_2;
  ...
  statement_n;
end;
В цикле repeat…until… операторные скобки begin_end могут быть опущены. Таким образом, в общем случае оператор repeat…until… имеет следующий вид:
repeat
  statement_l;
  statement_2;
  ...
  statement_n;
until expession;
Точка с запятой перед зарезервированным словом until необязательна. В при­веденном ниже частном случае
repeat
  statement_l;
  statement_2;
  statement_n;
until   False;
цикл выполняется бесконечно. Еще раз следует обратить внимание на то, что если в операторе while…do… проверка выполняется в начале цикла, то в цикле repeat...until... проверка выполняется в последнюю очередь, и тело цикла в любом случае выполняется хотя бы один раз.
Вот пример цикла с постусловием:
repeat
  Write('Значение счетчика равно', Count);
  Writeln;
  Count := Count + 2;
until Court = 10;

Операторы цикла for...to...do... и for...downto...do...

Третий вариант оператора цикла — цикл со счетчиком. Можно считать, что есть две очень похожих друг на друга разновидности цикла со счетчиком.
 for j := expression1 to expression2 do statement;
Здесь переменная j, называемая управляющей переменной цикла for, является произвольным идентификатором, который объявляется как переменная любо­го скалярного типа (к скалярным относятся целый, символьный, булев и пере­числимые типы).
При выполнении оператора for сначала вычисляется значение выражения expression1, затем вычисляется значение выражения expression2, далее управ­ляющая переменная цикла последовательно пробегает все значения от expres­sion1 до expression2. В том случае, когда значение expression оказывается больше значения expression2, тело цикла не будет выполняться вовсе. Эти значения остаются неизменными в ходе выполнения всего цикла for. Рассмат­риваемый вариант цикла for эквивалентен следующей последовательности операторов:
j := expressionl;
k := expression2;
while j <= k do begin
  statement;
  inc(j);
end;
в предположении, что при каждом выполнении оператора statement не изменяют­ся значения j и k.
Оператор for вида for j:=expression1 to expression2 do statement; неэквивален­тен последовательности операторов
begin
 j := expression1;
 while j <= expression2 do
 begin
  statement;
  j  :=  j  +  1;
 end;
end;
потому что выражение expression2 может изменяться при каждом выполнении оператора statement к цикле while.
В теле цикла for следует избегать операторов, изменяющих значение управля­ющей переменной j. Несмотря на то, что использование подобных конструк­ций не приводит к ошибкам компиляции, они потенциально опасны и могут приводить к неприятным последствиям. Рассмотрим пример:
sum := 0;
for k := 1 to 100 do
begin
  sum := sum + Sqr(k);     k := k + 2;
end;
Этот фрагмент программы является попыткой просуммировать n2 по всем це­лым значениям вида n = (3*k+1), лежащим в диапазоне от 1 до 100. Здесь допущена ошибка реализации алгоритма, так как управляющая переменная k изменяется в составном операторе, управляемом той же переменной k. Правильной будет конструкция следующего вида:
 sum := 0;
 for k := 0 to 33 do sum := sum + Sqr(3*k + 1);
или
 sum := 0;    k  := 1;
 repeat
  sum := sum + Sqr(k);    k := k + 3;
 until    k > 100;
После выполнения цикла for значение управляющей переменной становится неопределенным.
Вариант for_downto...do... цикла for аналогичен циклу for...to…do... за исключением того, что в нем управляющая переменная на каждом шаге выполнения не увеличивается, а уменьшается на единицу:
  for j := expression1 downto expression2 do statement;
Подводя итоги, для применения циклов можно сформулировать следующие  рекомендации:

  1. Используйте цикл for в том случае, когда точно знаете, сколько раз должно быть выполнено тело цикла, в противном случае обратитесь к циклам repeat или while.
  2. Используйте repeat, если необходимо, чтобы тело цикла выполнялось по крайней мере один раз.
  3. Используйте while, если хотите, чтобы проверка была произведена прежде, чем будет выполняться тело цикла.

Иногда бывает удобно проводить проверку на возможный выход из цикла где-нибудь и его середине, а не в начале или конце. Такой? выход из цикла обеспе­чивается процедурой Break модуля System, которая прерывает выполнение самого внутреннего вложенного цикла, будь то for, while или repeat. Указанный модуль подключается к программе автоматически, если в этом есть необходимость. При­мер:
while true do
begin
 statement1;
 if  expression  then   Break;
 statenent2;
end;
Следуем также упомянуть процедуру Continue, которая прерывает выполнение тела самого внутреннего цикла for, while или repeat и передает управление на его заголовок, так что начинается выполнение очередной итерации цикла.

Исключения

Управление порядком выполнения программы может происходить не только с помощью условных операторов и операторов цикла, но и при возникновении исключений — ситуаций в программе или операционной системе, требующих немедленного реагирования. Например, при выполнении оператора присваивания и вычислении выражения произошло деление на ноль. Программа остановилась, так как не знает, что ей делать дальше, — ведь получено ошибочное значение. Чаще всего выполнение программы просто прекращается по ошибке, но современные системы разработки позволяют программисту явно контролировать возникновение самых разных исклю­чений (они еще называются исключительными ситуациями, требующими немедлен­ного вмешательства) и указывать, какие операторы следует выполнять при их возникновении.

Параллельные вычисления

Еще одна область программирования, в которой возможно изменение явно указан­ного порядка выполнения операторов, — это область параллельных вычислений. С появлением недорогих ПК с несколькими процессорами возникла возможность распараллеливания программы — одновременного выполнения ее независимых частей на разных процессорах, что теоретически позволяет получить выигрыш в быстро­действии, линейно зависящий от числа процессоров. Однако на практике это очень сложная задача, которая требует правильного выделения независимых модулей кода (так называемых процессов), выполнение которых не скажется на результатах работы других процессов. Так как момент окончания работы того или иного про­цесса заранее неизвестен, то в программе надо предусмотреть действия, связанные с синхронизацией обработки получаемых результатов. Их выполнение может потре­боваться в самые неожиданные моменты, поэтому изменение линейной последо­вательности работы операторов неизбежно.
Наверх

Назад | Вперед