Search

5.3 — Switch statements

Although it is possible to chain many if-else statements together, this is difficult to read. Consider the following program:

Because doing if-else chains on a single variable testing for equality is so common, C++ provides an alternative conditional branching operator called a switch. Here is the same program as above in switch form:

The overall idea behind switch statements is simple: the switch expression is evaluated to produce a value, and each case label is tested against this value for equality. If a case label matches, the statements after the case label are executed. If no case label matches the switch expression, the statements after the default label are executed (if it exists).

Because of the way they are implemented, switch statements are typically more efficient than if-else chains.

Let’s examine each of these concepts in more detail.

Starting a switch

We start a switch statement by using the switch keyword, followed by the expression that we would like to evaluate. Typically this expression is just a single variable, but it can be something more complex like nX + 2 or nX - nY. The one restriction on this expression is that it must evaluate to an integral type (that is, char, short, int, long, long long, or enum). Floating point variables, strings, and other non-integral types may not be used here.

Following the switch expression, we declare a block. Inside the block, we use labels to define all of the values we want to test for equality. There are two kinds of labels.

Case labels

The first kind of label is the case label, which is declared using the case keyword and followed by a constant expression. A constant expression is one that evaluates to a constant value -- in other words, either a literal (such as 5), an enum (such as Color::red), or a constant variable (such as x, when x has been defined as a const int).

The constant expression following the case label is tested for equality against the expression following the switch keyword. If they match, the code under the case label is executed.

It is worth noting that all case label expressions must evaluate to a unique value. That is, you can not do this:

It is possible to have multiple case labels refer to the same statements. The following function uses multiple cases to test if the ‘c’ parameter is an ASCII digit.

In the case where c is an ASCII digit, the first statement after the matching case statement is executed, which is “return true”.

Note that you don’t need to use “break” if you use “return” (and vice versa), as either will cause the switch to terminate.

The default label

The second kind of label is the default label (often called the “default case”), which is declared using the default keyword. The code under this label gets executed if none of the cases match the switch expression. The default label is optional, and there can only be one default label per switch statement. It is also typically declared as the last label in the switch block, though this is not strictly necessary.

In the isDigit() example above, if c is not an ASCII digit, the default case executes and returns false.

Switch execution and fall-through

One of the trickiest things about case statements is the way in which execution proceeds when a case is matched. When a case is matched (or the default is executed), execution begins at the first statement following that label and continues until one of the following termination conditions is true:
1) The end of the switch block is reached
2) A return statement occurs
3) A goto statement occurs
4) A break statement occurs
5) Something else interrupts the normal flow of the program (e.g. a call to exit(), an exception occurs, the universe implodes, etc…)

Note that if none of these termination conditions are met, cases will overflow into subsequent cases! Consider the following snippet:

This snippet prints the result:

2
3
4
5

This is probably not what we wanted! When execution flows from one case into another case, this is called fall-through. Fall-through is almost never desired by the programmer, so in the rare case where it is, it is common practice to leave a comment stating that the fall-through is intentional.

Break statements

A break statement (declared using the break keyword) tells the compiler that we are done with this switch (or while, do while, or for loop). After a break statement is encountered, execution continues with the statement after the end of the switch block.

Let’s look at our last example with break statements properly inserted:

Now, when case 2 matches, the integer 2 will be output, and the break statement will cause the switch to terminate. The other cases are skipped.

Warning: Forgetting the break statement at the end of the case statements is one of the most common C++ mistakes made!

Multiple statements inside a switch block

With if statements, you can only have a single statement after the if-condition, and that statement is considered to be implicitly inside a block:

However, with switch statements, you are allowed to have multiple statements after a case label, and they are not considered to be inside an implicit block.

In the above example, the 4 statements between the case label and default label are part of case 1, but not considered to be inside an implicit block.

If this seems a bit inconsistent, it is.

Variable declaration and initialization inside case statements

You can declare (but not initialize) variables inside the switch, both before and after the case labels:

Note that although variable y was defined in case 1, it was used in case 2 as well. Because the statements under each case are not inside an implicit block, that means all statements inside the switch are part of the same scope. Thus, a variable defined in one case can be used in another case, even if the case in which the variable is defined is never executed!

