Какого типа должно быть выражение управляющее инструкцией switch

Оператор switch

Вторым оператором выбора в C# является оператор switch, который обеспечивает многонаправленное ветвление программы. Следовательно, этот оператор позволяет сделать выбор среди нескольких альтернативных вариантов дальнейшего выполнения программы. Несмотря на то, что многонаправленная проверка может быть организована с помощью последовательного ряда вложенных операторов if, во многих случаях более эффективным оказывается применение оператора switch. Этот оператор действует следующим образом. Значение выражения последовательно сравнивается с константами выбора из заданного списка. Как только будет обнаружено совпадение с одним из условий выбора, выполняется последовательность операторов, связанных с этим условием. Ниже приведена общая форма оператора switch.

switch(выражение) {
   case константа1:
      последовательность операторов
      break;
   case константа2:
      последовательность операторов
      break;
   case константа3:
      последовательность операторов
      break;
.
.
.
   default:
      последовательность операторов
      break;
}

Заданное выражение в операторе switch должно быть целочисленного типа (char, byte, short или int), перечислимого или же строкового. (О перечислениях и символьных строках типа string речь пойдет далее в этом курсе.) А выражения других типов, например с плавающей точкой, в операторе switch не допускаются.

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

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

Ниже приведен пример программы, в котором демонстрируется применение оператора switch.

// Продемонстрировать применение оператора switch,
using System;

class SwitchDemo {
  static void Main() {
    int i;

    for (i = 0; i < 10; i++)
      switch (i) {
        case 0:
          Console.WriteLine("i равно нулю");
          break;
        case 1:
          Console.WriteLine("i равно единице");
          break;
        case 2:
          Console.WriteLine("i равно двум");
          break;
        case 3:
          Console.WriteLine("i равно трем");
          break;
        case 4:
          Console.WriteLine("i равно четырем");
          break;
        default:
          Console.WriteLine("i равно или больше пяти");
          break;
      }
  }
}

Результат выполнения этой программы выглядит следующим образом.

i равно нулю
i равно единице
i равно двум
i равно трем
i равно четырем
i равно или больше пяти
i равно или больше пяти
i равно или больше пяти
i равно или больше пяти
i равно или больше пяти

Как видите, на каждом шаге цикла выполняются операторы, связанные с совпадающей константой выбора, в обход всех остальных операторов. Когда же значение переменной i становится равным или больше пяти, то оно не совпадает ни с одной из констант выбора, а следовательно, выполняются операторы из ветви default.

В приведенном выше примере оператором switch управляла переменная i типа int. Как пояснялось ранее, для управления оператором switch может быть использовано выражение любого целочисленного типа, включая и char. Ниже приведен пример применения выражения и констант выбора типа char в операторе switch.

// Использовать элементы типа char для управления оператором switch,

using System;

class SwitchDemo2 {
  static void Main() {
    char ch;

    for (ch = 'A'; ch <= 'E'; ch++)
      switch (ch) {
        case  'A':
          Console.WriteLine("ch содержит A");
          break;
        case 'B':
          Console.WriteLine("ch содержит B");
          break;
        case 'C':
          Console.WriteLine("ch содержит C");
          break;
        case 'D':
          Console.WriteLine("ch содержит D");
          break;
        case 'E':
          Console.WriteLine("ch содержит E");
          break;
      }
  }
}

Вот какой результат дает выполнение этой программы.

ch содержит A
ch содержит B
ch содержит C
ch содержит D
ch содержит E

Обратите в данном примере внимание на отсутствие ветви default в операторе switch. Напомним, что ветвь default не является обязательной. Когда она не нужна, ее можно просто опустить.

Переход последовательности операторов, связанных с одной ветвью case, в следующую ветвь case считается ошибкой, поскольку в C# должно непременно соблюдаться правило недопущения «провалов» в передаче управления ходом выполнения программы. Именно поэтому последовательность операторов в каждой ветви case оператора switch оканчивается оператором break. (Избежать подобных «провалов», можно также с помощью оператора безусловного перехода goto, рассматриваемого далее в этой главе, но для данной цели чаще применяется оператор break.)

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

Правило недопущения «провалов» относится к тем особенностям языка C#, которыми он отличается от C, C++ и Java. В этих языках программирования одна ветвь case может переходить (т. е. «проваливаться») в другую. Данное правило установлено в C# для ветвей case по двум причинам.

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

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

Несмотря на то, что правило недопущения «провалов» не допускает переход одной ветви case в другую, в двух или более ветвях case все же разрешается ссылаться с помощью меток на одну и ту же кодовую последовательность, как показано в следующем примере программы.

// Пример "проваливания" пустых ветвей case.

using System;

class EmptyCasesCanFall {
  static void Main() {
    int i;

    for (i = 1; i < 5; i++)
      switch (i) {
        case 1:
        case 2:
        case 3:
          Console.WriteLine("i равно 1, 2 или 3");
          break;
        case 4:
          Console.WriteLine("i равно 4");
          break;
      }
  }
}

Ниже приведен результат выполнения этой программы.

i равно 1, 2 или 3
i равно 1, 2 или 3
i равно 1, 2 или 3
i равно 4

Если значение переменной i в данном примере равно 1, 2 или 3, то выполняется первый оператор, содержащий вызов метода WriteLine(). Такое расположение нескольких меток ветвей case подряд не нарушает правило недопущения «провалов»; поскольку во всех этих ветвях используется одна и та же последовательность операторов.

