Урок №65. Оператор switch

  Юрий  | 

  |

  Обновл. 20 Дек 2022  | 

 350224

 ǀ   70 

На этом уроке мы рассмотрим еще один оператор управления потоком выполнения программы — оператор switch, а также то, зачем его использовать и как это делать эффективно.

Зачем использовать оператор switch?

Хоть мы и можем использовать сразу несколько операторов if/else вместе — читается и смотрится это не очень. Например:

Использование ветвления if/else для проверки значения одной переменной — практика распространенная, но язык C++ предоставляет альтернативный и более эффективный условный оператор ветвления switch. Вот вышеприведенная программа, но уже с использованием оператора switch:

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

Из-за своей реализации, операторы switch обычно более эффективны, чем цепочки if/else. Давайте рассмотрим это более подробно.

Оператор switch


Сначала пишем ключевое слово switch за которым следует выражение, с которым мы хотим работать. Обычно это выражение представляет собой только одну переменную, но это может быть и нечто более сложное, например, nX + 2 или nX − nY. Единственное ограничение к этому выражению — оно должно быть интегрального типа данных (т.е. типа char, short, int, long, long long или enum). Переменные типа с плавающей точкой или неинтегральные типы использоваться не могут.

После выражения switch мы объявляем блок. Внутри блока мы используем лейблы (англ. «labels») для определения всех значений, которые мы хотим проверять на соответствие выражению. Существуют два типа лейблов.

Лейблы case

Первый вид лейбла — это case (или просто «кейс»), который объявляется с использованием ключевого слова case и имеет константное выражение. Константное выражение — это то, которое генерирует константное значение, другими словами: либо литерал (например, 5), либо перечисление (например, COLOR_RED), либо константу (например, переменную x, которая была объявлена с ключевым словом const).

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

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

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

В случае, если p является числом из ASCII-таблицы, то выполнится первый стейтмент после кейса — return true;.

Лейбл по умолчанию


Второй тип лейбла — это лейбл по умолчанию (так называемый «default case»), который объявляется с использованием ключевого слова default. Код под этим лейблом выполняется, если ни один из кейсов не соответствует выражению switch. Лейбл по умолчанию является необязательным. В одном switch может быть только один default. Обычно его объявляют самым последним в блоке switch.

В вышеприведенном примере, если p не является числом из ASCII-таблицы, то тогда выполняется лейбл по умолчанию и возвращается false.

switch и fall-through

Одна из самых каверзных вещей в switch — это последовательность выполнения кода. Когда кейс совпал (или выполняется default), то выполнение начинается с первого стейтмента, который находится после соответствующего кейса и продолжается до тех пор, пока не будет выполнено одно из следующих условий завершения:

   Достигнут конец блока switch.

   Выполняется оператор return.

   Выполняется оператор goto.

   Выполняется оператор break.

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

Результат:

2
3
4
5

А это точно не то, что нам нужно! Когда выполнение переходит из одного кейса в следующий, то это называется fall-through. Программисты почти никогда не используют fall-through, поэтому в редких случаях, когда это все-таки используется — программист оставляет комментарий, в котором сообщает, что fall-through является преднамеренным.

switch и оператор break


Оператор break (объявленный с использованием ключевого слова break) сообщает компилятору, что мы уже сделали всё, что хотели с определенным switch (или циклом while, do while или for) и больше не намерены с ним работать. Когда компилятор встречает оператор break, то выполнение кода переходит из switch на следующую строку после блока switch. Рассмотрим вышеприведенный пример, но уже с корректно вставленными операторами break:

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

Предупреждение: Не забывайте использовать оператор break в конце каждого кейса. Его отсутствие — одна из наиболее распространенных ошибок новичков!

Несколько стейтментов внутри блока switch

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

Объявление переменной и её инициализация внутри case

Вы можете объявлять, но не инициализировать переменные внутри блока case:

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

Это может показаться немного нелогичным, поэтому давайте рассмотрим это детально. Когда мы определяем локальную переменную, например, int y;, то переменная не создается в этой точке — она ​​фактически создается в начале блока, в котором объявлена. Однако, она не видна в программе до точки объявления. Само объявление не выполняется, оно просто сообщает компилятору, что переменная уже может использоваться в коде. Поэтому переменная, объявленная в одном кейсе, может использоваться в другом кейсе, даже если кейс, объявляющий переменную, никогда не выполняется.

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

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

Правило: Если нужно инициализировать и/или объявить переменные внутри кейса — используйте блоки стейтментов.

Примечание: Также Вы можете приобрести Самоучитель «Уроки по С++» в .pdf-формате.

Тест

Задание №1

Напишите функцию calculate(), которая принимает две переменные типа int и одну переменную типа char, которая, в свою очередь, представляет одну из следующих математических операций: +, -, *, / или % (остаток от числа). Используйте switch для выполнения соответствующей математической операции над целыми числами, а результат возвращайте обратно в main(). Если в функцию передается недействительный математический оператор, то функция должна выводить ошибку. С оператором деления выполняйте целочисленное деление.

