logo
Языки программирования

6.1. Операторы switch и case

Оператор выбора используется для выбора одного из нескольких возможных путей, по которому должно выполняться вычисление (рис. 6.1). Обобщен­ный оператор выбора называется switch-оператором в языке С и case-onepaтором в других языках.

Switch-оператор состоит из выражения (expression) и оператора (statement) для каждого возможного значения (value) выражения:

switch (expression) {

C

case value_1:

statement_1;

break;

case value_2:

statement_2;

break;

….

}

Выражение вычисляется, и его результат используется для выбора оператора, который будет выполнен; на рис. 6. 1 выбранный оператор представляет путь. Отсюда следует, что для каждого возможного значения выражения должна су­ществовать в точности одна case-альтернатива. Для целочисленного выраже­ния это невозможно, так как нереально написать свой оператор для каждого 32-разрядного целочисленного значения. В языке Pascal case-оператор ис­пользуется только для типов, которые имеют небольшое число значений, тог­да как языки С и Ada допускают альтернативу по умолчанию (default), что по­зволяет использовать case-оператор даже для таких типов, как Character, ко­торые имеют сотни значений:

C

default:

default_statement;

break;

,

Если вычисленного значения выражения не оказывается в списке, то выпол­няется оператор, заданный по умолчанию (default_statement). В языке С, ес­ли альтернатива default отсутствует, по умолчанию подразумевается пустой оператор. Эту возможность использовать не следует, потому что читатель про­граммы не может узнать, подразумевался ли пустой default-оператор, или программист просто забыл задать необходимые операторы.

Во многих случаях операторы для двух или нескольких альтернатив иден­тичны. В языке С нет специальных средств для этого случая (см. ниже); а в Ada есть обширный набор синтаксических конструкций Для группировки альтер­натив:

С: Character;

case С is

Ada

when 'A'.. 'Z' => statement_1;

when '0'.. '9' => statement_2;

when '+' | '-' |' *' | '/' =>statement_3;

when others => statement_4;

end case;

В Ada альтернативы представляются зарезервированным ключевым словом when, а альтернатива по умолчанию называется others. Case-альтернативаможет содержать диапазон значений value_1 .. value_2 или набор значений, разделенных знаком «|».

Оператор break в языке С

В языке С нужно явно завершать каждую case-альтернативу оператором break, иначе после него вычисление «провалится» на следующую case-аль­тернативу. Можно воспользоваться такими «провалами» и построить конст­рукцию, напоминающую многоальтернативную конструкцию языка Ada:

char с;

switch (с) {

case 'A': case'B': ... case'Z':

statement_1 ;

C

break;

case'O': ... case '9':

statement_2;

break;

case '+'; case '-': case '*': case '/':

statement_3 :

break;

default:

statement_4;

break;

Поскольку каждое значение должно быть явно написано, switch-оператор в языке С далеко не так удобен, как case-оператор в Ada.

В обычном программировании «провалы» использовать не стоит:

switch (е) {

casevalue_1:

C

statement_1 ; /* После оператора statemerrM */

case value_2:

statement_2; /* автоматический провал на statement_2. */

break;

}

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

switch (e) {

case value_1 :

C

statement_1 ;

common_code();

break;

case value_2:

common_code();

break;

}

Реализация

Самым простым способом является компиляция case-оператора как после­довательности проверок:

compute R1 ,ехрг Вычислить выражение

jump_eq R1,#value_1,L1

jump_eq R1,#value_2 ,L2

… Другие значения

default_statement Команды, выполняемые по

умолчанию

jump End_Case

L1: statement_1 Команды для statement_1

jump End_Case

L2: statement_2 Команды для statement_2

jump End_Case

… Команды для других операторов

End_Case:

С точки зрения эффективности очевидно, что чем ближе к верхней части опе­ратора располагается альтернатива, тем более эффективен ее выбор; вы може­те переупорядочить альтернативы, чтобы извлечь пользу из этого факта (при условии, что вы не используете «провалы»!).

Некоторые case-операторы можно оптимизировать, используя таблицы переходов. Если набор значений выражения образует короткую непрерывную последовательность, то можно использовать следующий код (подразумевает­ся, что выражение может принимать значения от 0 до 3):

compute R1,expr

mult R1,#len_of_addr expr* длина_адреса

add R1 ,&table + адрес_начала_таблицы

jump (R1) Перейти по адресу в регистре R1

table: Таблица переходов

addr(L1)

addr(L2)

addr(L3)

addr(L4)

L1: statement_1

jump End_Case

L2: statement_2

jump End_Case

L3: statement_3

jump End_Case

L4: statement_4

End_Case:

Значение выражения используется как индекс для таблицы адресов операто­ров, а команда jump осуществляет переход по адресу, содержащемуся в реги­стре. Затраты на реализацию варианта с таблицей переходов фиксированы и невелики для всех альтернатив.

Значение выражения обязательно должно лежать внутри ожидаемого диа­пазона (здесь от 0 до 3), иначе будет вычислен недопустимый адрес, и про­изойдет переход в такое место памяти, где может даже не быть выполнимой команды! В языке Ada выражение часто может быть проверено во время ком­пиляции:

Ada

type Status is (Off, WarmJJp, On, Automatic);

S: Status;

case S is ... -- Имеется в точности четыре значения

В других случаях будет необходима динамическая проверка, чтобы гарантиро­вать, что значение лежит внутри диапазона. Таблицы переходов совместимы даже с альтернативой по умолчанию при условии, что явно заданные вариан­ты выбора расположены непрерывно друг за другом. Компилятор просто вставляет динамическую проверку допустимости использования таблицы пе­реходов; при отрицательном результате проверки вычисляется альтернатива по умолчанию.

Выбор реализации обычно оставляется компилятору, и нет никакой воз­можности узнать, какая именно реализация используется, без изучения ма­шинного кода. Из документации оптимизирующего компилятора вы, воз­можно, и узнаете, при каких условиях будет компилироваться таблица перехо­дов. Но даже если вы учтете их при программировании, ваша программа не перестанет быть переносимой, потому что сам case-оператор — переносимый; однако разные компиляторы могут реализовывать его по-разному, поэтому увеличение эффективности не является переносимым.