Расположение нескольких меток ветвей case подряд зачастую применяется в том случае, если у нескольких ветвей имеется общий код. Благодаря этому исключается излишнее дублирование кодовых последовательностей.

Время на прочтение
11 мин

Количество просмотров 52K

Старый добрый switch был в Java с первого дня. Мы все используем его и привыкли к нему — особенно к его причудам (кого-нибудь еще раздражает break?). Но начиная с Java 12, ситуация начала меняться: switch вместо оператора стал выражением:

boolean result = switch(ternaryBool) {
    case TRUE -> true;
    case FALSE -> false;
    case FILE_NOT_FOUND -> throw new UncheckedIOException(
        "This is ridiculous!",
        new FileNotFoundException());
    default -> throw new IllegalArgumentException("Seriously?!");
};

Результат работы switch-выражения теперь можно сохранять в переменную; ушла необходимость использовать break в каждой ветке case благодаря лямбда-синтаксису и многое другое.

Когда дело доходит до switch после Java 14, необходимо выбрать стиль его использования:

  • оператор или выражение (с Java 14)
  • двоеточия или стрелки (с Java 14)
  • метки или шаблоны (3-й превью в Java 19)
    В этом руководстве я расскажу обо всем, что необходимо знать о switch-выражениях, и как их лучше всего использовать в современной Java.

Недостатки оператора switch

Прежде, чем мы перейдем к обзору нововведений, давайте рассмотрим один пример кода. Допустим, мы столкнулись с «ужасным» тернарным boolean и хотим преобразовать его в обычный boolean. Вот один из способов сделать это:

boolean result;
switch(ternaryBool) {
    case TRUE:
        result = true;
        break;
    case FALSE:
        result = false;
        break;
    case FILE_NOT_FOUND:
        // объявление переменной для демонстрации проблемы в default
        var ex = new UncheckedIOException("This is ridiculous!",
             new FileNotFoundException());
        throw ex;
    default:
        // А вот и проблема: мы не можем объявить еще одну переменную с именем ex
        var ex2 = new IllegalArgumentException("Seriously?!");
        throw ex2;
}

Реализация данного кода хромает: наличие break в каждой ветке, которые легко забыть; можно не учесть все возможные значения ternaryBool (забыть реализовать какой-то case); с переменной result не все гладко — область видимости не соответствует ее использованию; нельзя объявить в разных ветках переменные с одинаковым именем. Согласитесь, что данное решение выглядит крайне громоздко и неудобно — тут явно есть, что улучшить.

А вот пример попроще, демонстрирующий похожие проблемы:

int result;
switch (number) {
    case 1:
        result = callMethod("one");
        break;
    case 2:
        result = callMethod("two");
        break;
    default:
        result = callMethod("many");
        break;
}

Давайте попробуем устранить все недостатки, поместив switch в отдельный метод:

private static boolean toBoolean(Bool ternaryBool) {
    switch(ternaryBool) {
        case TRUE: return true;
        case FALSE: return false;
        case FILE_NOT_FOUND:
            throw new UncheckedIOException("This is ridiculous!",
                  new FileNotFoundException());
        // без default метод не скомпилируется
        default:
            throw new IllegalArgumentException("Seriously?!");
      }
}

Так намного лучше: отсутствует фиктивная переменная result, нет break, загромождающих код и сообщений компилятора об отсутствии default (даже если в этом нет необходимости, как в данном случае).

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

Представляем switch-выражения!

Начиная с Java 12 и выше, вы можете решить вышеуказанные проблемы следующим образом:

boolean result = switch(ternaryBool) {
    case TRUE -> true;
    case FALSE -> false;
    case FILE_NOT_FOUND -> throw new UncheckedIOException(
        "This is ridiculous!",
        new FileNotFoundException());
    // в ветке `default` уже нет необходимости
    default -> throw new IllegalArgumentException("Seriously?!");
};

Я думаю, что это довольно очевидно: если ternartBool равен TRUE, то result будет присвоено true, а FALSE становится false.

Сразу возникают две мысли:

  • switch теперь может иметь результат
  • какие возможности предоставляют стрелки?

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

Выражение vs оператора

Возможно, вы удивлены, что switch теперь является выражением. А чем же он был до этого? До Java 12 switch был оператором — императивной конструкцией, управляющей исполняющим потоком.

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

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

if(condition) {
    result = doThis();
} else {
    result = doThat();
}

result = condition ? doThis() : doThat();

То же самое и у switch: до Java 12, если вы хотели вычислить значение и сохранить результат, то должны были либо присвоить его переменной, либо вернуть из метода, созданного специально для оператора switch.

Теперь же результат вычислений оператора switch может быть присвоен переменной.

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

Стрелка vs двоеточия

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

boolean result = switch (ternaryBool) {
    case TRUE:
        yield true;
    case FALSE:
        yield false;
    case FILE_NOT_FOUND:
        throw new UncheckedIOException(
            "This is ridiculous!",
            new FileNotFoundException());
    default:
        throw new IllegalArgumentException("Seriously?!");
};

Обратите внимание, что вам нужно использовать новое ключевое слово yield, чтобы вернуть значение из ветки case (этот синтаксис появился в Java 13. В Java 12 вместо yield применялся break, т. е. break true вместо yield true, что выглядело странно).

Исторически сложилось, что метки с двоеточием определяют точку входа в блок операторов. С этого места начинается выполнение всего кода ниже, даже когда встречается другая метка (при отсутствии break). Механизм такой работы известен, как сквозной переход к следующему case. Для его прерывания нужен break или return.

