Conditionals in C — theory with if, else if and switch to work out a bus fare (and the = that gave everyone a discount)

The first “useful” thing I ever programmed in C was a calculator for my bus fare to university (in Gran Canaria we call the bus the guagua). Nothing fancy: if you have a youth card, 30 % off. I tried it with my own case and got 1.75 €. I tried it with my dad’s, who has no youth card and never will, and he also got 1.75 €. And so did my 70-year-old neighbour. Everybody got the youth discount. This was the program:

#include <stdio.h>

int main(void) {
    int youth_card = 0;           // 0 = I do NOT have a youth card
    float price = 2.50f;          // ticket for 4 to 6 zones

    if (youth_card = 1) {         // a single =! it stores a 1 and then asks "is 1 true?"
        price = price * 0.70f;    // 30 % discount
        printf("Youth discount applied\n");
    }

    printf("Youth card: %d\n", youth_card);
    printf("Price: %.2f €\n", price);
    return 0;
}

When I ran it:

Youth discount applied
Youth card: 1
Price: 1.75 €

Look at the second line: the variable youth_card started at 0 and ends up as 1. The program was not just getting the price wrong: it was changing the data. The culprit is a single character, the = in if (youth_card = 1), which in C does not ask anything: it stores. And the funny part is that gcc was warning me, but I was not compiling with -Wall so I never saw it:

hook.c: In function 'main':
hook.c:7: warning: suggest parentheses around assignment used as truth value [-Wparentheses]

In this article we are going to cover conditionals in C (if, else and switch) and everything you need for a program to make decisions without surprises: what “true” means to C (spoiler: it is a number), comparison and logical operators, the if / else if / else chain, the braces and the semicolon that will trip you up, the ternary operator and switch with its famous break. All with one running example: the price of a bus ticket depending on the zones, your age, the youth card and the time of day.

If you are coming from Types and variables in C, you already know how to declare variables, read with scanf and that in C a comparison gives 1 or 0. Today we put that to work. When you finish, the C conditionals practice has three complete programs and then come the exercises. If you know Python, many ideas will sound familiar from conditionals in Python: the logic is the same, the way you write it changes.

The bus fares in this article are made up (1.50 € up to 3 zones, 2.50 € from 4 to 6 and 3.50 € from 7 on) so the sums come out round. Do not use them to plan a real trip.

What “true” means in C

Before writing a single if you need to understand what C looks at when you give it a condition. Python has True and False. Classic C does not: the condition is a number, and C asks itself only one question: is it 0 or not?

  • If it is 0, it is false.
  • If it is anything else (1, 4, -1, 0.5…), it is true.

And the other way round: when you write a comparison like zones > 3, the result is not an abstract “true”, it is the int 1; if it does not hold, the int 0. See it with this program:

#include <stdio.h>

int main(void) {
    int zones = 4;

    printf("zones > 3  is %d\n", zones > 3);      // a comparison gives 1 (true)...
    printf("zones == 7 is %d\n", zones == 7);     // ...or 0 (false). There is no true/false
    printf("!zones     is %d\n", !zones);         // ! turns "not 0" into 0
    printf("!0         is %d\n", !0);             // and 0 into 1

    if (zones) {                                  // if (4): 4 is not 0, so it is true
        printf("if (4): gets in\n");
    }
    if (-1) {                                     // negative numbers are not 0 either
        printf("if (-1): gets in\n");
    }
    if (0.5) {                                    // 0.5 is not 0 either
        printf("if (0.5): gets in\n");
    }
    if (0) {                                      // the only "false" is 0
        printf("if (0): this never prints\n");
    }
    return 0;
}

Output:

zones > 3  is 1
zones == 7 is 0
!zones     is 0
!0         is 1
if (4): gets in
if (-1): gets in
if (0.5): gets in

With zones = 4: 4 > 3 holds and gives 1; 4 == 7 does not and gives 0; !zones is “not 4”, and since 4 is true, its negation is 0; and !0 gives 1. Then if (4), if (-1) and if (0.5) all get in, and the only one that prints nothing is if (0). The drawing sums it up:

