Блок или составной оператор
С помощью фигурных скобок несколько операторов языка (возможно, перемежаемых объявлениями) можно объединить в единую синтаксическую конструкцию, называемую блоком или составным оператором:
{
оператор_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 " и о том, как можно обойтись без него, писал еще Эдгар Дейкстра при обосновании принципов структурного программирования.