Использование же стрелки позволяет выполнять только блок справа от нее. И никакого «проваливания».

Подробнее об эволюции switch

Несколько меток на case

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

switch (number) {
    case 1:
    case 2:
        callMethod("few");
        break;
    default:
        callMethod("many");
        break;
}

А в новом switch один case может соответствовать нескольким меткам:

String result = switch(ternaryBool) {
    case TRUE, FALSE -> "sane";
    default -> "insane";
};

Поведение этого кода очевидно: TRUE и FALSE приводят к одному и тому же результату — вычисляется выражение «sane».

Подробнее о стрелке

Начиная с Java 14, switch позволяет использовать лямбда-стрелку для «сопоставления» case с кодом:

switch (number) {
    case 1 -> callMethod("one");
    case 2 -> callMethod("two");
    default -> callMethod("many");
}

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

  • отсутствие сквозного перехода к следующему case
  • блоки операторов

Отсутствие сквозного перехода к следующему case

Вот, что говорится в JEP 325 об этом:

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

Я полностью согласен и приветствую возможность использовать switch без поведения по умолчанию:

switch(ternaryBool) {
    case TRUE, FALSE -> System.out.println("Bool was sane");
    default -> System.out.println("Bool was insane");
};

Стрелка позволяет вывести «Bool was sane» в единственном экземпляре, в то время, как с двоеточием это же сообщение отобразилось бы дважды.

Блоки операторов

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

boolean result = switch (Bool.random()) {
    case TRUE -> {
        System.out.println("Bool true");
        yield true;
    }
    case FALSE -> {
        System.out.println("Bool false");
        yield false;
    }
    case FILE_NOT_FOUND -> {
        var ex = new UncheckedIOException(
            "This is ridiculous!",
            new FileNotFoundException());
            throw ex;
    }
    default -> {
        var ex = new IllegalArgumentException(
            "Seriously?!");
            throw ex;
    }
};

Блоки необходимы для использования более одной строки кода в case. При этом они имеют дополнительное преимущество — позволяют создавать одинаковые имена переменных в разных ветках за счет локальной области видимости для каждой ветки.

Если вам показался необычным способ выхода из блоков с помощью yield, а не return, то это необходимо, чтобы избежать путаницы: return может быть неправильно истолкован, как выход из метода. Мы лишь завершаем работу switch, оставаясь в том же методе.

Подробнее о выражениях switch

И последнее, но не менее важное — особенности использования switch в качестве выражения:

  • множественные выражения
  • ранний возврат
  • охват всех значений (исчерпываемость)

Множественные выражения

Switch-выражения являются множественными выражениями. Это означает, что они не имеют своего собственного типа, но могут быть одним из нескольких типов. Наиболее часто в качестве таких выражений используются лямбда-выражения: s -> s + » «, могут быть и Function<String, String>, и Function<Serializable, и Object> или UnaryOperator.

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

String result = switch (ternaryBool) {
    case TRUE, FALSE -> "sane";
    default -> "insane";
};

Как итог — switch присваивается переменной String result. Следовательно, String является целевым типом, и все ветки должны возвращать результат этого типа.

То же самое происходит и здесь:

Serializable serializableMessage = switch (bool) {
    case TRUE, FALSE -> "sane";
    // note that we don't throw the exception!
    // but it's `Serializable`, so it matches the target type
    default -> new IllegalArgumentException("insane");
};

А что произойдет сейчас?

// compiler infers super type of `String` and
// `IllegalArgumentException` ~> `Serializable`
var serializableMessage = switch (bool) {
    case TRUE, FALSE -> "sane";
    // note that we don't throw the exception!
    default -> new IllegalArgumentException("insane");
};

Про применение типа var можно прочитать в статье: «26 рекомендаций по использованию типа var в Java».

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

Ранний возврат

Следствием различия между выражением и оператором switch является то, что вы можете использовать return для выхода из оператора switch:

public String sanity(Bool ternaryBool) {
    switch (ternaryBool) {
        // `return` is only possible from block
        case TRUE, FALSE -> { return "sane"; }
        default -> { return "This is ridiculous!"; }
    };
}

А вот внутри выражения использовать return уже не получится:

public String sanity(Bool ternaryBool) {
    String result = switch (ternaryBool) {
        // this does not compile - error:
        // "return outside of enclosing switch expression"
        case TRUE, FALSE -> { return "sane"; }
        default -> { return "This is ridiculous!"; }
    };
}

Это имеет смысл независимо от того, используете ли вы стрелку или двоеточие.

Охват всех значений (исчерпываемость)

Если вы используете switch в качестве оператора, тогда не имеет значения, охвачены все варианты или нет. Конечно, вы можете случайно пропустить case, и код будет работать неправильно, но компилятору все равно — вы, ваша IDE и ваши инструменты анализа кода останетесь с этим наедине.

Switch-выражения усугубляют эту проблему. Куда следует перейти switch, если нужная метка отсутствует? Единственный ответ, который может дать Java — это возвращать null для ссылочных типов и значение по умолчанию для примитивов. Это породило бы множество ошибок в основном коде.

Чтобы предотвратить такой исход, компилятор может помочь вам. Для switch-выражений компилятор будет настаивать, чтобы все возможные варианты были охвачены. Для каждого возможного значения переменной switch должна быть ветвь — это называется исчерпываемостью. Давайте посмотрим на пример, который может привести к ошибке компиляции:

// compile error:
// "the switch expression does not cover all possible input values"
boolean result = switch (ternaryBool) {
    case TRUE -> true;
    // no case for `FALSE`
    case FILE_NOT_FOUND -> throw new UncheckedIOException(
        "This is ridiculous!",
        new FileNotFoundException());
};

Интересным является следующее решение: добавление ветки default, конечно, исправит ошибку, но это не является единственным решением — еще можно добавить case для FALSE.

// compiles without `default` branch because
// all cases for `ternaryBool` are covered
boolean result = switch (ternaryBool) {
    case TRUE -> true;
    case FALSE -> false;
    case FILE_NOT_FOUND -> throw new UncheckedIOException(
        "This is ridiculous!",
        new FileNotFoundException());
};

Да, компилятор наконец-то сможет определить, охватываются ли все значения enum, что позволяет не использовать бесполезные значения в default!

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

Хотя, это все же вызывает один вопрос. Что делать, если кто-то возьмет и превратит сумасшедший Bool в кватернионный (с четырьмя значениями) Boolean, добавив четвертое значение? Если вы перекомпилируете switch-выражение для расширенного Bool, то получите ошибку компиляции (т. к. выражение больше не будет исчерпывающим). Чтобы отловить эту проблему, компилятор переходит в ветку default, которая ведет себя так же, как та, которую мы использовали до сих пор, вызывая исключение.

В настоящее время охват всех значений без ветки default работает только для enum, но когда switch в будущих версиях Java станет более мощным, он также сможет работать и с произвольными типами. Если метки case смогут не только проверять равенство, но и проводить сравнения (например _ < 5 -> …) — это позволит охватить все варианты для числовых типов.

Как пользоваться switch в современной Java

До этого мы рассматривали изменения, которые произошли до Java 14. Теперь обсудим то, что было реализовано после.

Паттерны (шаблоны)

Реализация сопоставления с образцом в switch все еще находится в процессе разработки, но есть три аспекта, которые особенно интересны по данной теме.

Паттерны типов

На момент написания статьи Java поддерживает только паттерны типов (Type Patterns) с паттернами деконструкции для записей (records), предложенными JEP 405. Их уже можно использовать в операторах if и switch:

Object obj = // ...

// работает с Java 16
if (obj instanceof String str)
    callStringMethod(str);
else if (obj instanceof Number no)
    callNumberMethod(no);
else
    callObjectMethod(obj);

// работает (как превью) с JDK 17+
switch (obj) {
    case String str -> callStringMethod(str);
    case Number no -> callNumberMethod(no);
    default -> callObjectMethod(obj);
}

Я думаю, что с такими возможностями switch станет более функциональным и интуитивным за счет того, что:

  • более четко выражает намерение выполнить ровно одну ветвь на основе свойств obj
  • компилятор проверяет исчерпываемость
  • если необходимо вычислить значение, то использование switch в качестве выражения является более кратким

Применение уточнений (Clauses)

Уточнения (ранее — guarded patterns) расширяют возможности паттерна с помощью дополнительных логических проверок. Это может быть представлено следующим образом (синтаксис, придуманный мной):

String str = // ...
String length = switch (str) {
    case str.length() > 42 -> "long";
    case str.length() > 19 -> "medium";
    case str.length() > 1 -> "small";
    case null || str.length() == 0 -> "empty";
};

По мере того, как switch становится все более мощным, я предполагаю, что он начнет поглощать части кода, для реализации которых используется if-else-if.

Выводы

Из статьи мы узнали, что Java превращает switch в выражение, наделяя его новыми возможностями:

  • теперь один case может соответствовать нескольким меткам
  • новая стрелочная форма case … -> … следует синтаксису лямбда-выражений:
    • допускаются однострочные операторы или блоки
    • предотвращается сквозной переход к следующему case
  • теперь все выражение оценивается, как значение, которое затем может быть присвоено переменной или передано, как часть более крупного оператора
  • множественное выражение: если целевой тип известен, то все ветки должны ему соответствовать. В противном случае определяется конкретный тип, который соответствует всем веткам
  • yield возвращает значение из блока
  • для выражения switch, использующее enum, компилятор проверяет охват всех его значений. Если default отсутствует, добавляется ветка, которая вызывает исключение
  • если шаблоны станут более функциональными, то они смогут сделать switch предпочтительнее if

Switch case statement evaluates a given expression and based on the evaluated value(matching a certain condition), it executes the statements associated with it. Basically, it is used to perform different actions based on different conditions(cases). 

  • Switch case statements follow a selection-control mechanism and allow a value to change control of execution.
  • They are a substitute for long if statements that compare a variable to several integral values.
  • The switch statement is a multiway branch statement. It provides an easy way to dispatch execution to different parts of code based on the value of the expression.

In C, the switch case statement is used for executing one condition from multiple conditions. It is similar to an if-else-if ladder.

The switch statement consists of conditional-based cases and a default case.

Syntax of switch Statement in C

switch(expression)
{
case value1: statement_1;
             break;
case value2: statement_2;
             break;
.
.
.
case value_n: statement_n;
              break;

default: default_statement;
}

How to use switch case Statement in C?

Before using the switch case in our program, we need to know about some rules of the switch statement.

Rules of the switch case statement

Following are some of the rules that we need to follow while using the switch statement:

  1. In a switch statement, the “case value” must be of “char” and “int” type.
  2. There can be one or N number of cases.
  3. The values in the case must be unique.
  4. Each statement of the case can have a break statement. It is optional.
  5. The default Statement is also optional.