What counts as true in C Number line with -2, -1, 0, 0.5, 1, 2, 3 and 4: only 0 is false; all the others are true. Below, with zones = 4: zones > 3 gives 1, zones == 7 gives 0 and !zones gives 0. What gets into an if? C has no true or false: it checks whether the number is 0 or not -2 -1 0 0.5 1 2 3 4 0 = false any other number = true And comparisons return an int (zones = 4) zones > 3 → 1 zones == 7 → 0 !zones → 0
Only 0 is false: -1 and 0.5 also get into the if. A comparison does not give "true", it gives the number 1.

Since C99 there is <stdbool.h>, which brings bool, true and false. But they are disguises: true is 1 and false is 0. In IC2 you will almost always see int with 1 and 0, as in this article, so get used to reading if (passed) as “if passed is not 0”.

This has a practical consequence you will see a thousand times in the course programs: if (n) means “if n is not 0″ and if (!ptr) means “if ptr is 0 (NULL)”. They are not weird tricks, it is just how C thinks.

Comparison and logical operators

Comparison operators are the same as in Python. Logical ones change their names:

I want…In CIn PythonBus example
Equal to====zones == 4
Not equal to!=!=trips != 0
Greater / less than> <> <hour > 19
Greater or equal / less or equal>= <=>= <=age >= 65
And (both)&&andage <= 25 && youth_card
Or (at least one)||orhour < 7 || hour > 19
Not!not!youth_card

Let’s use them to decide two things about the ticket: whether I get the youth fare (between 13 and 25 and with a card) and whether it is rush hour (7 to 9 or 17 to 19).

#include <stdio.h>

int main(void) {
    int age = 20;
    int youth_card = 1;
    int hour = 8;

    // && (and): all three conditions must hold
    int is_young = age >= 13 && age <= 25 && youth_card;

    // || (or): one of the two time slots is enough; the brackets make clear what goes with what
    int rush_hour = (hour >= 7 && hour <= 9) || (hour >= 17 && hour <= 19);

    // ! (not): flips a 1 into a 0 and the other way round
    int no_card = !youth_card;

    printf("Youth fare?     %d\n", is_young);
    printf("Rush hour?      %d\n", rush_hour);
    printf("No card?        %d\n", no_card);
    return 0;
}

Output:

Youth fare?     1
Rush hour?      1
No card?        0

At 20, with a card and at 8 in the morning: 20 >= 13 (1), 20 <= 25 (1), youth_card (1); three ones joined with && give 1. For rush hour, (8 >= 7 && 8 <= 9) is already 1, so the || gives 1 without needing the second slot. And !1 is 0: I do have a card.

Put brackets around each time slot when you mix && and ||. C already gives && higher priority, so the program would work the same without them, but you (and your lecturer) will read it much better.

Short-circuit evaluation: C stops looking as soon as it knows

With &&, if the left part is false, the result is already false whatever happens: C does not evaluate the right part. With ||, if the left part is true, the same. It looks like an efficiency detail, but it protects dangerous operations. Imagine I want to know whether my average per trip is over 1 € and I have not taken the bus yet this month:

#include <stdio.h>

int main(void) {
    int trips = 0;             // I have not taken the bus yet this month
    int spent = 1200;          // cents loaded on the card

    // If trips is 0, the left part is already false and C does NOT evaluate the right one:
    // the division by 0 never happens
    if (trips != 0 && spent / trips > 100) {
        printf("Average per trip: over 1 €\n");
    } else {
        printf("No trips or low average (no division done)\n");
    }
    return 0;
}

Output:

No trips or low average (no division done)

Since trips != 0 is false, the division spent / trips never happens. If you write the two conditions the other way round (spent / trips > 100 && trips != 0), C divides first… by 0. With integers, that kills the program:

Floating point exception (core dumped)

Putting the safety check after the operation it protects. The order of the conditions in an && matters: first “can I do it?”, then “do it”.

The range that looks like maths: 13 <= age <= 25

In maths, 13 ≤ age ≤ 25 is clear at first sight. In C it compiles… and does something else:

#include <stdio.h>

int main(void) {
    int age = 40;

    // Looks like maths... but C reads it as (13 <= age) <= 25, that is, 1 <= 25
    if (13 <= age <= 25) {
        printf("Without &&: youth fare\n");
    }

    // This is right: two comparisons joined with &&
    if (age >= 13 && age <= 25) {
        printf("With &&: youth fare\n");
    } else {
        printf("With &&: normal fare\n");
    }
    return 0;
}

