Video Coming Soon...

Created by Zed A. Shaw Updated 2024-10-28 08:02:26

13: switch-statements

In the previous exercise you learned the concept of "jumps" or goto in C++ and how that relates to the if-statement. There's another structure in C++ called a switch-statement that works similarily to an if-statement, but is better for fast selection based on a simple input. I predict you won't use switch too much, but there are times when it's superior to if-statements.

WARNING The switch is filled to the brim with footguns. There's all kinds of things that can go wrong, and it can also do many surprising things. When you use it, be sure to follow my advice at the end on how to write footgun resistant switch code.

switch Structure

Here is a basic switch with each element you can use:

switch(SELECTOR) {
  case OPTION:
    // code here
    break;
  default:
    // no option matches
}

Here's the breakdown:

  1. You start a switch with switch(SELECTOR) {.
  2. The SELECTOR is anything that becomes an int, so a char will also work. If you try to use a boolean test like in and if-statement then it won't work right.
  3. You then need case OPTION: to show where the code should jump when SELECTOR == OPTION. You can have as many of these as you need to cover the possible options for the SELECTOR.
  4. You then place your code under this, as shown with // code here.
  5. Once your code is finished--usually before the enext case or default--you'll need to write a break to exit out of the switch. I'll talk about this more later.
  6. You can also add a default: label which will run when no other case matches. I typically use this to log an error just in case I missed a possible case option.
  7. Finally you end the switch with } just like an if-statement.

You can think of a switch as a "controlled group of goto jumps". Instead of manually calculating a number and jumping to the right line, you use a switch. You give the switch a number and a set of case labels, then it does all the work of jumping to the correct location.

Enumerations

Imagine I want to write a switch that works with the two numbers 120 and 54. Let's say they're from some device that uses those to indicate failure states. I could write my switch like this:

switch(failure) {
  case 120:
    break;
  case 54:
    break;
  default:
    break;
}

The problem is, what does 120 or 54 mean? In programming we call this a "magic number," and these are typically bad because you can't figure out what a magic number represents by just looking at it. I can figure out it's a failure code of some kind because of failure, but that's it.

What you need is a way to label these numbers in your code so they're easy to understand. One way is to create two variables, but the better way is to use an enum, or "enumeration."

An enum looks like this:

enum FailureCode {
  BAD_FAIL=120,
  MEDIUM_FAIL=54
};

You simply write enum NAME { to give the enum a name (in this case FailureCode), then write out the names and values for each one. You don't have to uppercase the names, but it's a standard everyone follows so we can know this is a constant that's probably in an enum.

You also don't have to give each name a value. You can let the compiler choose like this:

enum FailureCode {
  BAD_FAIL, MEDIUM_FAIL
};

In this case the compiler will give each name a value starting at 0. In our situation though, we need to explicitly set BAD_FAIL=120 and MEDIUM_FAIL=54 to solve the problem.

You then use the enum FailureCode in the switch like this:

switch(failure) {
  case BAD_FAIL:
    break;
  case MEDIUM_FAIL:
    break;
  default:
    break;
}

Now it's more clear what those numbers mean, and it's easier to change them later if they're wrong. Let's say you find out that BAD_FAIL should be 134 instead of 120. You just change the value in the enum and recompile. Without the enum you have to search for 120 and change only the ones related to failure codes.

NOTE You can also write the switch so that it's explicit with case FailureCode::BAD_FAIL: which will make sure that you're using BAD_FAIL from the enum FalureCode explicitly. For now just use the names without the explicit version until you get more experienced, or if you find you need it.

The Code

I'll now show you a simple piece of code that uses both kinds of switch statements to do something useless. As usual, get this code working before trying to break it.

View Source file ex13.cpp Only

#include <iostream>
#include <fmt/core.h>

using namespace std;
using namespace fmt;

enum color { RED, GREEN, BLUE};

int main() {
  int which_door = 4;

  switch(which_door) {
    case 1:
      println("DOOR #1");
      break;
    case 2:
      println("DOOR #2");
      // fallthrough
    case 3:
      println("DOOR #3");
      break;
    case 4:
      println("DOOR #4");
      break;
    default:
      println("BAD DOOR YOU DIED!");
  }

  color what_color = RED;

  switch(what_color) {
    case color::RED:
      println("COLOR IS RED");
      break;
    case color::BLUE:
      println("COLOR IS BLUE");
      break;
    case color::GREEN:
      println("COLOR IS GREEN");
      break;
    default:
      // how can you hit this?
      println("BAD COLOR");
  }

  return 0;
}

There's nothing in this code that you don't already know, but if you're not sure about something then look it up on cppreference.com or in previous exercises of this book.

Breaking It

There's many ways to break a switch statement:

  1. Try writing a switch that does use a boolean test to see what happens.
  2. What happens if you leave off the break?
  3. What happens if you don't have default but give the switch an unexpected value?
  4. What if the switch is given an int but all of the case are using an enum? What can happen?
  5. How about the inverse? Your switch is an enum but all the case blocks use a number? Is it an error? Should it be? Why?

Bullet Proofing

There's a few things I do to make a switch statement more bulletproof:

I always include a default unless I explicitly do not want to handle all the possible cases. Even then I may add it but put a comment saying I'm ignoring possible values. If you have a default but don't know what to put there, then try logging it as an error. Later you'll learn about exceptions which will abort your code when you have a bad default.

If I do need to have a case that falls through then I label it with // fallthrough. The only time I'll not do this is if it's the common pattern of a series of case with no code in them, usually if I'm parsing and want multiple characters to be handled with one case. For example:

switch(input) {
  case 'x':
  case 'y':
  case 'z':
    println("end of alphabet");
    break;
  default:
    println("it's another character");
}

This is a very common idiom and obviously each case is falling through to the next so no need to flag it. In any other situation I'll add an explicit // fallthrough comment.

Every other case statement needs a break, and I try to write the case and break together so I don't forget it.

Further Study

Previous Lesson Next Lesson

Register for Learn C++ the Hard Way

Register to gain access to additional videos which demonstrate each exercise. Videos are priced to cover the cost of hosting.