Skip to content
Lesson 6 of 13

Step 1 of 5 · Reading · ~4 min

Learn

Control Flow

Conditionals

C's if looks like every language that copied it — which is all of them — but two of its original design choices are live ammunition: any number is a condition, and = fits where == belongs. Both compile silently. Both have specific defenses. Learn the defenses with the syntax.

The shape

if (score >= 90) {
    printf("A\n");
} else if (score >= 80) {
    printf("B\n");
} else {
    printf("F\n");
}

Parentheses around the condition: required. Braces: technically optional for single statements — write them anyway. The famous goto fail bug (Apple, 2014, TLS verification bypassed) was a brace-less if gaining an accidental second line. Every style guide since says the same thing: always brace.

Note the shape of the chain as a whole. else if links tests so that exactly one branch runs, and a final bare else is what makes "one branch runs" true for every input rather than most of them. A chain that ends on its last else if quietly does nothing for anything that matched none of the tests — no output, no error, no clue.

Truthiness, C-style

C (pre-C99) didn't even have a bool type: zero is false, everything else is true. Comparisons return the int 1 or 0.

if (n)          /* means: if n != 0 */
if (!n)         /* means: if n == 0 */
if (strcmp(a, b) == 0)   /* strcmp returns 0 on EQUAL — write the == 0! */

Idiomatic C leans on this (if (ptr) for "pointer isn't NULL" is universal), but it's also why the strcmp trap from Strings bites: if (strcmp(a, b)) is true for different strings — reads like "if equal," means the opposite. House rule for clarity: bare truthiness for pointers and flags, explicit comparison (== 0, != 0) whenever a function's return value has meaning beyond yes/no.

The assignment-in-condition trap

if (x = 5) { … }     /* assigns 5 to x, then tests 5 → always true */
if (x == 5) { … }    /* what you meant */

One = missing, no error, condition always true — the single most storied bug in C. Defenses, in order of effectiveness: compile with -Wall (it warns on exactly this), and let your editor highlight it. Some veterans write "Yoda conditions" (if (5 == x)) so the typo'd version fails to compile; modern consensus is that warnings make that style unnecessary — but now you can read it in old code without blinking.

switch: one value, many constants

switch (grade) {          /* integers, chars and enums — never strings */
    case 1:
        printf("one ");   /* no break: execution FALLS THROUGH */
    case 2:
        printf("two ");
    case 3:
        printf("three ");
        break;            /* stops here */
    default:
        printf("other ");
}

/* grade == 2 prints:  two three  */

switch compares one integer-valued expression against constant case labels — you cannot switch on a string, and strcmp inside an if/else if chain is C's answer when you want to. The design choice that surprises everyone is in the comment above: a case does not end where the next one begins. Control jumps to the matching label and then keeps running until it meets a break or falls out of the block, which is why matching case 2 printed two words. Deliberate fall-through is occasionally exactly right — stacking case 'a': case 'e': so several values share one body — so put a break on every case until you specifically mean otherwise.

Logical operators and short-circuit

if (n > 0 && 100 / n > 5)     /* && stops if n <= 0 — division never runs */
if (a || b)                   /* || stops at the first true */

&& and || short-circuit: the right side only evaluates if needed. That's not trivia — it's a guard idiom: test-before-use in one line (ptr != NULL && ptr->value > 0), relied on by every C codebase alive. ! negates. Precedence tip that ends debugging sessions: && binds tighter than ||; parenthesize anything mixed.

C also has the ternary — grade = (score >= 60) ? 'P' : 'F'; — compact for simple picks, unreadable when nested; use accordingly.

Your exercise: FizzBuzz for one number

The eternal kata: multiple of 3 → Fizz, of 5 → Buzz, both → FizzBuzz, else the number itself. Two edges, and the exercise is nothing but edges. Test the "both" case first (n % 15 == 0), because a 3-then-5 chain classifies 15 as Fizz and stops. And finish the chain with a plain else that prints the number — a three-branch version is silently wrong for every input that is neither a multiple of 3 nor of 5, and the examples you can see all happen to land in a Fizz or Buzz branch, so they will not warn you. Chain: 15, then 3, then 5, then printf("%d\n", n).

And since this is C: == in every condition, braces on every branch, -Wall mentally on.

Up nextLoopsControl Flow

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…