Output, with an age of 40:

Without &&: youth fare
With &&: normal fare

C reads 13 <= age <= 25 from left to right: first 13 <= 40, which gives 1, and then 1 <= 25, which also gives 1. Whatever the age: the result of the first comparison is 0 or 1, and both are less than 25. That is why a 40-year-old gets the “youth fare”. gcc spots it if you compile with -Wall:

range.c: In function 'main':
range.c:7: warning: comparison of constant '25' with boolean expression is always true [-Wbool-compare]
range.c:7: warning: comparisons like 'X<=Y<=Z' do not have their mathematical meaning [-Wparentheses]

The right way is always two comparisons joined with &&: age >= 13 && age <= 25.

The if / else if / else structure

We can write conditions. Now, the structure that chooses between several paths. In C it is written like this:

if (condition) {
    // if the condition is true
} else if (other_condition) {
    // if the first was false and this one is true
} else {
    // if none was
}

Compared with Python, three things change: the brackets around the condition are mandatory (if (x > 5), not if x > 5), braces {} mark the block (indentation is just decoration) and elif is written else if, two words. Let’s work out the ticket price from the zones:

#include <stdio.h>

int main(void) {
    int zones;
    float price;

    printf("How many zones are you travelling? ");
    scanf("%d", &zones);

    if (zones < 1) {                 // the impossible first: no zones, no ticket
        printf("Error: the minimum is 1 zone\n");
        return 1;                    // 1 = "something went wrong"
    } else if (zones <= 3) {         // here we already know zones >= 1
        price = 1.50f;
    } else if (zones <= 6) {         // and here, that zones >= 4
        price = 2.50f;
    } else {                         // everything else: 7 zones or more
        price = 3.50f;
    }

    printf("Ticket for %d zones: %.2f €\n", zones, price);
    return 0;
}

Running it and typing 4:

How many zones are you travelling? 4
Ticket for 4 zones: 2.50 €

This is how the program walks the chain with 4 zones:

QuestionWith zones = 4Gets in?
zones < 14 < 1 → 0No, on to the next
zones <= 34 <= 3 → 0No, on to the next
zones <= 64 <= 6 → 1Yes: price = 2.50f and it leaves the chain
else—Not even looked at
if / else if / else chain with 4 zones Three questions in order: zones < 1 (no), zones <= 3 (no), zones <= 6 (yes, price = 2.50). The 3.50 else is never looked at. if / else if / else with zones = 4 Questions go top to bottom and ONLY the first true branch runs zones < 1 yes Error + return 1 is 0 no zones <= 3 yes price = 1.50f is 0 no zones <= 6 yes price = 2.50f is 1 no else: price = 3.50f Ticket for 4 zones: 2.50 € the else is not even evaluated
With 4 zones the first two questions fail and the third succeeds; as soon as one branch is true, the rest of the chain is skipped.

The key is in the drawing: only the first true branch runs. As soon as zones <= 6 holds, C skips everything that is left. That is why the second else if does not need zones >= 4 && zones <= 6: if we got that far, we already know zones is not below 1 and not 3 or less. With other data (each one a separate run):

How many zones are you travelling? 9
Ticket for 9 zones: 3.50 €

With 9 zones the three questions fail and it reaches the else: 3.50 €. And with 0:

How many zones are you travelling? 0
Error: the minimum is 1 zone

return 1 ends main right there. By convention, main returns 0 if everything went well and another number if something failed; the terminal keeps it and you can see it with echo $? right after running the program.

Validate the impossible cases first (negative zones, a grade of 11…) and leave with return 1. That way the rest of the program can assume the data is good.

Why the order of the chain matters

Since only the first true branch runs, putting the conditions in the wrong order gives absurd results without any warning. This program has exactly the same three fares, but starts with the broadest condition:

#include <stdio.h>