Example

C

#include <stdio.h>

int main()

{

    int var = 1;

    switch (var) {

        case 1:

            printf("Case 1 is Matched.");

            break;

        case 2:

            printf("Case 2 is Matched.");

            break;

        case 3:

            printf("Case 3 is Matched.");

            break;

        default:

            printf("Default case is Matched.");

            break;

    }

    return 0;

}

How switch Statement Work?

The working of the switch statement in C is as follows:

  1. Step 1: The switch variable is evaluated.
  2. Step 2: The evaluated value is matched against all the present cases.
  3. Step 3A: If the matching case value is found, the associated code is executed.
  4. Step 3B: If the matching code is not found, then the default case is executed if present.
  5. Step 4A: If the break keyword is present in the case, then program control breaks out of the switch statement.
  6. Step 4B: If the break keyword is not present, then all the cases after the matching case are executed.
  7. Step 5: Statements after the switch statement are executed.

We can also understand the working of the switch statement in C using the flowchart.

Flowchart of Switch Statement

switch case in c

Flowchart of switch statement in C

Break in switch case

This keyword is used to stop the execution inside a switch block. It helps to terminate the switch block and break out of it. When a break statement is reached, the switch terminates, and the flow of control jumps to the next line following the switch statement.

The break statement is optional. If omitted, execution will continue on into the next case. The flow of control will fall through to subsequent cases until a break is reached.

Example of switch case without break

C

#include <stdio.h>

int main()

{

    int var = 2;

    switch (var) {

      case 1:

          printf("Case 1 is executed.n");

      case 2:

          printf("Case 2 is executed.n");

      case 3:

          printf("Case 3 is executed.");

      case 4:

          printf("Case 4 is executed.");

    }

    return 0;

}

Output

Case 2 is executed.
Case 3 is executed.Case 4 is executed.

Default in switch case

The default keyword is used to specify the set of statements to execute if there is no case match

It is optional to use the default keyword in a switch case. Even if the switch case statement does not have a default statement, it would run without any problem.

Important Points About Switch Case Statements

1. Switch expression should result in a constant value

If the expression provided in the switch statement does not result in a constant value, it would not be valid. Some valid expressions for switch case will be,

// Constant expressions allowed
switch(1+2+23)
switch(1*2+3%4)

// Variable expression are allowed provided
// they are assigned with fixed values
switch(a*b+c*d)
switch(a+b+c)

2. Expression value should be only of int or char type.

The switch statement can only evaluate the integer or character value. So the switch expression should return the values of type int or char only.

3. Case Values must be Unique

In the C switch statement, duplicate case values are not allowed.

3. Nesting of switch Statements

Nesting of switch statements is allowed, which means you can have switch statements inside another switch. However nested switch statements should be avoided as it makes the program more complex and less readable.

Examples of switch Statement in C

Example 1:  C Program to print the day of the week using a switch case.

C

#include <stdio.h>

int main()

{

    int day = 2;

    printf("The day with number %d is ", day);

    switch (day) {

      case 1:

          printf("Monday");

          break;

      case 2:

          printf("Tuesday");

          break;

      case 3:

          printf("Wednesday");

          break;

      case 4:

          printf("Thursday");

          break;

      case 5:

          printf("Thursday");

          break;

      case 6:

          printf("Thursday");

          break;

      case 7:

          printf("Thursday");

          break;

      default:

          printf("Invalid Input");

          break;

      }

    return 0;

}

Output

The day with number 2 is Tuesday

Example 2: Simple Calculator using switch case in C

C

#include <stdio.h>

#include <stdlib.h>

int main()

{

    char choice;

    int x, y;

    while (1) {

        printf("Enter the Operator (+,-,*,/)nEnter x to "

               "exitn");

        scanf(" %c", &choice);

        if (choice == 'x') {

            exit(0);

        }

        printf("Enter the two numbers: ");

        scanf("%d %d", &x, &y);

        switch (choice) {

          case '+':

              printf("%d + %d = %dn", x, y, x + y);

              break;

          case '-':

              printf("%d - %d = %dn", x, y, x - y);

              break;

          case '*':

              printf("%d * %d = %dn", x, y, x * y);

              break;

          case '/':

              printf("%d / %d = %dn", x, y, x / y);

              break;

          default:

              printf("Invalid Operator Inputn");

        }

    }

    return 0;

}

Output

Enter the operator (+, -, *, /)

Enter x to exit

+
Enter the two numbers: 100 + 200
100 + 200 = 300

Advantages of C switch Statement

  1. Easier to read than if else if.
  2. Easier to debug and maintain for a large number of conditions.
  3. Faster execution speed.

Disadvantages of C switch Statement

  1. Switch case can only evaluate int or char type.
  2. No support for logical expressions.
  3. Have to keep in mind to add a break in every case.

Conclusion

In this article, we discussed the switch statement in C programming and how to use it. It is a conditional statement like the if-else-if ladder having its own merits and demerits. It is mostly preferred when the number of conditions to evaluate is large.

FAQs on C switch Statement

1. What is the switch case in C?

The switch case statement is a flow control statement in which we can define a switch variable and then execute different code based on the value of the switch variable. It is an alternative of if else if ladder.

2. What is the case in the switch statement in C?

The case keyword is used to define the different cases and their associated code in the switch statement.

3. What does the break in the switch case do?

The break keyword is used to exit the switch block after executing the matching case.

4. What are the differences between switch and if else if ladder in C?