This may seem a bit counter-intuitive, so let’s examine why. When you define a local variable like “int y;”, the variable isn’t created at that point -- it’s actually created at the start of the block it’s declared in. However, it is not visible (in scope) until the point of declaration. The declaration statement doesn’t need to execute -- it just tells the compiler that the variable can be used past that point. So with that in mind, it’s a little less weird that a variable declared in one case statement can be used in another cases statement, even if the case statement that declares the variable is never executed.

However, initialization of variables directly underneath a case label is disallowed and will cause a compile error. This is because initializing a variable does require execution, and the case statement containing the initialization may not be executed!

If a case needs to define and/or initialize a new variable, best practice is to do so inside a block underneath the case statement:

Rule

If defining variables used in a case statement, do so in a block inside the case (or before the switch if appropriate)

Quiz time

Question #1


Write a function called calculate() that takes two integers and a char representing one of the following mathematical operations: +, -, *, /, or % (modulus). Use a switch statement to perform the appropriate mathematical operation on the integers, and return the result. If an invalid operator is passed into the function, the function should print an error. For the division operator, do an integer division.

Hint: “operator” is a keyword, variables can’t be named “operator”.

Show Solution

Question #2


Define an enum class named Animal that contains the following animals: pig, chicken, goat, cat, dog, ostrich. Write a function named getAnimalName() that takes an Animal parameter and uses a switch statement to return the name for that animal as a std::string. Write another function named printNumberOfLegs() that uses a switch statement to print the number of legs each animal walks on. Make sure both functions have a default case that prints an error message. Call printNumberOfLegs() from main() with a cat and a chicken. Your output should look like this:

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

Show Solution


5.4 -- Goto statements
Index
5.2 -- If statements