int main(void) {
    int zones = 9;
    float price;

    if (zones >= 1) {                // 9 >= 1 is true: it gets in here and stops looking
        price = 1.50f;
    } else if (zones >= 4) {         // never reached: anything >= 4 was already >= 1
        price = 2.50f;
    } else if (zones >= 7) {
        price = 3.50f;
    }

    printf("Ticket for %d zones: %.2f €\n", zones, price);
    return 0;
}

Output:

Ticket for 9 zones: 1.50 €

Nine zones for 1.50 €. 9 >= 1 is true, so it enters the first branch and stops looking; the other two else if are dead code, because any number greater than or equal to 4 is also greater than or equal to 1. The rule: when conditions overlap, from the strictest to the broadest (or, with <=, from the smallest to the largest, as in fare.c).

Braces: when they are mandatory

In C, if an if block has a single statement, braces are optional. That looks handy until you add a second line, nicely indented, and believe it also belongs to the if. It is lunchtime, no surcharge… or so I think:

#include <stdio.h>

int main(void) {
    int hour = 12;                 // midday: NOT rush hour
    float price = 2.50f;

    if (hour >= 7 && hour <= 9)
        printf("Rush hour: +20 %%\n");
        price = price * 1.20f;     // looks like part of the if... but it always runs

    printf("Price: %.2f €\n", price);
    return 0;
}

Output:

Price: 3.00 €

The rush-hour printf does not appear (good), but the price has gone up to 3.00 € (2.50 × 1.20 = 3.00). Without braces, only the first statement belongs to the if; the second one stands on its own and always runs, however indented it is. In Python this cannot happen because indentation is part of the language; in C the compiler does not even look at it. Well, almost: modern gcc with -Wall notices that the indentation is misleading.

no_braces.c: In function 'main':
no_braces.c:7: warning: this 'if' clause does not guard... [-Wmisleading-indentation]
no_braces.c:9: note: ...this statement, but the latter is misleadingly indented as if it were guarded by the 'if'

Removing the braces because “it is only one line” and adding another line later. Golden rule for IC2: always use braces, even if the block has a single statement. It costs two characters and avoids the hardest mistake to spot in the whole topic.

The semicolon after the if

A cousin of the previous one, and even sneakier because the code does have braces:

#include <stdio.h>

int main(void) {
    int age = 30;

    if (age < 4);                   // that ; IS the body of the if: an empty statement
    {
        printf("Under 4: you travel free\n");   // this block stands on its own
    }
    printf("Age: %d\n", age);
    return 0;
}

Output, at age 30:

Under 4: you travel free
Age: 30

That ; right after the bracket is an empty statement, and it is all there is inside the if. The { … } block below has nothing to do with it: it is a standalone block that always runs. So it tells a 30-year-old that they travel free. With -Wall, gcc warns here because of the indentation; with -Wextra it says it loud and clear:

semicolon.c: In function 'main':
semicolon.c:6: warning: suggest braces around empty body in an 'if' statement [-Wempty-body]
semicolon.c:6: warning: this 'if' clause does not guard... [-Wmisleading-indentation]
semicolon.c:7: note: ...this statement, but the latter is misleadingly indented as if it were guarded by the 'if'

Always compile with gcc -Wall -Wextra program.c -o program. Warnings do not stop the executable from being built, but almost every mistake in this article shows up there before you suffer it.

The dangling else: whose else is this?

When you nest an if inside another without braces, the else always pairs with the nearest if that does not have its own else yet, not the one you want according to the indentation:

#include <stdio.h>

int main(void) {
    int age = 30;
    int youth_card = 0;

    if (age <= 25)
        if (youth_card)
            printf("Youth discount\n");
    else
        printf("Over 25: normal fare\n");   // the indentation lies: this else belongs to the inner if

    printf("End of calculation\n");
    return 0;
}

Output:

End of calculation

The intention was: if you are over 25, “normal fare”. But the else belongs to if (youth_card), the inner one. Since age <= 25 is false at 30, the outer if is not entered and nothing prints; only “End of calculation”. gcc sees it:

dangling_else.c: In function 'main':
dangling_else.c:7: warning: suggest explicit braces to avoid ambiguous 'else' [-Wdangling-else]

With braces there is no possible doubt: if (age <= 25) { if (youth_card) { … } } else { … }. The same lesson again.

The = instead of == mistake

