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 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”.

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

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.

2) Define an enum (or enum class, if using a C++11 capable compiler) 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.

Quiz answers

1) Show Solution

2) Show Solution

5.4 -- Goto statements
Index
5.2 -- If statements

276 comments to 5.3 — Switch statements

  • C++guy

    I used the std::cin trick we saw on the std::string lesson so I don't have to ask the user three different time to input something and even if he doesn't put a whitespace between his reponses he will still have to put 3 different answers so that's ok. I was wondering when we say something like case '+' the '+' is converted into ASCII code (which is an int) and when the user input + it's converted into ASCII code too right ? I'm asking this because I tried to put case = 93 (93 is ASCII code for + if I remember well) and it worked too.

    • * Initialize your variables with uniform initialization
      * @main is missing a return value

      > the '+' is converted into ASCII code
      correct

      > which is an int
      no. it's a char, but a char is an integral type

      > when the user input + it's converted into ASCII code too right
      right

      • C++guy

        Oh ok I understood your point. The last thing I don't get is why you can assign a value to a variable inside a case with no block but not initialize it. They made this rule to follow the if/else rule (if you define something inside an if it's destroyed) and give more possibilities but assigning something is just like iniatilizing no ? I mean I know there's a difference but at the end of the day when you assign or initialize you give a value to some memory location so why isn't it forbidden too ? Is it because an assignment is only made when executed and not initialization ?

        • c can access the variables declared in b, because a switch doesn't use scopes. When c is executed, @i exists, but hasn't been initialized, because b never ran, meaning that @i has an undefined value although it has an initializer. That's why initializing variables inside a switch is illegal. @j is legal, because it doesn't have an initializer. A variables declared with an initializer must be initialized before it can be used.

          • C++guy

            Oh okay thanks a lot I got it now. That's why assigning instead of initialization isn't a problem, if it isn't executed then the variable will just be declared but because it doesn't have an initializer it isn't a problem if it isn't initialized. And when we put a block when initializing if it doesn't execute then the variable has block scope so it's destroyed after the block and there's no problem with no initialization.

  • Outrageous Gem

    Just to confirm:
    The "break" in the last case of a switch statement is not needed as it would always stop at the end of the switch block? You are just including it for good measure?

    • Alex

      Correct. The trailing break is really more of a defensive programming / documentary practice, so that:
      1) we can add new cases to the bottom without having to go back and modify the previous one by adding a break (less chance for error)
      2) it is more clear that the last case ends immediately and should not flow through into any additional cases added later.

  • Nayab Anjum

    Can we use logical operators with switch statements:
    e.g:

  • Nayab Anjum

    When I executed following lines of code I got output '4354190' but I was expecting to get 5

    #include <iostream>

    using namespace std;

    int main()
    {
        switch (3)
    {
        case 1:
            int y; // okay, declaration is allowed within a case
            y = 4; // okay, this is an assignment
            break;

        case 2:
            y = 5; // okay, y was declared above, so we can use it here too
            break;

        case 3:
            cout << y << endl;
            break;

        default:
            cout << "default case" << endl;
            break;
    }
        return 0;
    }

    • Alex

      When you switch (3), the code jumps immediately to case 3, which prints the value of y. y has been defined (in case 1), but not given a value (since neither the assignment in case 1 or 2 executed), so you're printing the value of an uninitialized variable.

  • DAT

    Question 1:

    #include"stdafx.h"
    #include<iostream>
    #include<string>

    struct AnimalDescript
    {
        bool bSuccess;
        std::string animalName;
        int animalLeg;
    };

    enum class Animal
    {
        PIG,
        CHICKEN,
        DOG,
        CAT,
    };

    AnimalDescript animalInformation(Animal animal)
    {
        switch (animal)
        {
        case Animal::PIG:
            return{ true,"pig",4 };
        case Animal::CHICKEN:
            return { true,"chicken",2 };
        case Animal::DOG :
            return { true,"dog",4 };
        case Animal::CAT:
            return { true,"cat",4 };
        default:
            std::cout << "Error" << "\n";
            return { false };
        }
    }
    void printAnimalInformation(AnimalDescript x)
    {
        if (x.bSuccess)
            std::cout << " A " << x.animalName << " has " << x.animalLeg << " legs." << "\n";
    }

    int main()
    {
            AnimalDescript x= animalInformation(Animal::CAT);
            printAnimalInformation(x);
            AnimalDescript y = animalInformation(Animal::CHICKEN);
            printAnimalInformation(y);
            return 0;
    }

  • Chassz

    This works but I first tried to use

    instead of

    to enter the operator symbol into thinking that char was a type of int but this didn't work. Can someone expand on why?

    • Alex

      std::cin handles int and char inputs differently. '+' is a valid char input, but not a valid int input, as int input is assumed to be numeric.

      Once '+' is actually input, then it is represented as an integer -- so the issue is really with how std::cin behaves.

  • Kio

    For Quiz 1.

    Maybe we can input some basic validation.

  • This solution works:

    but when I tried to use enum class (Animal) - I use VS2017 - everything was out of scope in the case statements.  Is this a peculiarity of switch/case blocks?

    • Hi Nigel!

      Line 8: Use enum class
      Line 36: This will cause a crash, return an empty string.
      Line 57, 61: Space before line feed is memory waste.

      The rest looks good!

      • Thank you.

        <Line 8: Use enum class>

        When I do this every case in the switch block is out of scope so not sure how to solve this.

        <Line 57, 61: Space before line feed is memory waste.>
        Thank you - I never considered it a waste of memory but I suppose a space is still another character - I was just trying to make the code easier to read.

        • > not sure how to solve this
          Enum classes declare enumerators in their own namespace to avoid name collisions. Give lesson 4.5a another read, it's short.

          > I never considered it a waste of memory
          Yeah, it's just one byte, but it's still wasteful.

          References
          Less 4.5a - Enum classes

          • Aha! Got it now:

            Thankyou - for helping out on this.  I take it the above solution is now correct?

            • Nope, read your compiler's warnings.
              @getAnimalName doesn't return a value in every case. Add a

              • Sorry!

  • MorbidPitaya

    Hey!)

    In task 1 of the Quiz an incorrect mathematical operator has two lines of code output to the console window:

    (number 1), (mathematical operator) and (number 2) represent the user-defined integer values and operator itself.

    In spite of the fact this may have been made intentional, I had been desperate to omit having the second line being printed out. I have found a solution. Simply substituting the line (37)

    with the following code

    does the trick :V

    Doing so results in only the message ("calculate(): Unhandled case") being printed. Does my option seem logical and/or syntactically concise?

    Great tutorials/lessons!

    • Hey!

      > Does my option seem logical
      No,

      is always true.
      Either try it with some example values in your head, or apply de morgan's law, which makes it a little more obvious

      The expression inside the brackets is false, because @op can only have one value at a time, negating that gives you true.

      You now have

      But 0 is a valid result of a calculation, you should only care about erroneous operators.

      So, your logic is wrong, and on top of that you're calling @calculate twice, which is a waste, because it will return the same value both times.
      Here's my approach with what you know from the lessons so far:

      You'll learn about better solutions later on.

      • MorbidPitaya

        nascardriver,

        Huh... That (code in your answer) was quite intuitive! Thank you very much for answering!

        Guess I should have thought it through thoroughly and tested with a broader range of examples.

        Could you also tell me what meaning line 44 (in your code) holds? I do not seem to remember using '\x00' as a value to initialise a char variable.

        Thanking you again for the quick reply!

  • Nguyen

    Hi Alex & nascardriver,

    "... 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!"

    After reading the paragraph, I decided to write a small program for testing.  

    #include <iostream>
    using namespace std;

    int main()
    {
        cout<<"Enter 1 or 2: ";
        int x;
        cin>>x;
    switch (x)
    {
        case 1:
            int y;
            y = 4;
            cout<<y;
            break;
        case 2:
            cout<<y;
            break;
         default:
           cout << "default case" <<endl;
           break;
    }
         return 0;
    }
    ===================
    The output:

    Enter 1 or 2: 2
    0

    I really expected 4 instead of 0.  Please review my program and let me know if there is something wrong?

    Thanks, Have a great day.

    • Hi Nguyen!

      Please use code tags.

      You entered 2, case 1 is skipped, execution starts at

      @y is accessible, but has not been initialized nor has the code in case 1 been executed, you get an undefined value, which happens to be 0.

      • Nguyen

        Thank you nascardriver,  

        This means a variable defined, not its assignment, in one case can be used in another case, even if the case in which the variable is defined is never executed!  

        It’s a little less weird, something I should keep in mind.

        Thanks again.

  • Jörg

    hello, is this valid aswell? thanks for the tutorials btw <3333

  • seriouslysupersonic

    Hi, I am curious as to why the code below compiles and runs just fine (using g++ 7.3.0), since I am initializing b inside the last case.

    If I had an extra case, I get the "crosses initialization" error; however, initializing a variable inside the last case doesn't generate a compiler error.

    • Hi there!

      The problem with initialization in cases is that the case could fall through and the variable could be initialized again in a later case (but variables cannot be initialized multiple times). (EDIT: I think that's wrong, but the real reason is similar).
      Since case(2) is your last case, there is no other case behind it, and a fall-through wouldn't change anything, so you're allowed to initialize variables in the last case.
      Even though this doesn't cause an error, I suggest you to write cases in a way that allows them to be position independent (Allow them to be move above and below any other case without changing the behavior), eg. by scoping the cases.

      • seriouslysupersonic

        Hi nascardriver!

        Thanks for the quick answer. However I am still a bit confused as to what is happening behind the scenes when the program executes.

        If the user enters 1, is variable b just created an left uninitialized or is it also initialized to 2 at the start of the block, even though the initialization statement in case (2) is never reached?

        • @b only exists when line 16 has been reached. When that happens, it is also initialization. case(1) doesn't know about @b.
          The rule is, when a variable in a case has an initializer, it must be inaccessible unless initialization is guaranteed.
          Since @b is only accessible after line 16, an it is initialized in line 16, there is no problem. If @a had an initializer and @input is 2, @a would be accessible in case(2), but @a would not have been initialized, which is illegal. Declarations without initializers are allowed. As it is, @a can be used in case(2).

  • Tamara

    Hi, I'm having trouble understanding as to why it's illegal to initialize a variable directly underneath a case statement, but it's legal to initialize it if it's inside a block?

    • Alex

      The C++ standard says, "It is possible to transfer into a block, but not in a way that bypasses declarations with initialization. A program that jumps from a point where a local variable with automatic storage duration is not in scope to a point where it is in scope is ill-formed unless the variable has POD type (3.9) and is declared without an initializer."

      Basically, that means it's illegal to jump over a local variable that has an initializer to a point where that variable is still in scope.

      Putting x inside a block ensures x will be destroyed before the next case, ensuring that the switch can't jump the user to a point where x is still in scope. Thus, x can be safely initialized in this case.

  • Idda

    Hi,
        I'm facing problem in understanding why is it not allowed to intialize a variable just before a case lable?

  • Jack

    If you are returning a value inside a case label, is there any need to break as there is no chance of fall-through? I know the break won't be executed, but is it good practice to keep it there? ie:

    Poor example, but just to illustrate the point. Some languages complain about an unreachable statement (I've seen this error in Java a few times when you return and then write more statements under it), is this true for some versions of C++?

    • nascardriver

      Hi Jack!

      The break after a return will never be reached and adding a break won't change anything.
      I still like having the break there just for good measures.

  • Henry

    Hi Alex

    Your solution is good but it has a bug
    To reproduce, try to use an animal that is not defined and the error message will come out all weird.
    e.g: printNumberOfLegs(Animal::TIGER);

    I think my solution you should not break the cout message
    So do something like that:
    void printNumberOfLegs(Animal a) {
      using namespace std;

      switch (a) {
        case Animal::CHICKEN:
        case Animal::OSTRICH:
          cout << "A " << getAnimalName(a) << " has 2 legs.\n";
          break;
        case Animal::GOAT:
        case Animal::CAT:
        case Animal::DOG:
        case Animal::PIG:
          cout << "A " << getAnimalName(a) << " has 4 legs.\n"; break;
        default : cout << "ERROR: undefined animal!\n";
      }
      
      return;
    }

    You can break the last part of the message but then you need to use return and not break.

    • Alex

      Example updated to use "???" as the number of legs instead of the more descriptive error message. Either way is fine, as this message should only come up as a defect.

  • ArmaIG

    Hi Teachers, when I enter anything else that isn't a number when the program is asking for an integer, the programs executes the rest incorrectly, is there anyway to deal with that problem with the tools we know so far?

    • nascardriver

      Hi ArmaIG!

      This is covered in lesson 5.10.
      Now that you know of switch-statements, those are even better than an if-else-if in your monster to string function.

      References
      * Lesson 5.10 - std::cin, extraction, and dealing with invalid text input

  • Dayyan218

    I wanted to ask, when is it a good time to use switch statements instead of if statements?
    To me switch statements aren't as easy to read as if-else statements. What are the pros of using switch statements over if-else statements?

    • nascardriver

      Hi Dayyan!

      http://www.learncpp.com/cpp-tutorial/53-switch-statements/comment-page-4/#comment-323739

      "Use 'switch' when you have a limited set of values that you want to test against, eg. enums or characters.
      Use 'if' otherwise."

      > What are the pros of using switch statements over if-else statements?
      It's easier for your compiler to optimize switch-statements than if-else-statements.

  • Rohit

    You mention that variables can't be initialized inside a case "because initializing a variable does require execution, and the case statement containing the initialization may not be executed!"

    So I'm curious as to why it's illegal to initialize before all of the case labels? Because all the statements before the first case are executed anyway right?

    This gives the error code C2360 "initialization of 'x' is skipped by 'case' label" in VS 2017. I'm wondering what's going on.
    I tried to put 'x' in its own block and declare it as a static int to try to access it outside of the block scope but that doesn't work. I had forgotten that static variables are only accessible in the block they're declared. Regardless, the initial issue of it not letting me initialize there still occurred. (edit: scratch that, it let me initialize in its own block. but if i can't access it after the block then it's useless anyway.)

    I can always declare on one line and assign on the next but I was just curious as to why this happens.

  • Samira Ferdi

    Hi nascardriver! Thank you for reply!
    So, when the best practice to use switch-case rather than if-else and otherwise?

    • nascardriver

      Hi Samira!

      Use 'switch' when you have a limited set of values that you want to test against, eg. enums or characters.
      Use 'if' otherwise.
      That's how I do it.

  • Samira Ferdi

    So, the only difference betwen if-else chain and switch-case is the switch-case is more easier to read than if-else chain?
    When best practice to use if-else rather than switch-case and otherwise?

    • nascardriver

      Hi Samira!

      No, there are more differences.

      * You can only switch one value whereas if-elseif takes any boolean expression.
      * "cases will overflow into subsequent cases", 'if' doesn't.
      * 'switch' behaves differently when using blocks vs when not using blocks: "You can declare (but not initialize) variables inside the switch", 'if' can initialize.

Leave a Comment

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