Блок или составной оператор

С помощью фигурных скобок несколько операторов языка (возможно, перемежаемых объявлениями) можно объединить в единую синтаксическую конструкцию, называемую блоком или составным оператором:

{

оператор_1

...

оператор_N

}

В языках программирования нет общепринятой нормы для использования символа точки с запятой при записи последовательности операторов. Есть три различных подхода и их вариации. Категорические противники точек с запятой считают, что каждый оператор должен записываться на отдельной строке (для длинных операторов определяются правила переноса). В этом случае точки с запятой (или другие аналогичные разделители) не нужны. Горячие поклонники точек с запятой (к ним относятся языки С++ и C#) считают, что точкой с запятой должен оканчиваться каждый оператор. В результате в операторе if перед elseпоявляется точка с запятой. Третьи полагают, что точка с запятой играет роль разделителя операторов, поэтому перед else ее не должно быть. В приведенной выше записи блока, следуя синтаксису C#, каждый из операторов заканчивается символом "точка с запятой". Но, заметьте, блок не заканчивается этим символом!

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

/// <summary>

/// демонстрация блоков (составных операторов)

/// </summary>

public void Block()

{

int limit = 100;

int x = 120, y = 50;

int sum1 =0, sum2=0;

for (int i = 0; i< 11; i++)

{

int step = Math.Abs(limit -x)/10;

if (x > limit)

{x -= step; y += step;}

else

{x += step; y -= step;}

sum1 += x; sum2 +=y;

}

//limit = step; //переменная step перестала существовать

//limit = i; // переменная i перестала существовать

Console.WriteLine("x= {0}, y= {1}, sum1 ={2}, sum2 = {3}",

x,y,sum1,sum2);

}

Заметьте, здесь в тело основного блока вложен блок, задающий тело цикла, в котором объявлены две локальные переменные - i и step.

В свою очередь, в тело цикла вложены блоки, связанные с ветвями then и else оператора if. Закомментированные операторы, стоящие сразу за окончанием цикла, напоминают, что соответствующие локальные переменные, определенные в блоке, перестают существовать по его завершении.

Приведенная процедура Block является методом класса Testing, который входит в проект Statements, созданный для работы с примерами этой лекции. Вот описание полей и конструктора классаTesting:

/// <summary>

/// Класс Testing - тестирующий класс. Представляет набор

/// скалярных переменных и методов, тестирующих работу

/// с операторами, процедурами и функциями C#.

/// </summary>

public class Testing

{

public Testing(string name, int age)

{

this.age = age;

this.name = name;

}

//поля класса

public string name;

public int age;

private int period;

private string status;

}

Пустой оператор

Пустой оператор - это "пусто", завершаемое точкой с запятой. Иногда полезно рассматривать отсутствие операторов как существующий пустой оператор. Синтаксически допустимо ставить лишние точки с запятой, полагая, что вставляются пустые операторы. Например, синтаксически допустима следующая конструкция:

for (int j=1; j<5; j++)

{;;;};

Она может рассматриваться как задержка по времени, работа на холостом ходе.

Операторы выбора

Как в С++ и других языках программирования, в языке C# для выбора одной из нескольких возможностей используются две конструкции - if и switch. Первую из них обычно называют альтернативным выбором, вторую - разбором случаев.

Оператор if

Начнем с синтаксиса оператора if:

if(выражение_1) оператор_1

else if(выражение_2) оператор_2

...

else if(выражение_K) оператор_K

else оператор_N

Какие особенности синтаксиса следует отметить? Выражения if должны заключаться в круглые скобки и быть булевого типа. Точнее, выражения должны давать значения true или false. Напомню, арифметический тип не имеет явных или неявных преобразований к булевому типу. По правилам синтаксиса языка С++, then -ветвь оператора следует сразу за круглой скобкой без ключевого слова then, типичного для большинства языков программирования. Каждый из операторов может быть блоком - в частности, if -оператором. Поэтому возможна и такая конструкция:

if(выражение1) if(выражение2) if(выражение3) ...

Ветви else и if, позволяющие организовать выбор из многих возможностей, могут отсутствовать. Может быть опущена и заключительная else -ветвь. В этом случае краткая форма оператора if задает альтернативный выбор - делать или не делать - выполнять или не выполнять then -оператор.

Семантика оператора if проста и понятна. Выражения if проверяются в порядке их написания. Как только получено значение true, проверка прекращается и выполняется оператор (это может быть блок ), который следует за выражением, получившим значение true. С завершением этого оператора завершается и оператор if. Ветвь else, если она есть, относится к ближайшему открытому if.

Оператор switch

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

switch(выражение)

{

case константное_выражение_1: [операторы_1 оператор_перехода_1]

...

case константное_выражение_K: [операторы_K оператор_перехода_K]

[default: операторы_N оператор_перехода_N]

}

Ветвь default может отсутствовать. Заметьте, по синтаксису допустимо, чтобы после двоеточия следовала пустая последовательность операторов, а не последовательность, заканчивающаяся оператором перехода. Константные выражения в case должны иметь тот же тип, что и switch -выражение.

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

Полагаю, что оператор switch - это самый неудачный оператор языка C# как с точки зрения синтаксиса, так и семантики. Неудачный синтаксис порождает запутанную семантику, являющуюся источником плохого стиля программирования. Понять, почему авторов постигла неудача, можно, оправдать - нет. Дело в том, что оператор унаследован от С++, где его семантика и синтаксис еще хуже. В языке C# синтаксически каждая case -ветвь должна заканчиваться оператором перехода (забудем на минуту о пустой последовательности), иначе возникнет ошибка периода компиляции. В языке С++ это правило не является синтаксически обязательным, хотя на практике применяется та же конструкция с конечным оператором break. При его отсутствии управление "проваливается" в следующую case -ветвь. Конечно, профессионал может с успехом использовать этот трюк, но в целом ни к чему хорошему это не приводит. Борясь с этим, в C# потребовали обязательного включения оператора перехода, завершающего ветвь. Гораздо лучше было бы, если бы последним оператором мог быть только оператор break, писать его было бы не нужно и семантика стала бы прозрачной - при совпадении значений двух выражений выполняются операторы соответствующей case -ветви, при завершении которой завершается и оператор switch.

Еще одна неудача в синтаксической конструкции switch связана с существенным ограничением, накладываемым на case -выражения, которые могут быть только константным выражением. Уж если изменять оператор, то гораздо лучше было бы использовать синтаксис и семантику Visual Basic, где в case -выражениях допускается список, каждое из выражений которого может задавать диапазон значений.

Разбор случаев - это часто встречающаяся ситуация в самых разных задачах. Применяя оператор switch, помните о недостатках его синтаксиса, используйте его в правильном стиле. Заканчивайте каждую case-ветвь оператором break, но не применяйте goto.

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

/// <summary>

/// Определяет период в зависимости от возраста - age

/// Использование ветвящегося оператора if

/// </summary>

public void SetPeriod()

{

if ((age > 0)&& (age <7))period=1;

else if ((age >= 7)&& (age <17))period=2;

else if ((age >= 17)&& (age <22))period=3;

else if ((age >= 22)&& (age <27))period=4;

else if ((age >= 27)&& (age <37))period=5;

else period =6;

}

Этот пример демонстрирует применение ветвящегося оператора if. С содержательной точки зрения он интересен тем, что в поля класса пришлось ввести специальную переменную period, позволяющую в дальнейшем использовать разбор случаев в зависимости от периода жизни:

/// <summary>

/// Определяет статус в зависимости от периода - period

/// Использование разбора случаев - оператора Switch

/// </summary>

public void SetStatus()

{

switch (period)

{

case 1:

status = "child";

break;

case 2:

status = "schoolboy";

break;

case 3:

status = "student";

break;

case 4:

status = "junior researcher";

break;

case 5:

status = "senior researcher";

break;

case 6:

status = "professor";

break;

default :

status = "не определен";

break;

}

Console.WriteLine("Имя = {0}, Возраст = {1}, Статус = {2}",

name, age, status);

}//SetStatus

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

/// <summary>

/// Разбор случаев с использованием списков выражений

/// </summary>

/// <param name="operation">операция над аргументами</param>

/// <param name="arg1">первый аргумент бинарной операции</param>

/// <param name="arg2">второй аргумент бинарной операции</param>

/// <param name="result">результат бинарной операции</param>

public void ExprResult(string operation,int arg1, int arg2,

ref int result)

{

switch (operation)

{

case "+":

case "Plus":

case "Плюс":

result = arg1 + arg2;

break;

case "-":

case "Minus":

case "Минус":

result = arg1 - arg2;

break;

case "*":

case "Mult":

case "Умножить":

result = arg1 * arg2;

break;

case "/":

case "Divide":

case "Div":

case "разделить":

case "Делить":

result = arg1 / arg2;

break;

default:

result = 0;

Console.WriteLine("Операция не определена");

break;

}

Console.WriteLine ("{0} ({1}, {2}) = {3}",

operation, arg1, arg2, result);

}//ExprResult

Операторы перехода

Операторов перехода, позволяющих прервать естественный порядок выполнения операторов блока, в языке C# имеется несколько.

Оператор goto

Оператор goto имеет простой синтаксис и семантику:

goto [метка|case константное_выражение|default];

Все операторы языка C# могут иметь метку - уникальный идентификатор, предшествующий оператору и отделенный от него символом двоеточия. Передача управления помеченному оператору - это классическое использование оператора goto. Два других способа использования goto (передача управления в case или default - ветвь ) используются в операторе switch, о чем шла речь выше.

"О вреде оператора goto " и о том, как можно обойтись без него, писал еще Эдгар Дейкстра при обосновании принципов структурного программирования.

Наши рекомендации