Back to the program from the start. = is assignment (it stores a value) and == is comparison (it asks whether two values are equal). In Python, putting = inside an if is an immediate syntax error. In C it is a valid expression: youth_card = 1 stores a 1 in the variable and is worth 1, so the if always gets in. Two kinds of damage at once: the condition is always true and the variable is overwritten.

You writeWhat it doesResult of the if
if (youth_card == 1)Compares1 only if you have a card
if (youth_card = 1)Stores 1 and is worth 1Always true
if (youth_card = 0)Stores 0 and is worth 0Always false

There is a classic defensive trick (the so-called “Yoda conditions”): writing the constant on the left, if (1 == youth_card). That way, if an = slips through, the program simply does not compile, because you cannot store anything in a 1:

#include <stdio.h>

int main(void) {
    int youth_card = 0;

    if (1 = youth_card) {         // constant on the left: the extra = no longer compiles
        printf("Youth discount\n");
    }
    return 0;
}

When compiling:

yoda.c: In function 'main':
yoda.c:6: error: lvalue required as left operand of assignment

An “lvalue” is “something that can go on the left of an =“: a variable can, a 1 cannot. It is a style that splits opinions (it reads oddly), but the real defence is the usual one: -Wall, which warns with the “suggest parentheses around assignment” you saw at the start.

Reading the warning suggest parentheses around assignment used as truth value and “fixing” it with double brackets, if ((x = 1)). That silences the compiler, but the bug is still there. The fix is ==.

The ternary operator in C

For small decisions that only choose between two values there is the ternary operator: condition ? value_if_true : value_if_false. It is the C version of Python’s x if condition else y, with the order changed: the condition comes first.

#include <stdio.h>

int main(void) {
    int hour = 18;
    float price = 1.75f;

    // condition ? value_if_true : value_if_false
    float total = (hour >= 17 && hour <= 19) ? price * 1.20f : price;

    printf("Total: %.2f €\n", total);
    printf("Slot: %s\n", (hour >= 17 && hour <= 19) ? "rush hour" : "normal");
    return 0;
}

Output, at 18:00:

Total: 2.10 €
Slot: rush hour

At 18 it is rush hour, so total is 1.75f * 1.20f = 2.10. And on the second line, the ternary chooses directly between two texts for the %s. Use it to assign or print one of two values; if you need to do several things on each path, or the condition does not fit in your head at a glance, go back to the good old if / else.

A ternary inside another (a ? b : c ? d : e) is legal, but hardly anybody understands it the first time. If you find yourself doing it, you need an if / else if.

switch/case: when to use it and why it surprises you

When you compare one integer variable against several exact values (a menu option, a day of the week, a letter), switch is cleaner than a chain of else if. The ticket machine at the bus station is the perfect example:

#include <stdio.h>

int main(void) {
    int option;

    printf("=== TICKET MACHINE ===\n");
    printf("1. Single ticket (1.50 €)\n");
    printf("2. 10-trip pass (8.50 €)\n");
    printf("3. Youth card top-up (5.00 €)\n");
    printf("Option: ");
    scanf("%d", &option);

    switch (option) {                // jumps straight to the case equal to option
        case 1:
            printf("Single ticket: 1.50 €\n");
            break;                   // break = "leave the switch"
        case 2:
            printf("10-trip pass: 8.50 €\n");
            break;
        case 3:
            printf("Youth card top-up: 5.00 €\n");
            break;
        default:                     // no case matches
            printf("Option %d does not exist\n", option);
            break;
    }
    return 0;
}

Running it and choosing option 2:

=== TICKET MACHINE ===
1. Single ticket (1.50 €)
2. 10-trip pass (8.50 €)
3. Youth card top-up (5.00 €)
Option: 2
10-trip pass: 8.50 €

The switch evaluates option once and jumps straight to the case label with the same value. There it runs statements until it finds a break, which tells it “leave the switch”. The first version I wrote of this program (a three-option menu in Spanish, compiled with gcc in the Fedora terminal and written in gedit) looked like this:

Fedora terminal with gcc case.c -o m and ./m, which prints Elegiste la opción 2 after typing 2, next to gedit with the Spanish three-option switch program

default: the fallback case