425 comments to 5.3 — Switch statements

  • newbie

    Hi! Thanks so much for all the tutorials!
    What reason to not do it this way?

  • ARNO

    Hello,
    In a quiz thread question 1 you made a mistake.

        std::cout << "Enter a mathematical operator (+, -, *, /, or %): ";
        char operation{}
        std::cin >> operation;

    the char operation without ; so, we can't compile the programm
    to fix it we need to add ;

    Have a nice day

  • litaci

    how can i improve this program?

  • AE35_Unit

    Great chapters. A step up from the normal tutorials (about the same subjects as these chapters), we get on the web and youtube, which is what I'm looking for. I like the application of what we have previously learned. Notes in code with some slight additions to test ideas form prior chapters. Code below, cheers.

    • nascardriver

      An auto-formatter fixes the indentation for you.
      If multiple cases in a switch do the same, you can let them fall-through

      • AE35_Unit

        Thanks. I've seen the solution and never thought about the fall through, but thought it was pretty cool. I copied it so I can experiment with using code the way the solution had it.  I liked how there was only one call in main to print the outcome so I'm going work with mine to try and get the same "format", thanks.

  • question #1

    Hi,
    Can the following be a good solution for the question #1?

  • sami

    "However, initialization of variables directly underneath a case label is disallowed and will cause a compile error. "

    I think there is an exception that if the case label is the last one (no 'default') we can both declare and initialize a variable.

  • sami

    What value should be passed to the function as an argument to cause 'default label' to be executed? I mean color is of enum class type. How come we can give another value when it is not defined inside enum class Color? Like if it was given the argument 'Color:pink", which doesn't exist, we would get compile error!

    • nascardriver

      An enum is a fancy integer. Every value that is valid for the underlying type (usually `int`), is a valid value for for an enum type.

      There is no enumerator to represent 12345, but it's a valid value.

  • Eric

    I didn't want to write two switch functions, so:

    #include <iostream>

    enum class Animal
    {
        pig,
        chicken,
        goat,
        cat,
        dog,
        ostrich
    };

    struct animalData
    {
        Animal species{ Animal::ostrich };
        int numLegs{ 0 };
        std::string name{ "noname" };

    };

    animalData setAnimalData(Animal animal)
    {
        animalData thisAnimal;

        switch( animal )
        {

        case Animal::pig:
            thisAnimal.species = animal;
            thisAnimal.numLegs = 4;
            thisAnimal.name = "Pig";
            return thisAnimal;
            break;

        case Animal::chicken:
            thisAnimal.numLegs = 2;
            thisAnimal.name = "Chicken";
            return thisAnimal;
            break;

        case Animal::goat:
            thisAnimal.numLegs = 4;
            thisAnimal.name = "Goat";
            return thisAnimal;
            break;

        case Animal::cat:
            thisAnimal.numLegs = 4;
            thisAnimal.name = "Cat";
            return thisAnimal;
            break;

        case Animal::dog:
            thisAnimal.numLegs = 4;
            thisAnimal.name = "Dog";
            return thisAnimal;
            break;

        case Animal::ostrich:
            thisAnimal.numLegs = 2;
            thisAnimal.name = "Ostrich";
            return thisAnimal;
            break;

        default:
            thisAnimal.numLegs = 0;
            thisAnimal.name = "Undefined";
            return thisAnimal;
            break;
        }
    }

    int main()
    {
        animalData newAnimal1{ setAnimalData(Animal::cat) };
        animalData newAnimal2{ setAnimalData(Animal::chicken)};

        std::cout << "A " << newAnimal1.name << " has " << newAnimal1.numLegs << " legs\n";
        std::cout << "A " << newAnimal2.name << " has " << newAnimal2.numLegs << " legs\n";

        return 0;
    }

    Thanks!!

    • nascardriver

      Now you can't do one or the other, you have to do both. If you know that in your program you'll always need the name and number of legs at the same time, that's fine.
      `return thisAnimal` is unconditional, it shouldn't be in the `switch`-statement, same for the species. You also don't need to `break` after a `return`, a `return` stops everything. Otherwise good job :)

      PS: Please use code tags.

  • Córdoba

    As an academic exercise, any improvements. Is this how one would go about using an enum (with user input) to achieve something like this? I realise it's unnecessary to have used enum class for the operator having now seen your solution.  

    Thank you in advance, this website is a god-send.

    • nascardriver

      `getUserOperator` should return the `Operation` right away, not an `int`. You also don't need `selected`. Execution inside a function stops when you `return`.

      • Córdoba

        How do I make it such that if there is an error, it'll go back and give the user another chance to input the operator? And with "getUserOperator", how do you return the operator straight away as it's in an enum Class?

        I get that the easy way is to just return a char as per the solution, just want to understand these enums better

  • Assignment help globally at all level academic. We work 24*7 for your convenience, so you don’t need to wait for any certain time, you can reach any time at our site, for any Assignment Help directly get into touch with our expert write college homework for me, you can concern directly with our Assignment Expert. UK assignment help websites in uk me at any time with good service hire essay helper online.

  • Knight

    How Color::blue evaluates to 4?

    • nascardriver

      Enumerators are integers in disguise. They're numbered starting at 0 and counting up.

  • AndrewPanini

    why is this not working anyone know?

    • Innervate

      The debugger should help you fix these problems.

      I made a few changes and added comments where it went wrong, I think the main issue was a missing brace, which made the main function contained inside of the calculate function. Aside from that, you should use breaks inside of the switch function. Try and make your formatting more consistent using '\n' for readability. Hope this helps!

  • Raffaello

    my take on question #2, should be improved somewhere?

    • nascardriver

      `printNumberOfLegs` doesn't do what it's supposed to do. We can do things like

      That's a very weird cat.

      The signature should be

      Give that a try, you're not too far off.

      As a note to your current `printNumberOfLegs`: Line 41 and 45 are identical. You can let the `case 2` fall through to `case 4`.

  • Gabe

    Quiz 1

    Quiz 2

  • Taras

    I think I got the lesson understanding in general but still confused of declaration and initialization. Please clarify me why this code not working (((

    • nascardriver

      Correct.
      If we use a variable with an initializer, we're safe to assume that the variable has been initialized.
      If line 7 was legal, there'd be a variable with an initializer that has not been initialized.

  • Ambareesh

    Why are use using break statements after "default" in the examples? Isn't that implicit - provided default is added at the last?

  • Dudz

    My answer for question #2

  • Dudz

  • Al

    I've been reading comments, and I want to make sure I did understand why an initialization is not permitted inside a switch statement.

    Initialization asks for two simultaneous things that, in this context, have different degree of contingency: declaration must happen, but you cannot ascertain the assignment will be performed since the execution might never get to that statement, so the engagement of having to do both simultaneously is like saying to the compiler "the program might need to do this and at the same time it should not". Nevertheless, separating declaration from assignment avoids this conflict: the assignment might never happen, but that's OK since thanks to its own statement the declaration will happen anyways.

  • salah

    here Control flow jumps from switch line to the first case line
    so how we could we define a without decleration since compiler will never see int a ;

Leave a Comment

Put all code inside code tags: [code]your code here[/code]