Ответ №1

Задание №2

Определите перечисление (или класс enum) Animal, которое содержит следующих животных: pig, chicken, goat, cat, dog и ostrich. Напишите функцию getAnimalName(), которая принимает параметр Animal и использует switch для возврата типа животного в качестве строки. Напишите еще одну функцию — printNumberOfLegs(), которая использует switch для вывода количества лап соответствующего типа животного. Убедитесь, что обе функции имеют кейс default, который выводит сообщение об ошибке. Вызовите printNumberOfLegs() в main(), используя в качестве параметров cat и chicken.

Пример результата выполнения вашей программы:

A cat has 4 legs.
A chicken has 2 legs.

Ответ №2

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (437 оценок, среднее: 4,86 из 5)
Загрузка...

Комментариев: 70

  1. Сашуня:

    Задание 1:

    Второе задание:

  2. SOM:

    Друге завдання

  3. Ivan:

    В первом задании сделал чуть сложнее, чем в ответе — в main добавил один if, который проверял знак и если знак был ‘/’, то вызывалась введенные переменные переводились в double и вызывалась функция для деления, возвращающая тип double. Иначе то решение, что есть, при вводе 3, к примеру, и 5 и потом выбирая / выдает 0 в ответе. А у меня — 0,6

    1. Golem:

      После return в case не нужно писать break, так как перехода к следующему case не будет — switch закончится на return

  4. Александр:

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

  5. Alexjazz:

    задание 2

  6. Alexjazz:

    задание 1

  7. Дмитрий:

  8. Gr1nya:

    1.

    2.

  9. ОЛЕГ-777:

    Выполнил задание 2, но добавил функцию выбора животного пользователем, чтобы программа приобрела больше смысла. Но странное дело, пришлось поменять целочисленные значения перечислителей. Когда счет начинался с "0", то на введение точки, скобки или чего нибудь другого (но не цифры) реагировал выбор PIG, который был равен 0. А когда нумерация пошла с 1, то все стало хорошо функционировать.
    Почему так произошло???.

    Задание 2:

    main.ccp:

    1. Fray:

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

      P.S.: в кейсах уже присутствует оператор return с определенным значением, соответственно оператор break уже можно не писать я думаю.

      1. Fray:

        Так оно и оказалось.
        Если пользователь введет число, не соответствующее типу данных переменной, то оно интерпретируется как 0.
        Если присвоить переменной double через cin просто букву, то компилятор воспринимает это число как за 0. Это можно понять, если вывести переменную через cout.

  10. Oleg:

    Первая задача

  11. Дмитрий:

    В ответе на задание 1 в функции int calculate(int x, int y, char op) есть строка :

    Почему при вводе некоректного оператора на экран выводится 0 , а не текст : "calculate(): Unhandled case" ?

  12. Алексей:

    А вдруг кому-то пригодится 🙂
    Добавил случай, когда используется default и возврат кода ошибки при завершении программы.

  13. Алексей:

    Добавил возврат кода ошибки (-1) при незнакомой операции.

  14. Андрей:

    народ, не пойму что за черное колдунство?

  15. Юрий:

    2 задача

  16. Инкогнито:

    Знаю, что структура не самая лучшая, но не понимаю из-за чего выдает ошибку:
    46 строка: switch (name)
    name — expression must have integral or enum type;
    И к каждому кейсу этой же функции — this constant expression has type "const char *" instead of the required "std::string" type

    1. Inkogito:

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

  17. Ruslan:

    Задание №2

  18. Ruslan:

    Задание №1

  19. Rock:

  20. Viktor:

    Так и не понятно, можно инициализировать переменную в "case" или нет!? Автор пишет что нет и даёт объяснение, что "case" может не сработать. Каким образом тогда, в следующем абзаце идёт речь об инициализации внутри блока "case" ? Как блок может снимать запрет!?

    1. Александр:

      У блока локальная область видимости

    2. Николай:

      Switch-case это тоже блок, поэтому язык программирования С++ не запрещает объявить переменную в любом месте данного блока, при этом действует правило, что переменна должна быть объявлена в том же блоке до её инициализации. Т.е. следующий код будет работать:

      Такой код, в принципе, допустим. А вот объявление переменных внутри блока оператора case — это дурной тон из-за того, что код читается компилятором сверху вниз.

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

      Если переменная и объявляется, то только сразу после switch, либо внутри блока кода ({ }). Второй вариант предпочтительнее.

      Язык С++ предоставляет широкий спектр возможностей с минимальными ограничениями, поэтому им нужно пользоваться очень осторожно.

  21. Onium:

    Написал второе задание через структуры

  22. Яна:

    Имеет ли жизнь такой вариант? Вроде как сразу видно что выводится. Или функцию main() лучше не засорять тем, что можно сделать в функциях об назв. животных и их лап?

    1. Сергей:

      Коза обычно имеет 4 "лапы". Может она травмирована?

  23. Борис:

    Первый пример в статье: как switch может быть эффективнее, если с ним аж на 7 строк больше? 🙂 Притом что читабельность совсем не улучшилась (а может, даже ухудшилась из-за постоянных break). Очень редко юзаю switch.

  24. mloborev:

  25. Inviser666:

    №1. по-моему, неплохо)

    1. Константин:

      ключевое слово break -то где, а?

      1. Inviser666:

        а зачем писать break после return?

        1. Константин:

          хочешь по лучше спрятать вещь — поставь её на самое видное место — ретурна я то и не просёк:-)

        2. foo:

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

  26. armus1:

  27. armus1:

  28. Алексей:

    Добрый день Юрий.

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

  29. Игорь:

    1. Павел:

      Очень длинная строка, а нельзя просто отправить проверить в списке enum не перечисляя каждое животное?

  30. Игорь:

  31. zashiki:

    т.е. если в кейсах есть оператор возврата return, то оператор break не нужен?

  32. Максим:

    Оцените пожалуйста

    Задание 1:

    Задание 2 (я усовершенствовал до ввода пользователем данных)

    P.S. Я в курсе, что так не желательно делать, но ситуация требовала

    code типа short

  33. Алексей:

    Да, это нечто.

    Колупал не слабо 2е задание с этим перечислителями и enum.
    Разобрался все же.

    Нашел интересную особенность. Если enum с классом, то код весь верный, хотя "enum class" нету, только "enum". Все компилирует.

    Господи, простейшие программы для теста. Ото бы не тупил, все элементарно.

  34. Алексей:

    Намного проще, чем думал и писал.

    Правда почему здесь нету в конце "break;"?

    Это же ошибка.

    1. Борис:

      Какая ошибка? Там return'ы стоят. А они завершают конструкцию switch аналогично break'у. Поэтому break и не нужны там.

  35. Алексей:

    Немного доработал, ибо выполняло в любом случаи.

    Единственный вопрос — как в if это оптимизировать. Сделать массив из значений для op.

  36. Михаил:

    1. Nikita:

      printNumberOfLegs получается void

      1. Михаил:

        Точно, пропустил этот момент. Но программа работает и так.

  37. Анна:

    Зачем в switch расписывать default, если с неправильным параметром функция просто не запустится? Выходит, что default не к чему.

    1. Фото аватара Юрий:

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

  38. Владимир:

    Задание №2

  39. Вячеслав:

    вот так я выполнил второе задание:

  40. Вячеслав:

    вот так получилось первое задание :

    1. Игорь:

      А если в Вашу функцию передать 0?

  41. Andrey:

    У меня QT ругается, если в функцию с возвратом стринг, засунуть только свитч:
    switch control reaches end of non-void function [-Wreturn-type]

    Вот сам кусочек кода.

    То есть он не видит возврата внутри switch?
    А еще у меня тут нет default: потому что на него тоже фреймворк ругается, говорит что ты учел все позиции из enum class Animal и default тебе не нужен, он не будет использован.

    1. Евгений:

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

  42. Роман:

    я один не понимаю смысл использования перечислений?

    1. Борис:

      Я тоже их недолюбливаю, и никогда не использовал. Хотя, не отрицаю, что иногда с ними удобнее.

  43. smigles:

    Зачем использовать break после default, если switch заканчивается?

  44. smigles:

    Зачем использовать break в default, если после него switch заканчивается?

    1. Евгений:

      Обычно break после default и не ставят

  45. Алибек:

    Не лучше ли инициализировать и\или объявлять переменные до лейбла case?

    1. Фото аватара Юрий:

      В статье рассматривается как можно делать. Как делать конкретно вам — дело ваше.

  46. Юрий:

    Работает тоже, но вопрос другой не совсем пойму как работает std::cin, если ввести "10 +50" или "10+50" или 10 + 50" или любое количество пробелов, ответ будет правильным. Std::cin отбрасывает все пробелы? пробел это не char?

    1. Фото аватара Юрий:

      У вас в программе 3 cin-а подряд. Если вы ввели первое значение, а затем пробелы, то для первого cin эти пробелы означают окончание потока входных данных. Затем у вас сразу же после пробелов и появления второго значения срабатывает второй cin (в который записывается только второе значение, без пробелов), затем третий.

      Если вы для одного cin введете число 3, а затем 4 пробела, а затем снова 6 и только тогда нажмете Enter, то в переменную сохраниться только 3. Всё, что идёт за пробелами, после первого значения, в входном потоке cin — игнорируется. Попробуйте сами.

  47. Александр:

    А если вот мне просто не нравится switch априори могу же я продолжать использовать if else?

    1. Фото аватара Юрий:

      В этих уроках рассказывается, что есть в C++ и как это использовать. А уже использовать ли это вам — это дело ваше. Если вам лучше использовать if else — используйте if else.

Добавить комментарий

Ваш E-mail не будет опубликован. Обязательные поля помечены *