default is the “in any other case”, the equivalent of the final else. It runs when no case matches. It is optional, but without it an unexpected value does nothing and gives no warning. With option 7:

=== TICKET MACHINE ===
1. Single ticket (1.50 €)
2. 10-trip pass (8.50 €)
3. Youth card top-up (5.00 €)
Option: 7
Option 7 does not exist

The break in default is technically unnecessary (it is the last one), but always write it: if one day you add another case below, you will not need to remember.

break in switch: the biggest surprise

Here is the surprise everyone gets the first time. A case is not a block, it is a label: a point to jump to. If there is no break, execution keeps going down and gets into the next case without checking anything. This is called fall-through:

#include <stdio.h>

int main(void) {
    int option = 1;                  // I asked for ONE single ticket
    float charged = 0;

    switch (option) {
        case 1:
            charged = charged + 1.50f;
            printf("Single ticket: 1.50 €\n");
                                     // missing break: execution keeps going down
        case 2:
            charged = charged + 8.50f;
            printf("10-trip pass: 8.50 €\n");
            break;
        default:
            printf("Invalid option\n");
            break;
    }
    printf("Charging you: %.2f €\n", charged);
    return 0;
}

Output:

Single ticket: 1.50 €
10-trip pass: 8.50 €
Charging you: 10.00 €

I asked for a 1.50 € ticket and the machine charges me 10.00 €: 1.50 for the ticket plus 8.50 for the pass, because there was no break after case 1 and execution fell into case 2. That one did have a break, and it left. Compare both paths in the drawing:

switch with and without break With option = 1 the switch jumps to case 1. With break it leaves and charges 1.50 €; without break it falls into case 2 and charges 10.00 €. switch (option) with option = 1 The switch jumps to the matching case; break is the only thing that gets it out With break switch (option) { case 1: +1.50 Single ticket case 2: +8.50 10-trip pass default: Invalid option break Charging you: 1.50 € Without break (fall-through) switch (option) { case 1: +1.50 Single ticket case 2: +8.50 10-trip pass default: Invalid option keeps going down break Charging you: 10.00 €
Same switch, same 1: break decides whether you pay for one ticket or a ticket plus a pass.

-Wall does not warn about this (in C, falling into the next case is legal and sometimes done on purpose), but -Wextra does:

no_break.c: In function 'main':
no_break.c:10: warning: this statement may fall through [-Wimplicit-fallthrough=]
no_break.c:12: note: here

Useful fall-through. Sometimes you want several values to do the same thing. Then you leave several case labels in a row with nothing between them, and they all fall into the same code. Bus timetables change at the weekend:

#include <stdio.h>

int main(void) {
    int day = 6;                     // 1 = Monday ... 7 = Sunday

    switch (day) {
        case 1:                      // no break on purpose: the five cases
        case 2:                      // fall into the same printf
        case 3:
        case 4:
        case 5:
            printf("Monday to Friday timetable\n");
            break;
        case 6:
        case 7:
            printf("Saturday and holiday timetable\n");
            break;
        default:
            printf("Invalid day\n");
            break;
    }
    return 0;
}

Output, with day 6 (Saturday):

Saturday and holiday timetable

day is 6, it jumps to case 6:, which has no statements, falls into case 7: and from there into the Saturday and holiday printf, and the break takes it out. When fall-through is intentional, many programmers mark it with a comment (// falls through to the next case) so nobody “fixes” it.

Forgetting the break at the end of a case. There is no compile error and no runtime error: the program does extra things and the bug only shows in the output. Write the break right after writing the case, before the code inside.

switch vs if/else: when to use each

SituationUseWhy
One integer variable against fixed values (menu 1-5, day 1-7)switchIt reads like a table and jumps straight to the case
A letter ('a', 'b'…)switchchar is a small integer, so it works
Ranges (grade >= 9, zones <= 3)if / else ifcase only compares equality
Numbers with decimals (float, double)if / else ifswitch does not accept them
Several variables at once (age <= 25 && youth_card)if / else ifA case cannot combine conditions
Just two paths that pick a valueTernaryOne line instead of five

Limitations of switch in C

switch is fast and clean, but it has three strict rules:

  • It only accepts integer expressions: int, char, short, long… No float or double.
  • It only compares equality: there is no case > 5.
  • Each case takes a constant known at compile time (3, 's', or a #define), never a variable.

This program tries to break the first and the third… and it does not compile:

#include <stdio.h>

int main(void) {
    float price = 2.50f;
    int zones = 4;
    int limit = 3;

    switch (price) {                // switch only accepts integers (int, char...)
        case 2.50:
            printf("Ticket for 4 to 6 zones\n");
            break;
    }

    switch (zones) {
        case limit:                 // a case must be a constant
            printf("Up to 3 zones\n");
            break;
    }
    return 0;
}

When compiling:

limits.c: In function 'main':
limits.c:8: error: switch quantity not an integer
limits.c:9: error: case label does not reduce to an integer constant
limits.c:15: error: case label does not reduce to an integer constant

“switch quantity not an integer”: whatever goes in the brackets of the switch must be an integer. “case label does not reduce to an integer constant”: whatever goes after each case must be an integer constant; neither 2.50 (decimal) nor limit (variable) will do. If you need any of those, the tool is if / else if.

Common mistakes with conditionals in C

= instead of == inside an if. It compiles, the condition is always true (or always false) and it also changes the variable. -Wall warns about it.

Removing the braces from a “one-line” block and adding another line later. Only the first statement belongs to the if.

The ; after if (…). It turns the if into an empty statement and the block below always runs.

Writing 13 <= age <= 25. In C it is always true: you need age >= 13 && age <= 25.

Putting an else if chain with overlapping conditions in the wrong order. Only the first one that holds runs.

Forgetting the break in a case, or trying a switch on a float or with case variable:.

Watch it step by step in Python Tutor

Python Tutor runs your code line by line and shows at each step which line is running and what each variable holds. Despite the name, it supports C: choose C from the language menu, paste this program and press Visualize Execution. It is the same one from the first version of this article, and it is perfect for seeing fall-through:

#include <stdio.h>

int main() {
    int x = 2;

    switch (x) {
        case 1:
            printf("one\n");
        case 2:
            printf("two\n");
        case 3:
            printf("three\n");
            break;
        default:
            printf("other\n");
    }

    return 0;
}

Output:

two
three

What to watch step by step:

  1. At the step where switch (x) runs, the arrow does not go through case 1: it jumps straight to case 2, because x is 2.
  2. After printing “two”, the next line to run is printf("three\n"). Nobody checked whether x is 3: there is no break, so it falls through.
  3. The break in case 3 makes the arrow jump to return 0. default never runs.
  4. Now add break; after printf("two\n"); and visualise it again: the output becomes just “two”.
Python Tutor running the Spanish version of the switch with x = 2: after case 2 execution carries on into case 3 because there is no break

Summary

ConceptIn CWhat to remember
True / false0 is false; any other number, trueA comparison returns the int 1 or 0
Compare== != < > <= >=== compares, = assigns
Combine&& || !Short-circuit: the right side may not be evaluated
Rangex >= a && x <= bNever a <= x <= b
Chainif (…) {} else if (…) {} else {}Only the first true branch runs
Braces{ … }Always; without them only the first line belongs to the if
Ternarycond ? a : bTo pick one of two values
switchswitch (integer) { case 1: … break; default: … }Integers, equality and constants; break to leave
Warningsgcc -Wall -WextraThey catch =, misleading indentation, ;, ranges and fall-through

Three ideas to take away:

  1. In C the condition is a number: 0 is false and everything else is true. That is why = instead of == compiles and does damage.
  2. Indentation means nothing to the compiler. Always braces, and never a ; after the bracket of an if.
  3. switch jumps to a case and keeps going down until a break. Use it with exact integers; for ranges and decimals, if / else if.

Now it is time to practise: in the C conditionals practice we write a grade classifier, a one-attempt guessing game and a calculator with a menu. And in the next topic, Loops in C, the ticket machine will be able to serve more than one passenger without running the program again.

Next step: practiceConditionals in C — practice with a grade classifier, a one-shot guessing game and a switch calculator (and the semicolon that failed me with a 7.5)

Did this article help you? Here is how you can support SergioLearns →

Sigue aprendiendo

Leave a Reply

Your email address will not be published. Required fields are marked *