7.4 – Основы работы с оператором switch

Добавлено 24 мая 2021 в 13:17

Хотя несколько операторов if-else можно связать вместе, но это будет трудно читать и неэффективно. Рассмотрим следующую программу:

#include <iostream>
 
void printDigitName(int x)
{
    if (x == 1)
        std::cout << "One";
    else if (x == 2)
        std::cout << "Two";
    else if (x == 3)
        std::cout << "Three";
    else
        std::cout << "Unknown";
}
 
int main()
{
    printDigitName(2);
 
    return 0;
}

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

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

#include <iostream>
 
void printDigitName(int x)
{
    switch (x)
    {
        case 1:
            std::cout << "One";
            return;
        case 2:
            std::cout << "Two";
            return;
        case 3:
            std::cout << "Three";
            return;
        default:
            std::cout << "Unknown";
            return;
    }
}
 
int main()
{
    printDigitName(2);
 
    return 0;
}

Идея оператора switch проста: выражение (иногда называемое условием) вычисляется для получения значения. Если значение выражения равно значению после любой из меток case, выполняются инструкции после соответствующей метки case. Если не удается найти соответствующее значение, и метка default (по умолчанию) существует, выполняются инструкции после метки default.

По сравнению с исходным оператором if преимущество оператора switch заключается в том, что он вычисляет выражение только один раз (что делает его более эффективным), и оператор switch также дает читателю понять, что это одно и то же выражение проверяется на равенство в каждом случае.

Лучшая практика


Если есть выбор, предпочитайте использовать операторы switch вместо цепочек if-else.

Давайте рассмотрим каждую из этих концепций более подробно.

Начало switch

Оператор switch мы начинаем с ключевого слова switch, за которым следует условное выражение в скобках, которое мы хотели бы вычислить. Часто выражение – это всего лишь одна переменная, но это может быть любое допустимое выражение.

Единственное ограничение заключается в том, что условие должно вычисляться как целочисленный тип (если вам нужно напоминание, какие базовые типы считаются целочисленными типами, просмотрите урок «4.1 – Введение в основные типы данных»). Небазовые типы, которые можно преобразовать в int (например, перечисляемые типы и некоторые классы), также допустимы. Выражения, которые вычисляются как типы с плавающей запятой, строки и другие нецелочисленные типы, здесь не могут использоваться.

Для продвинутых читателей


Почему switch допускает только целочисленные типы? Ответ в том, что операторы switch разработаны с учетом высокой степени оптимизации. Исторически сложилось так, что наиболее распространенный способ реализации компиляторами операторов switch – это таблицы переходов, а таблицы переходов работают только с целочисленными значениями.

Для тех из вас, кто уже знаком с массивами, таблица переходов работает во многом как массив, целочисленное значение используется в качестве индекса массива для «перехода» непосредственно к результату. Это может быть намного эффективнее, чем несколько последовательных сравнений.

Конечно, компиляторам не обязательно реализовывать switch с помощью таблиц переходов, и иногда они так и делают. Технически нет причин, по которым C++ не мог ослабить ограничение для switch на использование только целочисленных значений, просто это еще не сделано (по крайней мере, в C++20).

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

Метки case

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

Если значение условного выражения равно выражению после метки case, выполнение начинается с первой инструкции после этой метки case, а затем продолжается последовательно.

Вот пример условия, соответствующего метке case:

#include <iostream>
 
void printDigitName(int x)
{
    switch (x)        // x вычисляется для получения значения 2
    {
        case 1:
            std::cout << "One";
            return;
        case 2:       // что соответствует этому выражению case
            std::cout << "Two"; // поэтому выполнение начинается здесь
            return;   // и потом возвращаемся в вызывающую функцию
        case 3:
            std::cout << "Three";
            return;
        default:
            std::cout << "Unknown";
            return;
    }
}
 
int main()
{
    printDigitName(2);
 
    return 0;
}

Этот код печатает:

Two

В приведенной выше программе x вычисляется для получения значения 2. Поскольку имеется метка case со значением 2, выполнение переходит к инструкции под этой совпавшей меткой case. Программа выводит Two, а затем выполняется оператор return, который возвращает выполнение в вызывающую функцию.

Практического ограничения на количество меток case, которые вы можете иметь, не существует, но все метки case в switch должны быть уникальными. То есть так делать нельзя:

switch (x)
{
    case 54:
    case 54:  // ошибка: значение 54 уже использовано!
    case '6': // ошибка: '6' преобразуется в целое число 54, которое уже используется
}

Метка default

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

Вот пример условия, соответствующего метке по умолчанию:

#include <iostream>
 
void printDigitName(int x)
{
    switch (x) // x вычисляется для получения значения 5
    {
        case 1:
            std::cout << "One";
            return;
        case 2:
            std::cout << "Two";
            return;
        case 3:
            std::cout << "Three";
            return;
        default:     // что не соответствует ни одной метке case
            std::cout << "Unknown"; // поэтому выполнение начинается здесь
            return; // и потом возвращаемся в вызывающую функцию
    }
}
 
int main()
{
    printDigitName(5);
 
    return 0;
}

Этот код печатает:

Unknown

Метка по умолчанию является необязательной, и для каждого оператора switch может быть только одна метка default. По соглашению метка по умолчанию помещается в блок switch последней.

Лучшая практика


Помещайте метку default в блок switch последней.

Прекращение выполнения

В приведенных выше примерах мы использовали операторы return, чтобы остановить выполнение инструкций после наших меток. Однако это также приводит к выходу из всей функции.

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

Вот немного измененный пример, переписанный с использованием break вместо return:

#include <iostream>
 
void printDigitName(int x)
{
    switch (x) // x вычисляется как 3
    {
        case 1:
            std::cout << "One";
            break;
        case 2:
            std::cout << "Two";
            break;
        case 3:
            std::cout << "Three"; // выполнение начинается здесь
            break;                // переходим в конец блока switch
        default:
            std::cout << "Unknown";
            break;
    }
 
    // выполнение продолжается здесь
    std::cout << " Ah-Ah-Ah!";
}
 
int main()
{
    printDigitName(3);
 
    return 0;
}

Приведенный выше пример напечатает:

Three Ah-Ah-Ah!

Лучшая практика


Каждый набор инструкций под меткой должен заканчиваться инструкцией break или инструкцией return.

Итак, что произойдет, если вы не завершите набор инструкций под меткой с помощью break или return? Мы рассмотрим эту и другие темы в следующем уроке.

Теги

C++ / CppLearnCppswitchДля начинающихОбучениеПрограммированиеУсловный оператор

На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.

В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.