Following are the main differences between C switch and C if else if ladder:

switch

if else if

It executes the different cases on the basis of the value of the switch variable. It executes the different blocks based on the condition specified.
It can only evaluate the int or char type expressions. It can evaluate any type of expression.
Faster and easier to read for the large number of conditions. It can get messy when there are lots of conditions.

Must Read:

  • Interesting Facts About Switch Case in C
  • What should be Data type of Case Labels of Switch Statement in C?
  • Print Individual Digits as Words Without Using if or Switch

Last Updated :
30 Mar, 2023

Like Article

Save Article

В этом руководстве вы научитесь использовать оператор switch в Java для управления потоком выполнения вашей программы.

В Java мы использовали if..else..if для выполнения блока кода среди нескольких блоков. Однако синтаксис if … else … if слишком длинный.

Следовательно, мы можем использовать оператор switch вместо длинных if … else … if. Использование операторов switch делает наш код более читабельным.

Switch эволюционировал со временем – были добавлены новые поддерживаемые типы, особенно в Java 5 и 7. Кроме того, он продолжает развиваться – выражения switch, вероятно, будут введены в Java 12.

switch (variable/expression) {
case value1:
   // statements of case1
   break;

case value2:
   // statements of case2
   break;

   .. .. ...
   .. .. ...

default:
   // default statements
}

Оператор switch оценивает выражение (в основном переменное) и сравнивает его со значениями (могут быть выражениями) каждой метки case.

Теперь, если значение соответствует определенной метке case, то выполняются все операторы соответствующей метки case.

Например, если переменная / выражение равно значению 2. В этом случае выполняются все операторы этого совпадающего case (операторы case2).

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

Это важно, потому что если break не используется, все операторы после соответствующего case выполняются последовательно до конца оператора switch.

Что следует помнить:

  • Для выражения переключения может быть одно или N значений case.
  • Значение case должно быть только типа выражения переключения. Значение case должно быть буквальным или постоянным. Он не допускает переменных.
  • Значения case должны быть уникальными. В случае повторяющегося значения он отображает ошибку времени компиляции.
  • Выражение переключателя Java должно быть byte, short, int, long, перечислением и строкой.
  • Каждый оператор case может иметь оператор break, который не является обязательным.
  • Когда элемент управления достигает оператора break, он переходит к элементу управления после выражения switch.
  • Также case может иметь метку по умолчанию, которая является необязательной.

блок схема switch

Пример 1

class Main {
    public static void main(String[] args) {

        int week = 4;
        String day;

        // switch statement to check day
        switch (week) {
            case 1:
                day = "Sunday";
                break;
            case 2:
                day = "Monday";
                break;
            case 3:
                day = "Tuesday";
                break;

            // match the value of week
            case 4:
                day = "Wednesday";
                break;
            case 5:
                day = "Thursday";
                break;
            case 6:
                day = "Friday";
                break;
            case 7:
                day = "Saturday";
                break;
            default:
                day = "Invalid day";
                break;
        }
        System.out.println("The day is " + day);
    }
}

Вывод:

The day is Wednesday

В приведенном выше примере мы использовали оператор switch, чтобы узнать день недели. Здесь у нас есть переменная неделя, которая содержит целочисленное значение. Значение сравнивается с каждым случаем внутри блока переключателя.

Здесь значение недели равно 4. Следовательно, оно соответствует случаю 4. Таким образом, выполняется оператор внутри случая 4.

Пример 2: создание калькулятора с использованием оператора switch

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

Перед тем как приступить к работе с программой, убедитесь, что вы знаете о Java Scanner, чтобы принимать данные от пользователя.

import java.util.Scanner;

class Main {
    public static void main(String[] args) {

        char operator;
        Double number1, number2, result;

        // create an object of Scanner class
        Scanner scanner = new Scanner(System.in);
        System.out.print("Enter operator (either +, -, * or /): ");

        // ask user to enter operator
        operator = scanner.next().charAt(0);
        System.out.print("Enter number1 and number2 respectively: ");

        // ask user to enter numbers
        number1 = scanner.nextDouble();
        number2 = scanner.nextDouble();

        switch (operator) {

            // performs addition between numbers
            case '+':
                result = number1 + number2;
                System.out.print(number1 + "+" + number2 + " = " + result);
                break;

            // performs subtraction between numbers
            case '-':
                result = number1 - number2;
                System.out.print(number1 + "-" + number2 + " = " + result);
                break;

            // performs multiplication between numbers
            case '*':
                result = number1 * number2;
                System.out.print(number1 + "*" + number2 + " = " + result);
                break;

            // performs division between numbers
            case '/':
                result = number1 / number2;
                System.out.print(number1 + "/" + number2 + " = " + result);
                break;

            default:
                System.out.println("Invalid operator!");
                break;
        }
    }
}

Вывод:
Enter operator (either +, -, * or /): *
Enter number1 and number2 respectively: 1.4
-5.3
1.4*-5.3 = -7.419999999999999

В приведенном выше примере мы использовали оператор switch для создания калькулятора на Java. Он выполняет расчет на основе оператора, предоставленного пользователем.

Пример 3

Допустим, у нас есть следующие вложенные операторы if-else:

public String exampleOfIF(String animal) {
    String result;
    if (animal.equals("DOG") || animal.equals("CAT")) {
        result = "domestic animal";
    } else if (animal.equals("TIGER")) {
        result = "wild animal";
    } else {
        result = "unknown animal";
    }
    return result;
}

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

