Выбирающий оператор

ВыбирающийОператор ::= if (Выражение) Оператор [else Оператор]
                   ::= switch (Выражение) Оператор 

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

Операторы выбора определяют один из возможных путей выполнения программы.

Выбирающий оператор if имеет собственное название. Его называют условным оператором.

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

if (i)
 {int k = 1;}
else
 {int l = 10;}

Этот пример условного оператора интересен тем, что операторы, выполняемые после проверки условия (значение переменной i), являются операторами объявления. В ходе выполнения одного из этих операторов объявления в памяти создаётся объект типа int с именем k и значением 1, либо объект типа int с именем l и значением 10. Областью действия этих имён являются блоки операторов, заключающих данные операторы объявления. Эти объекты имеют очень короткое время жизни. Сразу после передачи управления за пределы блока эти объекты уничтожаются. Ситуация не меняется, если условный оператор переписывается следующим образом:

if (i)
 int k = 1;
else
 int l = 10;

При этом область действия имён и время жизни объектов остаются прежними. Это позволяет несколько расширить первоначальное определение блока: операторы, входящие в выбирающий оператор также считаются блоком операторов.

Подобное обстоятельство являлось причиной стремления запретить использование операторов объявлений в теле условного оператора. В справочном руководстве по C++ Б.Строуструпа по этому поводу сказано, что в случае, если объявление является единственным оператором, то в случае его выполнения возникает имя "с непонятной областью действия".

Однако запрещение использования оператора объявления в условном операторе влечёт за собой очень много ещё более непонятных последствий. Именно по этой причине в последних реализациях C++ это ограничение не выполняется. Проблема области действия является проблемой из области семантики языка и не должна оказывать влияния на синтаксис оператора.

Выбирающий оператор switch или оператор выбора предназначается для организации выбора из множества различных вариантов.

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

Синтаксис выбирающего оператора допускает пустой составной оператор и пустой оператор в качестве операторов, следующих за условием выбирающего оператора:

switch (i) ;  // Синтаксически правильный оператор выбора…
switch (j) {} // Ещё один… Такой же бесполезный и правильный…
switch (r) i++;// Этот правильный оператор также не работает.

В теле условного оператора в качестве оператора может быть использовано определение:

switch (k) {
            int q, w, e;
           }

Этот оператор выбора содержит определения объектов с именами q, w, e.

Туда могут также входить операторы произвольной сложности и конфигурации:

switch (k) {
          int q, w, e;
          q = 10; e = 15;
          w = q + e;
         }

Входить-то они могут, а вот выполняться в процессе выполнения условного оператора не будут!

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

switch (k) {
            int q = 100, w = 255, e = 1024; // Ошибка…
            default: int r = 100; // Опять ошибка…
           }

Дело в том, что в ходе выполнения оператора объявления с одновременной инициализацией создаваемого объекта происходят два события:

во-первых, производится определение переменной, при котором выделяется память под объект соответствующего типа:

int q; int w; int e;

во-вторых, выполняется дополнительное днйствие - нечто эквивалентное оператору присвоения:

q = 100; w = 255; e = 1024;

а вот этого в данном контексте и не разрешается делать! Просто так операторы в теле условного оператора не выполняются.

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

Казалось, логичнее было бы не делать никаких различий между операторами объявления и прочими операторами. Но дело в том, что оператор выбора состоит из одного единственного блока. И нет иного пути создания объекта с именем, область действия которого распространялась бы на всё тело оператора выбора, как разрешение объявления переменных в любой точке оператора выбора. Судя по всему, переменная создаётся до того момента, как начинают выполняться операторы в блоке. Объявление превыше всего!

И всё же, какие операторы выполняются в теле оператора выбора (разумеется, за исключением объявления без инициализации)? Ответ: все подряд, начиная с одного из помеченных.

Возможно, что помеченного меткой "default:". При этом в теле оператора выбора может быть не более одной такой метки.

