switch
Chapter Twenty-Six
Syllabus topic 1, "Control Flow: Statements and Blocks, If-Else, Else-If, Switch, Loops- While and For Loops Do-while, Break and Continue, Goto and Labels"
Pages 122 to 126 of 222
In one line
switch compares one integer expression against a list of constant values, jumps to the matching case, and then keeps going until it meets a break.
The form
switch (expression) {
case constant1:
statements
break;
default:
statements
break;
}Any number of case labels may appear between the braces. The expression is evaluated once. Control jumps to the case whose constant equals it, or to default if none does, and then runs forward from there.
That last clause is the whole of switch. It is a jump into a block, not a set of separate branches. A case is a label, not a box.
The four restrictions
These are the ones students break, and each has a reason.
- The expression must be of integer type. An
int, achar, ashort, anenum. Not adouble, because equality on floating-point values is not reliable, and not a string, because a string is an array and comparing arrays is not what==does. - Each
casevalue must be a constant expression, known at compile time. A literal, a#define, anenumconstant, or an arithmetic expression of those. Not a variable. - No two
casevalues may be equal. The compiler rejects a duplicate. - There may be at most one
default, and it may go anywhere, though the end is where a reader expects it.
A worked example
#include <stdio.h>
int main(void)
{
for (char grade = 'A'; grade <= 'F'; grade++) {
printf("%c : ", grade);
switch (grade) {
case 'A':
printf("distinction\n");
break;
case 'B':
case 'C':
printf("first class\n");
break;
case 'D':
printf("pass\n");
break;
default:
printf("no such grade\n");
break;
}
}
return 0;
}A : distinction
B : first class
C : first class
D : pass
E : no such grade
F : no such gradecase 'B': followed immediately by case 'C': is deliberate fall-through, and it is the normal way to give two values the same treatment. There is nothing between the two labels, so a match on B runs straight into C's statements. This is the good use of fall-through, and it is the only one you need this semester.
Fall-through when you did not mean it
Forget a break and control runs into the next case's statements.
#include <stdio.h>
int main(void)
{
int choice = 2;
switch (choice) {
case 1:
printf("one\n");
case 2:
printf("two\n");
case 3:
printf("three\n");
default:
printf("default\n");
}
return 0;
}The compiler warns about every missing break, because -Wextra turns that check on:
fallthrough.c: In function ‘main’:
fallthrough.c:9:13: warning: this statement may fall through [-Wimplicit-fallthrough=]
9 | printf("one\n");
| ^~~~~~~~~~~~~~~
fallthrough.c:10:9: note: here
10 | case 2:
| ^~~~
fallthrough.c:11:13: warning: this statement may fall through [-Wimplicit-fallthrough=]
11 | printf("two\n");
| ^~~~~~~~~~~~~~~
fallthrough.c:12:9: note: here
12 | case 3:
| ^~~~
fallthrough.c:13:13: warning: this statement may fall through [-Wimplicit-fallthrough=]
13 | printf("three\n");
| ^~~~~~~~~~~~~~~~~
fallthrough.c:14:9: note: here
14 | default:
| ^~~~~~~switch
And it prints three lines when the programmer wanted one:
two
three
defaultchoice was 2, so control jumped to case 2 and then simply carried on downwards through case 3 and default. A case is a label, not a block.
Where you genuinely want fall-through past some statements, say so. gcc, and the standard from C23, accept an attribute:
case 1:
printf("one, and continuing\n");
/* fall through */
case 2:
printf("two\n");
break;The comment silences the warning in gcc and, more importantly, tells the next reader that you meant it.
The practical: a menu-driven calculator
MU's Practical 2(b), in her own words: a menu-driven program using switch case to add, subtract, multiply or divide based on the user's choice.
#include <stdio.h>
int main(void)
{
int choice;
double a, b;
printf("1 add\n2 subtract\n3 multiply\n4 divide\n");
printf("Enter your choice: ");
if (scanf("%d", &choice) != 1) {
printf("\nThat was not a choice.\n");
return 1;
}
printf("Enter two numbers: ");
if (scanf("%lf %lf", &a, &b) != 2) {
printf("\nThose were not two numbers.\n");
return 1;
}
printf("\n");
switch (choice) {
case 1:
printf("%.4f + %.4f = %.4f\n", a, b, a + b);
break;
case 2:
printf("%.4f - %.4f = %.4f\n", a, b, a - b);
break;
case 3:
printf("%.4f * %.4f = %.4f\n", a, b, a * b);
break;
case 4:
if (b == 0.0) {
printf("Division by zero is not defined.\n");
} else {
printf("%.4f / %.4f = %.4f\n", a, b, a / b);
}
break;
default:
printf("%d is not one of the four choices.\n", choice);
break;
}
return 0;
}4
22 71 add
2 subtract
3 multiply
4 divide
Enter your choice: Enter two numbers:
22.0000 / 7.0000 = 3.1429Three things earn the marks here.
1. The division-by-zero check. Chapter 15 said the only correct handling is to test first. Here the operands are double, so a division by zero would not kill the program, and it would print inf, which is not an answer a student should hand in.
b == 0.0 on a double is the one case where comparing with == is right: zero is exactly representable, and what is being asked is whether the user typed a zero, not whether an arithmetic result landed on zero.
2. The default branch. An input the menu does not offer must be reported, not ignored.
3. Both scanf calls are checked. Two lines for the whole of the input validation.
switch
The practical says "menu driven", and a real menu repeats until the user asks to stop. That needs a loop round the switch, which is chapter 29's do-while, and the full version is in chapter 44.
A case can share a body
Counting vowels is the standard example, and it is worth one program:
#include <stdio.h>
int main(void)
{
const char *text = "Programming with C";
int vowels = 0, consonants = 0, spaces = 0, others = 0;
for (int i = 0; text[i] != '\0'; i++) {
char c = text[i];
if (c >= 'A' && c <= 'Z') {
c = c - 'A' + 'a';
}
switch (c) {
case 'a': case 'e': case 'i': case 'o': case 'u':
vowels++;
break;
case ' ':
spaces++;
break;
default:
if (c >= 'a' && c <= 'z') {
consonants++;
} else {
others++;
}
break;
}
}
printf("\"%s\"\n", text);
printf("vowels %d, consonants %d, spaces %d, others %d\n",
vowels, consonants, spaces, others);
return 0;
}"Programming with C"
vowels 4, consonants 12, spaces 2, others 0default here does real work rather than reporting an error, which is a legitimate use: the cases pick out the special values and default handles the general one.
switch against an else-if ladder
switch | else-if ladder | |
|---|---|---|
| Tests | One integer expression against constants | Any conditions at all |
| Expression type | Integer or char or enum only | Anything |
case values | Constant, distinct, compile-time | Not applicable |
| Ranges | Not directly | Naturally |
double or strings | Cannot | Can |
| Order of tests | Irrelevant; the match is by value | Part of the logic |
| Falls through | Yes, unless you break | No |
| Typical use | A menu, a command, a state | Grade bands, compound conditions |
A case cannot express a range. case 1 ... 5: is a gcc extension, not standard C, and a program that uses it will not compile elsewhere. For ranges, use a ladder.
What this does NOT mean
A case is not a block. It is a label. Control enters at the label and continues until a break or the closing brace.
break is not optional. Leaving it out is legal and is nearly always a bug. -Wextra warns.
switch cannot test a double. Equality on floating-point values is unreliable, so the language does not allow it.
switch cannot test a string. A string is an array, and == on arrays compares addresses. Use strcmp in a ladder.
default is not required and is not necessarily last. It may go anywhere; put it last because that is where a reader looks. Its absence means unmatched values do nothing.
A case value cannot be a variable. It must be a constant expression the compiler can evaluate.
switch
switch is not always faster than a ladder. A compiler may build a jump table for a dense set of values, which is fast, or a chain of comparisons, which is not. Choose on clarity.
Quick revision
switch (expr)evaluates the expression once and jumps to the matchingcaselabel.- The expression must be integer,
charorenum. Never adoubleor a string. - Each
casevalue is a distinct compile-time constant. - Control falls through from one case to the next unless stopped by
break. - Empty consecutive labels,
case 'B': case 'C':, are the deliberate fall-through you want. defaulthandles everything unmatched; it is optional and conventionally last.-Wextrawarns about an implicit fall-through. A/ fall through /comment silences it and documents intent.switchcannot express a range; use an else-if ladder.- A
breakinside aswitchleaves theswitch, not any enclosing loop. Chapter 30.
Test yourself
1. What does this print when n is 2?
switch (n) {
case 1: printf("one ");
case 2: printf("two ");
case 3: printf("three ");
}two three . Control enters at case 2 and falls through case 3, because there is no break.
2. Can switch be used on a double?
No. The controlling expression must have integer type, because equality comparison of floating-point values is not reliable.
3. Can two case labels have the same value?
No. The compiler rejects a duplicate, because there would be no way to decide which to jump to.
4. Is default compulsory? Must it come last?
Neither. Without it, an unmatched value does nothing. It may appear anywhere, and it is put last by convention.
5. How do you make case 'B' and case 'C' do the same thing?
Write the labels one after the other with nothing between them: case 'B': case 'C': statements break;
6. Why can a case value not be a variable?
Because the compiler must know every case value while compiling, so that it can decide where each value jumps to. A variable's value is not known until the program runs.
7. How would you handle marks bands 40 to 49, 50 to 59 and 60 to 100 with a switch?
Not directly, because a case cannot express a range. Either use an else-if ladder, or switch on marks / 10 so that each band becomes one or more distinct integers.
What can be asked on this, and how to answer it
"Explain the switch statement with syntax and an example." Give the syntax, say the expression is evaluated once and control jumps to the matching label, and stress that execution continues from there until a break. Give the four restrictions and a worked example with a deliberate shared case.
switch
"What is fall-through in a switch? Give one useful and one harmful example." Control continuing from one case into the next when no break stops it. Useful: two labels sharing a body, case 'B': case 'C':. Harmful: a missing break running the next case's statements as well, which -Wextra warns about.
"Write a menu-driven program using switch to perform addition, subtraction, multiplication and division." Give this chapter's program. Keep the division-by-zero check and the default; they are the two things the examiner asks about.
"Distinguish between switch and if-else." Give the table's rows: one integer expression against constants versus arbitrary conditions, constant distinct labels versus any test, no ranges versus ranges, fall-through versus none, and the order of tests being irrelevant in one and part of the logic in the other.
"Can a switch be nested?" Yes, and a break in the inner one leaves only the inner switch. Nesting more than one level is hard to read; a function for the inner decision is usually better.
The rest of this subject
These notes are cut from the University's printed syllabus. Open the syllabus itself for the same subject.