public String exampleOfSwitch(String animal) {
    String result;
    switch (animal) {
        case "DOG":
            result = "domestic animal"; 
            break;
        case "CAT":
            result = "domestic animal";
            break;
        case "TIGER":
            result = "wild animal";
            break;
        default:
            result = "unknown animal";
            break;
    }
    return result;
}

Как показано выше, мы сравниваем animal аргумента switch с несколькими значениями case. Если ни одно из значений case не равно аргументу, то выполняется блок под меткой по умолчанию.

Проще говоря, оператор break используется для выхода из оператора switch.

Заявление break

Хотя большинство операторов switch в реальной жизни подразумевают, что должен быть выполнен только один из блоков case, оператор break необходим для выхода из switch после завершения блока.

Чтобы продемонстрировать это, опустим операторы break и добавим вывод в консоль для каждого блока:

public String forgetBreakInSwitch(String animal) {
    switch (animal) {
    case "DOG":
        System.out.println("domestic animal");
    default:
        System.out.println("unknown animal");
    }
}

Давайте выполним этот код ForgotBreakInSwitch(«DOG») и проверим вывод, чтобы убедиться, что все блоки выполняются:

domestic animal
unknown animal

Поэтому нам следует быть осторожными и добавлять операторы break в конце каждого блока, если нет необходимости переходить к коду под следующей меткой.

Единственный блок, в котором не требуется разрыв, – это последний, но добавление разрыва к последнему блоку делает код менее подверженным ошибкам.

Мы также можем воспользоваться этим поведением, чтобы опустить break, когда мы хотим, чтобы один и тот же код выполнялся для нескольких операторов case. Давайте перепишем пример из предыдущего раздела, сгруппировав первые 2 случая:

public String exampleOfSwitch(String animal) {
    String result;
    switch (animal) {
        case "DOG":
        case "CAT":
            result = "domestic animal";
            break;
        case "TIGER":
            result = "wild animal";
            break;
        default:
            result = "unknown animal";
            break;
    }
    return result;
}

Переключение Аргумента и регистра значений

Теперь давайте обсудим допустимые типы аргументов switch и значений case, требования к ним и то, как оператор switch работает со строками.

Типы данных

Мы не можем сравнивать все типы объектов и примитивов в операторе switch. Переключатель работает только с четырьмя примитивами и их оболочками, а также с типом перечисления и классом String:

  • byte and Byte;
  • short and Short;
  • int and Integer;
  • char and Character;
  • enum;
  • String.

Тип String доступен в операторе switch, начиная с Java 7. Тип перечисления был представлен в Java 5 и с тех пор доступен в операторе switch. Классы-оболочки также доступны с Java 5.

Конечно, аргументы переключателя и значения case должны быть одного типа.

Нет null значений

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

@Test(expected=NullPointerException.class)
public void whenSwitchAgumentIsNull_thenNullPointerException() {
    String animal = null;
    Assert.assertEquals("domestic animal", s.exampleOfSwitch(animal));
}

Конечно, мы также не можем передать null как значение в метку case оператора switch. Если мы это сделаем, код не скомпилируется.

Значения регистра как константы времени компиляции

Если мы попытаемся заменить значение case DOG переменной dog, код не будет компилироваться, пока мы не отметим переменную dog как final:

final String dog="DOG";
String cat="CAT";
 
switch (animal) {
case dog: //compiles
    result = "domestic animal";
case cat: //does not compile
    result = "feline"
}

Сравнение строк

Если оператор switch использовал оператор равенства для сравнения строк, мы не смогли бы правильно сравнить аргумент String, созданный с помощью оператора new, со значением case String.

К счастью, оператор switch использует скрытый метод equals().

@Test
public void whenCompareStrings_thenByEqual() {
String animal = new String("DOG");
assertEquals("domestic animal", s.exampleOfSwitch(animal));
}

switch выражения

JDK 13 теперь доступен и содержит улучшенную версию новой функции, впервые представленной в JDK 12: выражения switch.

Чтобы включить его, нам нужно передать компилятору параметр –enable-preview.

Новое выражение переключателя

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

var result = switch(month) {
    case JANUARY, JUNE, JULY -> 3;
    case FEBRUARY, SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER -> 1;
    case MARCH, MAY, APRIL, AUGUST -> 2;
    default -> 0; 
};

Отправка значения типа Month.JUNE установит результат в 3.

Обратите внимание, что новый синтаксис использует оператор -> вместо двоеточия, которое мы использовали с операторами switch. Кроме того, нет ключевого слова break: выражение switch не пропускает регистры. Еще одним дополнением является тот факт, что теперь у нас могут быть критерии, разделенные запятыми.

Ключевое слово yield

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

var result = switch (month) {
    case JANUARY, JUNE, JULY -> 3;
    case FEBRUARY, SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER -> 1;
    case MARCH, MAY, APRIL, AUGUST -> {
        int monthLength = month.toString().length();
        yield monthLength * 4;
    }
    default -> 0;
};

Возврат внутри выражения переключателя

Вследствие различия между операторами switch и выражениями переключения, можно вернуться изнутри оператора, но нам не разрешено делать это из выражения.

Следующий пример вполне допустим и будет компилироваться:

switch (month) {
    case JANUARY, JUNE, JULY -> { return 3; }
    default -> { return 0; }
}

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

var result = switch (month) {
    case JANUARY, JUNE, JULY -> { return 3; }
    default -> { return 0; }
};

