C / BRANCHING AND SWITCH
switch on integers and characters with a default case
Use switch to branch on an integer or character value, write case labels as compile-time constants, and catch everything else with a default branch.
What you will learn
- Switch on any integer type, including char, because 'A' is the integer constant 65
- Write case labels as compile-time constants: literals, macros, or enum members
- Add a default branch so unexpected values are reported instead of silently ignored
- Print the actual number in default so a bad value is easy to identify
Understanding switch on integers and characters with a default case
A switch takes one expression of integer type, evaluates it exactly once, and compares that single value for equality against each case label. Characters fit naturally because char is a small integer type and a character constant like 'A' has an integer value (65 on ASCII systems), so case 'A': is really a portable way of writing case 65:. Nothing else fits: you cannot switch on a double, a struct or a string, and a label cannot hold a comparison or a range, because equality against a fixed set of integers is the only test a switch performs.
Each case label must be an integer constant expression the compiler can compute while translating the file, and no two labels in the same switch may share a value. The reason is code generation: the compiler is free to turn a switch into a jump table indexed by the value, or a binary search over sorted labels, and both need the complete label set known up front. That is why a literal, a #define or an enum member works as a label while a const int variable does not, since in C a const object is not a constant expression even though C++ accepts one.
default is the label control jumps to when no case value matched. It is optional, and when it is absent and nothing matches, the whole switch body is skipped and execution resumes after the closing brace, which is exactly how a bad value produces no output and no error. Label order has no effect on matching, so default is legal in the middle of the body, but readers expect it last; treat it as the place to report the unexpected value, return an error, or abort, and print the number you actually received so diagnosis is immediate.
Because the controlling expression is promoted to an integer type, a switch on a char compares character codes, not the meaning a human reads into them: '1' is 49, not 1, and 'q' and 'Q' are two different labels.
<stdio.h>
int main(void)
{
const char *ops = "+-*/%";
int a = 12, b = 5;
for (int i = 0; ops[i] != '\0'; i++) {
char op = ops[i];
switch (op) {
case '+':
printf("%d %c %d = %d\n", a, op, b, a + b);
break;
case '-':
printf("%d %c %d = %d\n", a, op, b, a - b);
break;
case '*':
printf("%d %c %d = %d\n", a, op, b, a * b);
break;
case '/':
printf("%d %c %d = %d\n", a, op, b, a / b);
break;
default:
printf("no case for '%c' (code %d)\n", op, op);
break;
}
}
return 0;
}
A switch compares one integer value against a fixed set of constant labels and jumps to default when none of them match.
Worked examples
default does not have to be last
Switching on a computed integer, with default written before one of the case labels.
<stdio.h>
const char *band(int score)
{
switch (score / 10) {
case 10:
return "perfect";
case 9:
return "excellent";
default:
return "needs work";
case 8:
return "good";
}
}
int main(void)
{
int scores[] = {100, 95, 83, 42};
for (int i = 0; i < 4; i++)
printf("%3d -> %s\n", scores[i], band(scores[i]));
return 0;
}
Example explained
Line 1switch (score / 10) shows the controlling expression can be a computation; it is evaluated once and the resulting value is matched.
Line 2case 10: matches only 100, because 95 / 10 truncates to 9 and 83 / 10 truncates to 8.
Line 3default appears before case 8: yet 83 still reaches the good branch, which proves matching ignores the order of labels.
Line 4Each branch returns, so control leaves the function directly and never falls out of the switch.
labels from enum and #define
Legal case labels built from an enum constant and a macro that expands to a character constant.
<stdio.h>
TAB
enum { MAX_RETRIES = 3 };
int main(void)
{
int retries = 3;
char c = '\t';
switch (retries) {
case MAX_RETRIES:
printf("hit the retry ceiling\n");
break;
default:
printf("%d retries used\n", retries);
break;
}
switch (c) {
case TAB:
printf("tab: advance to the next stop\n");
break;
case ' ':
printf("space: advance one column\n");
break;
default:
printf("other character, code %d\n", c);
break;
}
return 0;
}
Example explained
Line 1MAX_RETRIES is an enum member, so it is an integer constant expression and a valid label; const int limit = 3; case limit: would be rejected by a C compiler.
Line 2TAB expands to '\t' before compilation, so the label is the integer 9.
Line 3case ' ' is 32 and case TAB is 9, two distinct values, so exactly one branch runs and default is not reached.
Line 4Both switches read a value of a different type, int and char, and both are fine because both types are integer types.
default catches the case you forgot
A switch on n % 3 that misses because C remainders can be negative.
<stdio.h>
int main(void)
{
int values[] = {7, -7};
for (int i = 0; i < 2; i++) {
int r = values[i] % 3;
switch (r) {
case 0:
printf("%d is a multiple of 3\n", values[i]);
break;
case 1:
printf("%d leaves remainder 1\n", values[i]);
break;
case 2:
printf("%d leaves remainder 2\n", values[i]);
break;
default:
printf("%d gave an unplanned remainder: %d\n", values[i], r);
break;
}
}
return 0;
}
Example explained
Line 1r is computed into a variable first so the default branch can print the same value the switch matched on.
Line 27 % 3 is 1, so the second label matches and the loop moves on.
Line 3-7 % 3 is -1 in C because integer division truncates toward zero, so none of 0, 1, 2 matches.
Line 4Without default the second iteration would print nothing at all and the wrong assumption about remainders would stay hidden.
Important notes
Two labels in the same switch may not have the same value even when spelled differently, so case '0': and case 48: cannot both appear.
Store the result of getchar() in an int, not a char: EOF is -1, and where plain char is unsigned it becomes 255, so case EOF: would never match.
Common mistakes
Switching on something that is not an integer, such as a char * holding a word or a double: the compiler rejects the switch outright, so string dispatch needs an if/strcmp chain or a step that maps the word to an enum first.
Using a variable as a label: const int limit = 3; case limit: compiles in C++ but fails in C with a message about the label not being a constant expression; write enum { LIMIT = 3 }; or #define LIMIT 3 instead.
Writing case 1: while the switch runs on the character '1', whose code is 49: that branch never fires, default runs instead, and the program silently treats valid input as garbage. Use case '1': or switch on c - '0'.
Try it yourself
Change, predict, then run
Write a switch on char piece = 'N'; that prints the piece value for 'K', 'Q', 'R', 'B', 'N' and 'P', with a default that prints the character and its numeric code. Then set piece to lowercase 'n' and confirm default reports code 110.
Open the C workspaceCheck your understanding
A variable is declared char c = '7'. The switch on c has a label case 7 (the integer seven) and a default that prints the numeric code of c. What happens when the program runs on an ASCII system?
- default runs and prints 55, because the character '7' has the value 55, which never equals 7
- The case 7 branch runs, because C converts a digit character to its numeric value for the comparison
- Neither branch runs and the switch is skipped, because a char value cannot match an int label
- The compiler rejects the code, because a switch on a char accepts only character constants as labels
Show answer
The switch compares the promoted integer value of c, which is 55 for '7', against each label; nothing equals 55, so control goes to default. Option 2 is tempting because '7' looks like a seven, but C never maps a digit character to its numeric value on its own; you would write case '7': or switch on c - '0'.