Ветвление (программирование)Ветвление в программировании — операция, применяющаяся в случаях, когда выполнение или невыполнение некоторого набора команд должно зависеть от выполнения или невыполнения некоторого условия. Ветвление — одна из трёх (наряду с последовательным исполнением команд и циклом) базовых конструкций структурного программирования. Основные формы реализации ветвления в императивных языках программирования — условный оператор (оператор Условный операторУсловный оператор реализует выполнение определённых команд при условии, что некоторое логическое выражение (условие) принимает значение «истина» При выполнении условного оператора с одной ветвью При выполнении условного оператора с двумя ветвями if условие1
then команды1
else if условие2 then команды2
else if условие3 then команды3
...
else if условиеN+1 then командыN+1
else команды end;
В этом случае условия будут проверяться последовательно, и как только встретится истинное, будет выполнен соответствующий набор команд и исполнение перейдёт к команде, следующей за условным оператором. Если ни одно из условий не окажется истинным, выполняются команды из ветви Ряд языков программирования содержит специальную конструкцию для каскадирования условных операторов, позволяющую записать множественное ветвление несколько компактнее и менее подверженную ошибкам написания: if условие1 then команды1
elsif условие2 then команды2
elsif условие3 then команды3
...
else команды end;
РеализацииПаскаль унаследовал от Алгола-60 синтаксис, согласно которому в ветвях условного оператора может быть помещена только одна команда. Поэтому для размещения там большего количества команд они группируются в составной оператор с помощью пары ключевых слов If условие
then
begin
операторы;
end
else
begin
операторы;
end;
Необходимость условного оператора в Алголе и Паскале с момента появления была объектом критики. Критики говорили, что многочисленные составные операторы загромождают программу, мешают нормальной расстановке отступов и провоцируют ошибки (если в последней ветви оператора if забыть составной оператор там, где он необходим, то компилятор ничего не заметит, но при выполнении программы из группы операторов, которые должны выполняться в этой ветви, по условию будет выполняться только первый, все остальные — всегда). Следующие поколения языков — потомков Алгола попытались избавиться от этого недостатка. В их числе три широко известных языка: Алгол-68, Модула-2 и Ада. Конструкция оператора if в них практически одинакова, с точностью до отдельных ключевых слов:
if условие
...
fi;
IF условие1 THEN команды1
ELSE IF условие2 THEN команды2
...
ELSE командыN
END;
if условие1 then команды1
else if условие2 then команды2
...
else командыN
end if;
Во всех случаях «командыX» — любое число операторов разделённых точкой с запятой. Во всех случаях все ветви условного оператора, кроме первой (ветви «then») необязательны и могут быть пропущены. Если ветвь «else» отсутствует и ни одно из условий не выполняется, то управление передаётся на команду, следующую за ключевым словом завершения условной конструкции (END, FI или END IF). Си и C++ (а вслед за ними и Java, C#, PHP и множество других языков) имеют условный оператор, структурно аналогичный Паскалю. Отличие состоит в том, что условие должно быть записано в круглых скобках, а вместо ключевых слов if (<условие>)
{
<операторы>
}
else
{
<операторы>
}
В Nemerle, в отличие от большинства языков, где оператор when (условие){ операторы }
unless (условие) { операторы }
В Форте условный оператор имеет отличный от вида в других языках вид, из-за постфиксной формы записи и стековой организации. Условный оператор состоит из трёх слов <условие>
IF
<выражение_1_если_условие_истинно>
ELSE
<выражение_2_если_условие_ложно>
THEN
Здесь
При отсутствии Фортран изначально имел только арифметический IF, в котором в зависимости от знака выражения производился переход на одну из трёх меток. Например, часть кода подпрограммы решения квадратного уравнения: DN = B*B - 4*A*C
IF(DN) 90,10,10
10 D = SQRT(DN)
X1 = (-B + D) / (2*A)
X2 = (-B - D) / (2*A)
Затем были добавлены логические (булевские) выражения и логический IF с одним оператором, вычисляемый GOTO, позже — структурный IF (с несколькими условиями), например: DN = B*B - 4*A*C
IF(DN.GE.0) THEN
D = SQRT(DN)
X1 = (-B + D) / (2*A)
X2 = (-B - D) / (2*A)
ELSE
!(код для случая отрицательного дискриминанта не приводится)
END IF
Perl поддерживает структурный if с несколькими условиями, а также модификаторы оператора (statement modifiers), которые записываются после выполняемой части оператора. Например, два следующих примера идентичны по функциональности: if ($a == 0) {
++$zero_count;
}
++$zero_count if $a == 0;
Вместо if можно писать unless, что приводит к инверсии значения условного выражения перед проверкой. То же самое действие через unless: ++$zero_count unless $a != 0;
Для составного оператора (блока) допустима только структурная форма, но не модификатор. Например: if ($a == 0) {
...
} else if ($a > 0) {
...
} else {
...
}
Завершающее ключевое слово не нужно, за счёт требования обязательного оформления операторов под условиями в блоки {…}. Не существует аналога слова unless для веток elsif. Erlang использует два условных оператора — if и case. Оба имеют результирующее значение, которое равно значению последнего оператора в выполненной ветке и может быть использовано (назначено имени, передано в функцию…), поэтому в нём нет отдельного тернарного условного оператора. В операторе case выполняется Сопоставление с образцом, с возможностью дополнительных условий на значения в сравниваемом, а в операторе if — только проверка условий. В условиях (guard tests) допускается ограниченное множество операций и встроенных функций. Пример на case (удаление записи о событии из дерева времён): {NewEBW, NewEBN} = case dict:find(EBN, Node) of
error ->
{EBW, EBN};
{ok, Expiry} ->
{gb_trees:delete_any({Expiry,Node}, EBW),
dict:erase(Node, EBN)}
end,
Примеры на if: if
NeedLater ->
erlang:send_after(
trunc(1000*(1+Elapsed)),
self(), i_apply_nodes_portion);
true ->
nop
end,
After2 = if
%% If it was too far ago, schedule timeout immediately.
After1 =< ?EXPIRY_STEP_MIN -> ?EXPIRY_STEP_MIN;
After1 >= ?EXPIRY_STEP_MAX -> ?EXPIRY_STEP_MAX;
true -> After1
end,
Оператор множественного выбораКонструкция переключателя имеет несколько (две или более) ветвей. Переключатель выполняет одну заданную ветвь в зависимости от значения вычисляемого ключевого выражения. Принципиальным отличием этой инструкции от условного оператора является то, что выражение, определяющее выбор исполняемой ветви, возвращает не логическое, а целое значение, либо значение, тип которого может быть приведён к целому. В некоторых языках допускается использовать в переключателе выражения некоторых типов, не приводимых к целому (например, текстовые строки). Прототипом современной синтаксической конструкции была используемая в старых языках программирования команда перехода по вычисляемой метке. В этой команде указывалось выражение-селектор, возвращающее целое значение, и набор меток. При выполнении команды вычислялось выражение, а его значение использовалось как номер метки (в списке команды), на которую производился переход. Такие конструкции были, например, в языках программирования Фортран («вычисляемый GOTO») и Бейсик. Привлекательной стороной конструкции является её достаточно высокая эффективность: для определения нужной ветви (метки перехода) не требуется последовательно сравнивать результат выражения-селектора со многими значениями, достаточно записать в память массив команд безусловного перехода с нужными адресами, чтобы при выполнении команды вычислять нужный элемент непосредственно из значения выражения. При этом скорость выполнения команды не зависит от количества меток. В современных языках реализация оператора-переключателя также часто выполняется в виде таблицы перехода, состоящей из команд безусловного перехода на соответствующие фрагменты кода. Вычисляемое выражение преобразовывается в значение сдвига по таблице перехода, определяющее выполняемую команду. В языках, где выражение-селектор может иметь нецелое значение, напрямую вычислить нужную ветвь конструкции переключателя можно далеко не всегда, поэтому в них используются другие методы оптимизации исполнения. В современных языках программирования высокого уровня команда-переключатель обычно имеет имя Например, в языке Си синтаксис команды следующий: switch (i)
{
case 0:
case 1: // последовательность операторов
break;
case 2: // последовательность операторов
break;
default:
;
}
Здесь i — выражение-селектор, которое обязано иметь приводимый к целому тип, каждая ветвь исполнения начинаются с ключевого слова Синтаксис команды-переключателя Си унаследован множеством языков, но семантика его не всегда полностью аналогична Си. Например, в C# допускается использовать выражение-селектор строкового типа и соответствующие метки. Особенности вычисления логических выраженийНа порядок исполнения программы с условными операторами может существенно влиять принятая в языке логика вычисления условных выражений. Когда условие представляет собой сложное логическое выражение, к примеру «f(x) > 0 И g(y) > 0», существует две стратегии вычисления его результата:
Второй вариант является наиболее распространённым для промышленных языков (в частности, для Алгола, Фортрана, C++, С, Java, JavaScript, ECMAScript, JScript, C#, Python). В этих языках действует жёсткое правило: «Логическое выражение всегда вычисляется слева направо и его вычисление останавливается сразу же, как только результат всего выражения становится определённым». Это означает, что если выражение состоит из нескольких подусловий, объединённых оператором «И» (AND), то вычисление выражения прекратится, как только одно из подусловий окажется ложным (так как «ложь AND любое значение» в результате всегда даёт «ложь»), и, наоборот, если несколько подусловий объединены оператором «ИЛИ» (OR), вычисление прекратится после первого же истинного подусловия, поскольку в этом случае всё выражение истинно, независимо от дальнейших вычислений. А вот выражение, содержащее оператор «Исключающее ИЛИ» (XOR) неполному вычислению не поддаётся, поскольку в нём одно из значений не может определить результат вычисления всего выражения. Языки Ада и Erlang используют разные ключевые слова для этих вариантов: слова and и or означают полное вычисление, а and then, or else (Ада), andalso, orelse (Erlang) — неполное. В Erlang andalso и orelse менее приоритетны, чем операции сравнения, что позволяет избежать скобок вокруг элементарных условий. Язык Visual Basic.NET имеет похожие ключевые слова: AndAlso и OrElse. Фиксированный порядок вычисления подусловий (логическое выражение всегда вычисляется слева направо) вводится для того, чтобы иметь возможность управлять порядком вычисления выражения и помещать в него сначала те условия, которые должны вычисляться в первую очередь. Этим, кстати, логические выражения отличаются от арифметических, для которых, в большинстве языков, порядок вычисления подвыражений (если только он не определён приоритетом и ассоциативностью операций) языком не задаётся и в разных случаях может быть различным. Выбор именно такой логики исполнения связан с тем, что она позволяет упростить логические выражения, в которых используются зависимые элементы. Классический пример — линейный поиск в массиве: // Поиск в массиве целых чисел на языке Паскаль
// Параметры - искомое значение и открытый массив целых чисел
// Результат - индекс найденного элемента или -1 в случае, если элемент не найден
function Find(e: integer; var a: array of integer): integer;
var
i: integer;
begin
i := 0;
while (i <= High(a)) AND (a[i] <> e) do inc(i); // !!!
if i <= High(a)
then Find := i
else Find := -1;
end;
Алгоритм, реализуемый программой, совершенно очевиден, но в реализации есть одна тонкость (см. строку, помеченную восклицательными знаками): условие цикла состоит из двух частей, связанных оператором AND. Первое подусловие проверяет, не вышел ли индекс i за пределы массива, второе — не равен ли текущий элемент массива искомому значению. Если массив не содержит искомого значения, то после проверки последнего элемента значение переменной i увеличится на единицу; на следующей итерации первое подусловие окажется ложным и цикл завершится без проверки второго подусловия. Если бы логические выражения вычислялись полностью, то при отсутствии искомого элемента в массиве после последней итерации происходила бы ошибка: попытка определить a[i] вызывала бы некорректное обращение к памяти. Следует обратить внимание, что, кроме неполного вычисления значения выражения, здесь также играет существенную роль фиксированный порядок вычисления подусловий: поскольку проверка выхода за границу массива записана первой, она всегда будет выполняться раньше, чем проверка достижения искомого значения. Если бы порядок вычисления подвыражений был неопределённым, гарантировать правильность приведённого фрагмента программы было бы невозможно. При полном вычислении логических выражений приведённый алгоритм пришлось бы записать примерно в следующем виде: // Поиск в массиве целых чисел на языке Паскаль
// Гипотетический вариант при полном вычислении логических выражений
// Параметры - искомое значение и открытый массив целых чисел
// Результат - индекс найденного элемента или -1 в случае, если элемент не найден
function Find(e: integer; var a: array of integer): integer;
var
i: integer;
f: boolean; // дополнительная переменная - флаг завершения цикла
begin
i := 0;
f := false;
while not f do
if i > High(a) then f := true
else if a[i] = e then f := true
else inc(i);
if i <= High(a)
then Find := i
else Find := -1;
end;
Как видим, пришлось искусственно задать порядок вычисления условий выхода и ввести дополнительную переменную. Именно для того, чтобы избежать подобных трюков, и введено неполное вычисление логических выражений. Примечание: Код изложенный выше, является примером использования оператора IF, но не более. Этот код нельзя использовать как правило для написания алгоритмов на языке Паскаль. Оптимальный алгоритм для поиска числа в массиве: function Find(e: integer; var a: array of integer): integer;
var
i: integer;
begin
Result := -1;
for i := Low(a) to High(a) do begin
if a[i] = e then begin
Result := i;
Break;
end;
end;
end;
Примечания
Information related to Ветвление (программирование) |