При использовании операторов switch на самом деле не имеет значения, охвачены ли все случаи.

Следующий код, например, имеет место быть:

switch (month) { 
    case JANUARY, JUNE, JULY -> 3; 
    case FEBRUARY, SEPTEMBER -> 1;
}

Однако для выражений переключения компилятор настаивает на том, чтобы были учтены все возможные случаи. Следующий фрагмент кода, например, не будет компилироваться, так как нет случая по умолчанию и не все возможные случаи рассмотрены:

var result = switch (month) {
    case JANUARY, JUNE, JULY -> 3;
    case FEBRUARY, SEPTEMBER -> 1;
}

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

var result = switch (month) {
    case JANUARY, JUNE, JULY -> 3;
    case FEBRUARY, SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER -> 1;
    case MARCH, MAY, APRIL, AUGUST -> 2;
}

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

Последнее обновление: 20.02.2023

Конструкция switch-case позволяет сравнить некоторое выражение с набором значений. Она имеет следующую форму:

switch(выражение)
{
	case значение_1: инструкции_1;
	case значение_2: инструкции_2;
    ...................
	case значение_N: инструкции_N;
	
	default: инструкции;
}

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

Стоит отметить, что сравниваемое выражение в switch должно представлять один из целочисленных или символьных типов или перечисление (рассматриваются далее).

В конце конструкции switch может стоять блок default. Он необязателен и выполняется в том случае, если значение после switch не соответствует ни одному из операторов case. Например:

#include <iostream>

int main()
{
	int x {2};
     
    switch(x)
    {
        case 1: 
            std::cout << "x = 1" << "n";
            break;
        case 2: 
            std::cout << "x = 2" << "n";
            break;
        case 3: 
            std::cout << "x = 3" << "n";
            break;
        default: 
            std::cout << "x is undefined" << "n";
            break;
    }
}

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

case 2: 
	std::cout << "x = 2" << "n";
	break;

После выполнения оператора break произойдет выход из конструкции switch..case, и остальные операторы case будут проигнорированы.
Поэтому на консоль будет выведена следующая строка

x = 2

Стоит отметить важность использования оператора break. Если мы его не укажем в блоке case, то после этого блока выполнение перейдет к следующему блоку case. Например,
уберем из предыдущего примера все операторы break:

#include <iostream>

int main()
{
	int x {2};
     
    switch(x)
    {
        case 1: 
            std::cout << "x = 1" << "n";
        case 2: 
            std::cout << "x = 2" << "n";
        case 3: 
            std::cout << "x = 3" << "n";
        default: 
            std::cout << "x is undefined" << "n";
    }
}

В этом случае опять же будет выполняться оператор case 2:, так как переменная x=2. Однако так как этот блок case не завершается
оператором break, то после его завершения будет выполняться набор инструкций после case 3: даже несмотря на то, что переменная x по прежнему равна 2.
В итоге мы получим следующий консольный вывод:

x = 2
x = 3
x is undefined

Совмещение условий

Можно определять для нескольких меток case один набор инструкций:

#include <iostream>
 
int main()
{
    int x {2};
      
    switch(x)
    {
        case 1:
        case 2: 
            std::cout << "x = 1 or 2" << "n";
            break;
        case 3: 
        case 4:
            std::cout << "x = 3 or 5" << "n";
            break;
        case 5:
            std::cout << "x = 5" << "n";
            break;
    }
}

Здесь если x=1 или x=2, то выполняется одна и та же инструкция std::cout << "x = 1 or 2" << "n". Аналогично для вариантов
x=3 и x=4 также определена общая инструкция.

Переменные в блоках case

Определение переменных в блоках case, возможно, встречается нечасто. Однако может вызвать затруднения. Так, если переменная определяется в блоке case, то все инструкции
блока помещаются в фигурные скобки (для блока default это не требуется):

#include <iostream>
 
int main()
{
    int x {2};
      
    switch(x)
    {
        case 1:
        {
            int a{10};
            std::cout << a << std::endl;
            break;
        }
        case 2:
        {
            int b{20};
            std::cout << b << std::endl;
            break;
        }
        default:
            int c{30};
            std::cout << c << std::endl;
    }
}

Блок switch с инициализацией переменной

Иногда в конструкции switch для различных промежуточных вычислений необходимо определить переменную. Для этой цели начиная со стандарта
C++17 язык С++ поддерживает особую форму конструкции switch:

switch(инициализация; выражение) 
{
    // ..........
}

Подобная форма также принимает выражение, значение которого сравнивается с константами после операторов case. Но теперь перед выражением еще может идти
определение и инициализация переменной. Например:

#include <iostream>
 
int main()
{
    char op = '+';
    int n = 10;
    switch(int k{2}; op)
    {
        case '+':
            std::cout << n + k << std::endl;
            break;
        case '-':
            std::cout << n - k << std::endl;
            break;
        case '*':
            std::cout << n * k << std::endl;
            break;
    }
}

В данном случае в конструкции switch определяется переменная k, которая доступна только в рамках этой конструкции switch. В качестве выражения
используется значение переменной op, которая представляет знак операции. И в зависимости от этого значения, выполняем определенную операцию с переменными n и k.

Понравилась статья? Поделить с друзьями:
  • Виферон мазь инструкция по применению цена для детей
  • Детская инфекционная больница руководство
  • Руководства по эксплуатации автогидроподъемника
  • Кордафлекс инструкция по применению цена таблетки 20мг
  • Bosch sportline стиральная машина инструкция по применению на русском