switch (val1) default: x++;

А возможно, помеченного меткой "case КонстантноеВыражение :". В теле оператора выбора таких операторов может быть сколь угодно много. Главное, чтобы они различались значениями константных выражений.

Нам уже известно, что является константным выражением и как вычисляется его значение.

Небольшой тест подтверждает факт вычисления значения константного выражения транслятором:

switch (x)
{
       int t;// Об этом операторе уже говорили…
       case 1+2: y = 10;
       case 3:   y = 4; t = 100; // На этом месте транслятор
       //сообщит об ошибке. А откуда он узнал, что 1+2 == 3 ?
       // Сам сосчитал…
       default: cout << y << endl;
      }

А вот пример, который показывает, каким оразом вычисляется выражение, содержащее операцию запятая:

int XXX = 2;
switch (XXX)
{
 case 1,2: cout << "1,2"; break;
 case 2,1: cout << "2,1"; break;
}

Константное выражение принимает значение правого операнда, на экран дисплея выводится первое сообщение.

И ещё один вопрос. Почему множество значений выражения, располагаемого после switch, ограничивается целыми числами. Можно было бы разрешить использование выражения без ограничения на область значений. Это ограничение связано с использованием константных выражений. Каждый оператор в теле оператора выбора выполняется только при строго определённых неизменных условиях. А это означает, что выражения должны представлять константные выражения. Константные выражения в C++ являются выражениями целочисленного типа (константных выражений плавающего типа в C++ просто не существует).

Рассмотрим, наконец, схему выполнения оператора switch:

Метки case и default в теле оператора switch используются лишь при начальной проверке, на стадии определения начальной точки выполнения тела оператора. На стадии выполнения все операторы от точки выполнения и до конца тела оператора выполняются независимо от меток, если только какой-нибудь из операторов не передаст управление за пределы оператора выбора. Таким образом, программист сам должен заботиться о выходе из оператора выбора, если это необходимо. Чаще всего для этой цели используется оператор break.

В этом разделе нам остаётся обсудить ещё один вопрос. Это вопрос о соответствии оператора выбора и условного оператора. На первый взгляд, оператор выбора легко может быть переписан в виде условного оператора. Рассмотрим в качестве примера следующий оператор выбора:

int intXXX;
:::::
switch (intXXX)
{
 case 1:
    int intYYY;
/* Здесь инициализация переменной запрещена, однако определение
переменной должно выполняться. */
    break;
 case 2:
 case 3:
    intYYY = 0;
    break;
}

Казалось бы, этот оператор выбора может быть переписан в виде условного оператора:

int intXXX;
:::::
if (intXXX == 1)
{
 int intYYY = 0; // Здесь допускается инициализация!
}
else if (intXXX == 2 || intXXX == 3) 
{
 intYYY = 0;
/*
Здесь ошибка! Переменная intYYY не объявлялась в этом блоке операторов.
*/
}

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

В условном операторе переменная должна объявляться в каждом блоке.

Ситуация с необъявленной в одном из блоков условного оператора переменной может быть решена путём создания внешнего блока, в который можно перенести объявления переменных, которые должны использоваться в блоках условного оператора.

int intXXX;
:::::
if (1) 
/*
Этот условный оператор определяет внешний блок операторов, в котором
располагается объявление переменной intYYY.
*/
{
int intYYY = 0;
if (intXXX == 1)
{
 intYYY = 0;
}
else if (intXXX == 2 || intXXX == 3) 
{
 intYYY = 0;
}
}

Нам удалось преодолеть проблемы, связанные с областями действия, пространствами и областями видимости имён путём построения сложной системы вложенных блоков операторов. Простой одноблочный оператор выбора, содержащий N помеченных операторов, моделируется с помощью N+1 блока условных операторов.

Однако каждый оператор хорош на своём месте.

Назад | Содержание | Вперед