4. Управляющие конструкции

4.1. Составной оператор

Последовательность операторов, заключенная в фигурные скобки, называется составным оператором. Как и в языке Паскаль, в Си составной оператор обычно употребляется тогда, когда синтаксис языка требует использова­ния одного оператора, а необходимо написать несколько.

4.2. Условный оператор (полная и  неполная формы). Операция  ?.

Формат условного оператора следующий:

if (выражение) оператор1;[else оператор0;]

Следует обратить внимание на то, что:

— (выражение) должно заключаться в скобки — это требование языка;

— значение выражения приводится к целому и рассматривается в соответствии с указанным выше правилом:

нулевое значение — ложь, все остальные значения — истина;

— в случае, если выражение истинно, выполняется оператор!, в противном случае — оператор0;

— точка с запятой после оператора1 ставится в любом случае (вне зависимости от наличия или отсутствия else ).

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

Помимо перечисленных особенностей, следует обратить внимание еще на одну. (Правда, о ней обычно честно предупреждает компилятор Си, но ведь предупреждения (warnings), как и инструкции, мы обычно читаем в последнюю очередь.) Рассмотрим следующую конструкцию:

а=1;b=0;

if (a=b) printf("равно");eise printf("He равно");

Вопрос 1: что будет выдано на печать?

Ответ: конечно, "не равно".

Так вот, это неверно! Во-первых, на печать будет выдано "равно", во-вторых, переменная а получит значение 0. Дело в том, что для проверки на равенство необходимо использовать соответствующую операцию (==), а не оператор присваивания, как в приведенном примере. Приведенный пример только на первый взгляд является экзотическим. Использование операции присваивания (= ) вместо операции сравнения (== ) является очень распространенной ошибкой начинающих программировать на Си.

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

(условное_выражение)?выражение1:выражение 0;

Если условное выражение истинно (отлично от нуля), то в качестве значения всей операции ?: возвращается значение выражения1, иначе — значение выражения 0. Ряд примеров использования операции ? : приводится дальше (например, при рассмотрении #define с параметрами).

4.3. Циклы while, do.. .while и for и операторы break  и  continue

Начнем этот раздел с рассмотрения двух последних операторов. Они могут использоваться во всех циклах (поэтому мы с них и начинаем) и работают следующим образом. Оператор break безусловно прерывает цикл и передает управление на оператор, следующий за телом цикла (пример будет приведен ниже). Этот оператор может встречаться в любом месте цикла. Оператор continue также может встречаться в любом месте цикла, но он в отличие от break цикл не завершает, а немедленно передает управление на проверку условия цикла. Буквальных аналогов двух этих операторов в классическом Паскале нет, и они, несмотря на то, что в практическом программировании бывают удобны, безусловно являются отступлением от строгих правил структурного программирования.

Замечание. В Турбо Паскале операторы break и continue имеются.

Теперь собственно о циклах. Цикл while является циклом с предусловием. Его вид:

while (выражение) оператор;

 

Цикл выполняется до тех пор, пока (выражение) — кстати, обратите внимание, что оно заключено в скобки, — истинно (отлично от нуля). Для тех, кто программировал на Паскале, это аналог привычного цикла while. .do.

Цикл do. .while является циклом с постусловием (и, следовательно, выполняется по крайней мере один раз). Его вид:

do оператор; while (выражение);

Обратите внимание, что в этом цикле также используется условие продолжения (что несколько непривычно для тех, кто привык программировать на Паскале и использовать в цикле repeat, .until условие окончания).

Наконец, в Си имеется еще один цикл, который называют просто "циклом for". Он вовсе не является простым циклом с параметром (в терминологии языка Паскаль), хотя может использоваться и в таком качестве. Это еще один цикл с предусловием, который обладает рядом особенностей, делающих его использование особенно удобным. Общий вид цикла for:

for (инициализация; выражение; шаг) оператор;

(Названия "инициализация", "выражение" и "шаг" весьма условны, но обычно эти части цикла называют именно так.) Каждая из частей: инициализация, выражение и шаг должна представлять собой одно выражение, которое может быть и пустым (почему на это стоит обратить особое внимание, станет ясно чуть позже). Схема работы этого цикла показана на рисунке.

Сначала управление передается на инициализацию, т.е. эта часть цикла безусловно выполняется (причем ровно один раз). Далее вычисляется значение выражения, и, если оно истинно (отлично от нуля), мы переходим к выполнению тела цикла (оператора). После выполнения оператора цикла управление передается на шаг цикла). После выполнения шага снова проверяется значение выражения, ну и т.д. И что же в этом хитрого, спросите вы? Что все происходит именно так, и ежику понятно. Так вот, ежику понятно на самом деле не все.

Вот простой пример использования цикла for:

s=0;
for (i=l;i<11;i++) s+=i;

Что происходит в этом цикле, в комментариях, конечно, не нуждается, интереснее другое. Здесь наконец представляется возможность показать (а мы это чуть выше обещали), как используется "странная" операция запятая (", "). Согласитесь, несколько неестественно, что переменная s, которая имеет непосредственное отношение к циклу, инициализируется вне него. Выхода вроде бы нет, ведь написать такую конструкцию for (s=0; i=l; i<11; i++) s+=i; нельзя. Нельзя написать и такую: for ({s=0;i=l;};i<11;i++) s+=i;

Но зато можно написать все гораздо проще, вот так: for (s=0, i=l; i<11; i++) s+=i;

Операция запятая, объединяя два выражения в одно, делает именно то, что нам надо: позволяет внести инициализацию переменной s внутрь цикла. Но и это еще не все, сам оператор цикла тоже можно внести в раздел шаг, ведь по смыслу он шаг и есть. И тут опять понадобится операция запятая:

for (s=0,i=0;i<11;s+=i,i++);

Таким образом, "тело цикла", собственно оператор вообще стал пустым!

Кстати, как вы думаете, какое значение получит переменная s, если выражения в шаге поменять местами:

for (s=0,i=0;i<11;i++,s+=i);

А если написать так:

for (s=0,i=0;i<11;s+=++i);

А так:

for (s=0,i=0;i<11;s+=i++) ;

Все вышеперечисленные вопросы, конечно, любопытные, но не более того. Си — гибкий язык, в нем можно записать довольно сложные конструкции и долго ломать голову над тем, что они означают. Заниматься этим, на взгляд автора, не стоит (хотя такие задачки вполне можно использовать "для разгрузки" и на различных состязаниях).

4.4. Переключатели switch и break

Оператор switch является аналогом case в Паскале (т.е. служит для упрощения записи множества вложенных условий). По сравнению с case оператор switch имеет несколько особенностей, на которые необходимо обратить внимание, формат его следующий:

 switch (выражение)
 { case значение1: последовательность операторов_1;

  case значение2 : последовательность_операторов_2 ;
  ...
  case значениеN: последовательность операторовN;

   [ default: последовательность операторов DEFAULT]

  }

Выполняется этот оператор следующим образом. Вычисляется значение выражения, которое последовательно сравнивается со значениями, указанными в разделах case. Как только находится подходящее значение, управление передается на соответствующую последовательность_операторов, а вот дальше... выполняются все операторы, расположенные ниже (включая раздел default, если таковой имеется). То есть, начиная с точки входа, тело оператора switch выполняется "насквозь", что неизменно вызывает удивление у тех, кто привык программировать на Паскале. Приведем короткий пример:

а=3;

switch (a)
 
{ case 1:printf("один "); printf("l ");

case 2:printf("два "); printf ("2 ");

case 3:printf("три "); printf("3 ");

case 4:printf("четыре "); printf("4 ");

case 5:printf("пять "); printf("5 "};

default: printf("много "); printf("> ");

}

В результате выполнения этого оператора на печать будет выведено: "три 3 четыре 4 пять 5 много >". Но это же жутко неудобно, скажете вы! Оператор case в Паскале устроен гораздо удобнее! Ничего подобного! Просто switch более гибок. Для того чтобы прервать его выполнение в любой момент, можно использовать break. Для придания оператору "нормального" вида его надо переписать в виде:

а=3;

switch (a)

{ case 1:printf("один "); printf ("I "); break;

case 2:printf("два "); printf("2 "); break;

case 3:printf("три "); printf("З "); break;

case 4:printf("четыре "); printf("4 "); break;
case
5:printf("пять "); printf ("5 "); break;

default: printf("много "); printf ("> ");

}

В этом случае после выполнения соответствующей последовательности операторов мы "вывалимся" из оператора switch (управление будет передано на первый оператор после закрывающей фигурной скобки).

Замечание. К сожалению, в case нельзя задавать диапазон значений. Если в Паскале метка оператора выбора может иметь следующий вид: 1..3,6,9..11, то в Си приходится писать громоздкую конструкцию:

                      case 1:case 2:case 3:case 6:case 9:case 9:case 10:case 11:

TopList