Loops in C — exercises with primes, rock-paper-scissors, a 3-attempt login and a menu calculator (and the ; that left the list of primes empty)

Revising for the IC2 practical, I tried the primes exercise from memory, without looking at my notes. I compiled it, ran it with 30 and got Total: 0. Not 2, not 3, not 5. My first theory was that the % was wrong; the second, that the break was wrong. It was a ; at the end of the inner for, and the oddest part is that the break, which was written correctly, made the program finish before it even looked at 3. In this article you will see it taken apart step by step, because it is the trickiest loop mistake there is: it compiles and does not do what you read.

You have already seen the loops in C theory and the practice with the ticket machine, the series and the guessing game. Now it is your turn: five exercises on three levels, a “What’s wrong here?” section with three snippets that compile and fail, the commented solutions and a cheat sheet to keep everything at hand.

How to use this article

  1. Read the task and the expected output. Your program has to give the same output with the same data.
  2. Try to solve it in gedit (or in the site’s C playground, typing the data in the Input tab) and compile with gcc -Wall -std=c11.
  3. If you have been stuck for more than ten minutes, open the hint. Not before.
  4. Compare with the commented solution, even if yours worked: the difference is often in why it is done that way.

Loops in C exercises — Basic Level

Exercise 1 — Number to word converter

Write a program that asks for a number between 1 and 10 and writes its name (“5 -> five”). It has to repeat until the user types 0, which says goodbye with “Goodbye”. If the number is out of range, or if the user types letters, show an error and ask again. Use a switch inside a do…while.

Expected output (with 5, 3, 15, five and 0):

=== NUMBER TO WORD CONVERTER ===
Enter a number (1-10, 0 to quit): 5
5 -> five

Enter a number (1-10, 0 to quit): 3
3 -> three

Enter a number (1-10, 0 to quit): 15
Error: enter a number between 0 and 10

Enter a number (1-10, 0 to quit): five
Error: enter a number between 0 and 10

Enter a number (1-10, 0 to quit): 0
Goodbye

0 has to be handled (goodbye) and then end the loop, so the do…while condition is number != 0. For letters, check what scanf returns and, if it is not 1, empty the line with getchar() and put a value in number that falls into the error (for example, -1).

Exercise 2 — Rock Paper Scissors with replays

The player chooses with numbers (1 rock, 2 paper, 3 scissors, 0 quit) and plays as many rounds as they like. The machine does not use rand() (we have not seen it yet): it plays rock, paper, scissors, rock… depending on the round number. A choice outside 0-3 is rejected and does not count as a round. On quitting, the program shows the rounds played and the score.

Expected output (with 2, 2, 1, 5, 3 and 0):

=== ROCK PAPER SCISSORS ===
1. Rock  2. Paper  3. Scissors  0. Quit

Round 1
Your choice: 2
You: Paper | Machine: Rock
-> You win!

Round 2
Your choice: 2
You: Paper | Machine: Paper
-> Draw

Round 3
Your choice: 1
You: Rock | Machine: Scissors
-> You win!

Round 4
Your choice: 5
Error: choose between 0 and 3
Your choice: 3
You: Scissors | Machine: Rock
-> The machine wins

Round 5
Your choice: 0

--- Final result ---
Rounds played: 4
Wins: 2 | Losses: 1 | Draws: 1

Use while (playing) with a flag playing = 1 that becomes 0 when the player picks 0. The machine’s move comes from the round with (round - 1) % 3 + 1, which gives 1, 2, 3, 1, 2, 3… And only count the round when it has really been played.

Loops in C exercises — Intermediate Level

Exercise 3 — Prime numbers up to n

Ask for a number n between 2 and 1000 (validated) and write all the primes from 2 to n, the total and how many divisions you did in total. A number is prime if it can only be divided by 1 and itself.

Expected output (with 30):

Find primes up to (2-1000): 30

Primes up to 30:
2 3 5 7 11 13 17 19 23 29 

Total primes found: 10
Divisions made: 45

Two nested loops. The outer one goes through the candidates x from 2 to n; the inner one tries divisors i. Start each candidate with is_prime = 1 and, as soon as you find a divisor (x % i == 0), set it to 0 and break. There is no need to test up to x - 1: i * i <= x is enough (if x has a divisor larger than its square root, it also has a smaller one). With that you get exactly 45 divisions.

Exercise 4 — Login with 3 attempts in a single block

In the conditionals in C exercises we wrote a login with three attempts by writing the same block three times, because we did not have loops yet. Write it now with a single block that repeats: ask for the user’s initial (s) and the PIN (5678), three times at most, and finish as soon as the login is correct. If the three attempts are used up, the account is locked.

Expected output when it is right on the third attempt (with x 1234, s 1111 and s 5678):

=== LOGIN SYSTEM ===

Attempt 1 of 3
User initial: x
PIN: 1234
Wrong user or PIN

Attempt 2 of 3
User initial: s
PIN: 1111
Wrong user or PIN

Attempt 3 of 3
User initial: s
PIN: 5678

Access granted. Welcome, sergio.

And when all three fail (with s 1, s 2 and a 1234):

=== LOGIN SYSTEM ===

Attempt 1 of 3
User initial: s
PIN: 1
Wrong user or PIN

Attempt 2 of 3
User initial: s
PIN: 2
Wrong user or PIN

Attempt 3 of 3
User initial: a
PIN: 1234
Wrong user or PIN

Account locked: 3 failed attempts

It is the same scheme as the guessing game in the practice: for (attempt = 1; attempt <= 3 && !logged_in; attempt++). To read the initial, scanf(" %c", &initial) with a space before %c: that way it skips the Enter left by the previous scanf.

Loops in C exercises — Final Challenge

Exercise 5 — Full interactive calculator

Put together everything from this block: loops, conditionals and types. The calculator:

  • Shows a menu (add, subtract, multiply, divide, modulo, power and quit) and validates the option.
  • Asks for two numbers; the second cannot be 0 when dividing or in the modulo (it is asked again).
  • Calculates the power with a for, without pow.
  • Asks whether you want another operation.
  • On quitting, shows how many operations were done and the largest result.

Expected output (15 + 7, 10 / 0 corrected to 3, 2 ^ 10 and quit):

=== INTERACTIVE CALCULATOR ===

1. Add         2. Subtract
3. Multiply    4. Divide
5. Modulo      6. Power
0. Quit
Operation: 1
Number A: 15
Number B: 7
15.00 + 7.00 = 22.00
Another operation? (y/n): y

1. Add         2. Subtract
3. Multiply    4. Divide
5. Modulo      6. Power
0. Quit
Operation: 4
Number A: 10
Number B: 0
Error: you cannot divide by zero
Number B: 3
10.00 / 3.00 = 3.3333
Another operation? (y/n): y

1. Add         2. Subtract
3. Multiply    4. Divide
5. Modulo      6. Power
0. Quit
Operation: 6
Number A: 2
Number B: 10
2.00 ^ 10 = 1024.00
Another operation? (y/n): n

--- Summary ---
Operations done: 3
Largest result: 1024.00

The main loop is a do…while (again == 'y' || again == 'Y'), because at least one operation is always done (or quitting is chosen, with a break). Inside, two more do…while loops validate the option and the divisor. For the largest, the first result always wins: if (operations == 1 || result > largest).

What’s wrong here?

Three snippets that compile. Before reading the explanation, predict what they print.

Snippet 1: the primes that do not show up

#include <stdio.h>

int main(void) {
    int x, i, is_prime, total = 0;

    printf("Primes up to 30:\n");
    for (x = 2; x <= 30; x++) {
        is_prime = 1;
        for (i = 2; i < x; i++);
        {
            if (x % i == 0) {
                is_prime = 0;
                break;
            }
        }
        if (is_prime) {
            printf("%d ", x);
            total++;
        }
    }
    printf("\nTotal: %d (x = %d)\n", total, x);
    return 0;
}

What does it print? Think about it before reading on.

Primes up to 30:

Total: 0 (x = 2)

Not a single prime, and the outer loop stopped at x = 2. The ; at the end of for (i = 2; i < x; i++); leaves that for with an empty body: it does its laps doing nothing and ends with i == x. The block of braces that follows no longer belongs to the inner for but to the outer one, and it runs once for each x. With x = 2, i is 2, 2 % 2 == 0 is true and is_prime becomes 0. And then the break, which you thought left the inner for, leaves the only loop it is in now: the outer one. That is why x stays at 2 and the total is 0.

The ; after the primes forOn the left, the code as you read it: the block with the if looks like the inner for body. On the right, what gcc compiles: the inner for has an empty body and leaves i = x; the block runs once with x = 2 and i = 2, 2 % 2 is 0, is_prime becomes 0 and break leaves the outer for. Real output: Total: 0 (x = 2).What you readWhat gcc compilesfor (x = 2; x <= 30; x++) { for (i = 2; i < x; i++); { if (x % i == 0) { is_prime = 0; break; } }looks like the inner for bodyinner for: empty body ( ; )does no useful work and leaves i = xthe { } block runs ONCEx = 2, i = 2 → 2 % 2 == 0 → is_prime = 0break: it leaves the OUTER for!Real output: Total: 0 (x = 2)not one prime, and the outer for stops at x = 2
A ; orphans the block, and the orphaned break looks for the closest loop left: the outer one.

gcc warned about it with -Wall, just like in the theory story:

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

A ; after for (...) or while (...). It is not a compile error; at most, a -Wmisleading-indentation warning. Before looking for the bug in the logic, look at the end of every loop line.

The fix is to remove the ;. The correct version is the solution to exercise 3, which gives the 10 primes.

Always put the { on the same line as the for. With for (...) { there is no room for a stray ;.

Snippet 2: the converter that keeps on asking

With the input five (letters), what does this converter do? It has a 4-lap emergency brake so the output is not infinite:

#include <stdio.h>

int main(void) {
    int number = -1;
    int laps = 0;

    do {
        if (++laps > 4) {              // emergency brake (only for the article)
            printf("[emergency brake: 4 laps]\n");
            return 1;
        }
        printf("Enter a number (1-10, 0 to quit): ");
        scanf("%d", &number);
        if (number < 0 || number > 10) {
            printf("Error: enter a number between 0 and 10\n");
        }
    } while (number != 0);
    return 0;
}
Enter a number (1-10, 0 to quit): five
Error: enter a number between 0 and 10
Enter a number (1-10, 0 to quit): Error: enter a number between 0 and 10
Enter a number (1-10, 0 to quit): Error: enter a number between 0 and 10
Enter a number (1-10, 0 to quit): Error: enter a number between 0 and 10
[emergency brake: 4 laps]

It only read five once, but asked four times (and without the brake, forever). scanf("%d") cannot turn five into a number: it returns 0, does not touch number (which stays at -1) and leaves the letters in the input. On the next lap it finds five again, fails again, and the condition number != 0 is still true.

scanf inside a loop without checking what it returns. It works with numbers and becomes infinite with the first letter.

The fix is in the solution to exercise 1: if (scanf("%d", &number) != 1), empty the line with while ((c = getchar()) != '\n' && c != EOF); and set number = -1.

Snippet 3: the login that gives four attempts

With four failed attempts ready in the input, how many times does it ask?

#include <stdio.h>

int main(void) {
    char initial;
    int pin;
    int attempt = 0;
    int logged_in = 0;

    while (attempt <= 3 && !logged_in) {
        attempt++;
        printf("Attempt %d of 3 - initial and PIN: ", attempt);
        scanf(" %c %d", &initial, &pin);
        if (initial == 's' && pin == 5678) {
            logged_in = 1;
        }
    }
    printf("%s\n", logged_in ? "Logged in" : "Locked");
    return 0;
}
Attempt 1 of 3 - initial and PIN: x 1
Attempt 2 of 3 - initial and PIN: x 2
Attempt 3 of 3 - initial and PIN: x 3
Attempt 4 of 3 - initial and PIN: x 4
Locked

“Attempt 4 of 3”. The counter starts at 0 and goes up at the start of the body, so the condition is checked with 0, 1, 2 and 3: true four times with attempt <= 3. It is the extra lap from <= in the theory, disguised because attempt++ is not where you are used to seeing it.

Mixing a counter that starts at 0 with a condition designed to count from 1. Either start at 0 and use <, or start at 1 and use <=.

With attempt < 3 (and everything else the same) you get exactly three attempts:

Attempt 1 of 3 - initial and PIN: x 1
Attempt 2 of 3 - initial and PIN: x 2
Attempt 3 of 3 - initial and PIN: x 3
Locked

When the number of laps matters, write the loop as a for: start, condition and step sit on the same line and the mistake jumps out at you.

Commented solutions

Solution to exercise 1

#include <stdio.h>

int main(void) {
    int number;
    int c;

    printf("=== NUMBER TO WORD CONVERTER ===\n");

    do {
        printf("Enter a number (1-10, 0 to quit): ");
        if (scanf("%d", &number) != 1) {
            while ((c = getchar()) != '\n' && c != EOF);   // throws the word away
            number = -1;                                   // and carries on as a bad number
        }

        if (number == 0) {
            printf("Goodbye\n");
        } else if (number < 0 || number > 10) {
            printf("Error: enter a number between 0 and 10\n\n");
        } else {
            printf("%d -> ", number);
            switch (number) {
                case 1:  printf("one\n");    break;
                case 2:  printf("two\n");    break;
                case 3:  printf("three\n");   break;
                case 4:  printf("four\n"); break;
                case 5:  printf("five\n");  break;
                case 6:  printf("six\n");   break;
                case 7:  printf("seven\n");  break;
                case 8:  printf("eight\n");   break;
                case 9:  printf("nine\n");  break;
                default: printf("ten\n");   break;
            }
            printf("\n");
        }
    } while (number != 0);    // the 0 is handled (goodbye) and then it stops
    return 0;
}

Why it is done this way:

  • do…while and not while: you have to ask at least once. With while (number != 0) you would have to invent a starting value for number just to get in.
  • 0 is handled inside: the goodbye is printed in the same lap in which 0 arrives, and then the condition number != 0 stops it. If the condition were at the start (while), there would be no natural place for “Goodbye”.
  • default in the switch: by the time it reaches the switch, number is already between 1 and 10, so default can only be 10. Using it avoids a case 10 and a switch with no route for anything else.
  • Letters: if scanf returns anything other than 1, the line is emptied and number = -1 makes the else if give the error. Without this, you get snippet 2.

Solution to exercise 2

#include <stdio.h>

int main(void) {
    int player, machine;
    int playing = 1;             // flag: the loop goes on while it is 1
    int round = 1;
    int wins = 0, losses = 0, draws = 0;
    int c;

    printf("=== ROCK PAPER SCISSORS ===\n");
    printf("1. Rock  2. Paper  3. Scissors  0. Quit\n");

    while (playing) {
        printf("\nRound %d\n", round);

        // Valid move: at least once and until it is between 0 and 3
        do {
            printf("Your choice: ");
            if (scanf("%d", &player) != 1) {
                while ((c = getchar()) != '\n' && c != EOF);
                player = -1;
            }
            if (player < 0 || player > 3) {
                printf("Error: choose between 0 and 3\n");
            }
        } while (player < 0 || player > 3);

        if (player == 0) {
            playing = 0;         // the while will see it when it goes back up
        } else {
            machine = (round - 1) % 3 + 1;   // no rand(): rock, paper, scissors, rock...

            printf("You: %s | Machine: %s\n",
                   player == 1 ? "Rock" : player == 2 ? "Paper" : "Scissors",
                   machine == 1 ? "Rock" : machine == 2 ? "Paper" : "Scissors");

            if (player == machine) {
                printf("-> Draw\n");
                draws++;
            } else if ((player == 1 && machine == 3) ||
                       (player == 2 && machine == 1) ||
                       (player == 3 && machine == 2)) {
                printf("-> You win!\n");
                wins++;
            } else {
                printf("-> The machine wins\n");
                losses++;
            }
            round++;             // only counts a round actually played
        }
    }

    printf("\n--- Final result ---\n");
    printf("Rounds played: %d\n", round - 1);
    printf("Wins: %d | Losses: %d | Draws: %d\n", wins, losses, draws);
    return 0;
}

Why it is done this way:

  • The playing flag: when the player picks 0, it is set to 0 and the while ends when it goes back up. No break is needed: the rest of the body is in the else, so nothing else runs.
  • round++ only in the else: round 5 (the 0) is not played, which is why round - 1 = 4 is printed at the end. Rejected rounds (the 5) do not raise the counter either, because the do…while stays on the same round until it has valid input.
  • The machine without rand(): (round - 1) % 3 + 1 gives 1, 2, 3, 1… for rounds 1, 2, 3, 4. With that, round 4 is won by the machine (rock against scissors).
  • The three wins with ||: rock beats scissors (1 against 3), paper beats rock (2 against 1) and scissors beat paper (3 against 2). Whatever is neither a draw nor a win is a loss, and the else covers that.
RoundPlayerMachineResultW / L / D
12 Paper1 RockYou win1 / 0 / 0
22 Paper2 PaperDraw1 / 0 / 1
31 Rock3 ScissorsYou win2 / 0 / 1
43 Scissors (after rejecting a 5)1 RockThe machine wins2 / 1 / 1

The three wins fit in a single calculation: (player - machine + 3) % 3 == 1. Rock against scissors: (1 − 3 + 3) % 3 = 1. It works, but in an exam the version with || can be understood without thinking.

Solution to exercise 3

#include <stdio.h>

int main(void) {
    int n, x, i;
    int is_prime;
    int total = 0;
    int divisions = 0;             // how many divisions we have done in total
    int c;

    do {
        printf("Find primes up to (2-1000): ");
        if (scanf("%d", &n) != 1) {
            while ((c = getchar()) != '\n' && c != EOF);
            n = 0;
        }
        if (n < 2 || n > 1000) {
            printf("Error: enter a number between 2 and 1000\n");
        }
    } while (n < 2 || n > 1000);

    printf("\nPrimes up to %d:\n", n);

    for (x = 2; x <= n; x++) {            // each candidate
        is_prime = 1;                     // innocent until proven otherwise
        for (i = 2; i * i <= x; i++) {    // testing up to the square root of x is enough
            divisions++;
            if (x % i == 0) {
                is_prime = 0;
                break;                    // there is a divisor: no need to go on
            }
        }
        if (is_prime) {
            printf("%d ", x);
            total++;
        }
    }

    printf("\n\nTotal primes found: %d\n", total);
    printf("Divisions made: %d\n", divisions);
    return 0;
}

Why it is done this way:

  • is_prime = 1 inside the outer for: each candidate starts out “innocent”. If you set it only once before the loops, as soon as 4 set it to 0 no later number could ever be prime again.
  • break at the first divisor: if 30 can be divided by 2, it is not prime; testing 3 and 5 is a waste of time. And this break only leaves the inner for, which is what we want.
  • i * i <= x: if x = a · b, one of the two factors is at most the square root of x. For 29 it is enough to test 2, 3, 4 and 5 (6 · 6 = 36 > 29). With i < x you would test up to 28.
  • The divisions counter: it is there to check the improvement. With i * i <= x you get 45 divisions for n = 30; with i < x, 135.

How those 45 divisions are spread out is shown in the grid: composites fall at their first divisor (nearly all of them at 2) and only primes go all the way to their square root.

Which divisor knocks out each candidateA grid with the numbers 2 to 30. The primes (2, 3, 5, 7, 11, 13, 17, 19, 23 and 29) in violet; for each composite, the first divisor the inner loop finds before the break (÷2, ÷3 or ÷5). In total, 10 primes and 45 divisions.Candidates 2 to 30: first divisor found (then break)2prime3prime4÷25prime6÷27prime8÷29÷310÷211prime12÷213prime14÷215÷316÷217prime18÷219prime20÷221÷322÷223prime24÷225÷526÷227÷328÷229prime30÷210 primes · 45 divisions in total (with i * i <= x)
break stops at the first divisor: 30 only needs dividing by 2. That is why the inner loop hardly ever reaches its condition.
xDivisors tested (i * i <= x)Result
2, 3none (2 · 2 > 3)prime, 0 divisions
92, 39 % 3 == 0: not prime
252, 3, 4, 525 % 5 == 0: not prime
292, 3, 4, 5prime (6 · 6 = 36 > 29)

Solution to exercise 4

#include <stdio.h>

#define PIN_OK 5678

int main(void) {
    char initial;
    int pin;
    int attempt;
    int logged_in = 0;              // flag: 1 when the login is correct
    int c;

    printf("=== LOGIN SYSTEM ===\n");

    // ONE single block that repeats: 3 times at most, fewer if they get in
    for (attempt = 1; attempt <= 3 && !logged_in; attempt++) {
        printf("\nAttempt %d of 3\n", attempt);
        printf("User initial: ");
        scanf(" %c", &initial);           // the space skips the previous Enter
        printf("PIN: ");
        if (scanf("%d", &pin) != 1) {
            while ((c = getchar()) != '\n' && c != EOF);
            pin = -1;                     // a PIN that is never right
        }

        if (initial == 's' && pin == PIN_OK) {
            logged_in = 1;
        } else {
            printf("Wrong user or PIN\n");
        }
    }

    if (logged_in) {
        printf("\nAccess granted. Welcome, sergio.\n");
    } else {
        printf("\nAccount locked: 3 failed attempts\n");
    }
    return 0;
}

Why it is done this way:

  • A single block: the body of the for is what was copied three times in conditionals. If tomorrow the login allows 5 attempts, you change one number.
  • Two ways out in the condition: attempt <= 3 && !logged_in. If the login is right on the first attempt, logged_in becomes 1 and the for does not ask again.
  • The final message depends on the flag, not the counter: on leaving after getting it right on the third, attempt is 4, the same as on leaving after failing all three. Only logged_in tells the two cases apart.
  • scanf(" %c") with a space: without it, the second lap would read the Enter left by the previous PIN as the initial and the login would fail without the user being able to type anything.
  • pin = -1 if scanf fails: if someone types letters in the PIN, they are thrown away and the PIN is one that is never right, instead of keeping the previous attempt’s.

Solution to exercise 5

#include <stdio.h>

int main(void) {
    int option;
    float a, b, result = 0, largest = 0;
    char again = 'n';
    int operations = 0;
    int exp, j;
    int c;

    printf("=== INTERACTIVE CALCULATOR ===\n");

    do {
        printf("\n1. Add         2. Subtract\n");
        printf("3. Multiply    4. Divide\n");
        printf("5. Modulo      6. Power\n");
        printf("0. Quit\n");

        do {                               // valid option
            printf("Operation: ");
            if (scanf("%d", &option) != 1) {
                while ((c = getchar()) != '\n' && c != EOF);
                option = -1;
            }
            if (option < 0 || option > 6) {
                printf("Error: choose between 0 and 6\n");
            }
        } while (option < 0 || option > 6);

        if (option == 0) {
            break;                         // leaves the main do...while
        }

        printf("Number A: ");
        scanf("%f", &a);
        do {                               // B cannot be 0 when dividing
            printf("Number B: ");
            scanf("%f", &b);
            if ((option == 4 || option == 5) && b == 0) {
                printf("Error: you cannot divide by zero\n");
            }
        } while ((option == 4 || option == 5) && b == 0);

        switch (option) {
            case 1: result = a + b; printf("%.2f + %.2f = %.2f\n", a, b, result); break;
            case 2: result = a - b; printf("%.2f - %.2f = %.2f\n", a, b, result); break;
            case 3: result = a * b; printf("%.2f * %.2f = %.2f\n", a, b, result); break;
            case 4: result = a / b; printf("%.2f / %.2f = %.4f\n", a, b, result); break;
            case 5:
                result = (int)a % (int)b;  // % only works with integers
                printf("%d %% %d = %.0f\n", (int)a, (int)b, result);
                break;
            default:
                exp = (int)b;
                result = 1;                // product accumulator: starts at 1
                for (j = 0; j < exp; j++) {
                    result *= a;
                }
                printf("%.2f ^ %d = %.2f\n", a, exp, result);
                break;
        }

        operations++;
        if (operations == 1 || result > largest) {
            largest = result;              // the first one is always the largest so far
        }

        printf("Another operation? (y/n): ");
        scanf(" %c", &again);
    } while (again == 'y' || again == 'Y');

    printf("\n--- Summary ---\n");
    printf("Operations done: %d\n", operations);
    if (operations > 0) {
        printf("Largest result: %.2f\n", largest);
    }
    return 0;
}

Why it is done this way:

  • Main do…while: the menu is shown at least once. Option 0 leaves with break, because after choosing it there is no need to ask for numbers or “Another operation?”.
  • The divisor is validated with its own do…while: the condition (option == 4 || option == 5) && b == 0 is only true when it has to repeat; when adding, a 0 is a normal number.
  • The power with a for: result starts at 1 (the neutral element of multiplication) and is multiplied by a exp times. With 2 and 10: 2¹⁰ = 1024.
  • The largest: the first operation always is (operations == 1); after that, only if it beats it. If it started with largest = 0 and every result were negative, the summary would say 0, which never came out.
  • (int)a % (int)b in the modulo: % does not work with float, which is why they are turned into integers.
LapOperationresultoperationslargest
115 + 722.00122.00 (the first)
210 / 3 (after rejecting 0)3.3333222.00
32 ^ 101024.0031024.00

Watch it step by step in Python Tutor

Python Tutor (pythontutor.com) runs the code line by line. Choose C and paste this cut-down version of snippet 1, without the break, which checks the numbers from 2 to 7:

#include <stdio.h>

int main(void) {
    int x, i, is_prime;

    for (x = 2; x <= 7; x++) {
        is_prime = 1;
        for (i = 2; i < x; i++);
        {
            if (x % i == 0) {
                is_prime = 0;
            }
        }
        printf("%d -> %d\n", x, is_prime);
    }
    return 0;
}

Output:

2 -> 0
3 -> 0
4 -> 0
5 -> 0
6 -> 0
7 -> 0

What to watch step by step:

  1. With x = 2, the arrow goes through the inner for line and jumps straight to the block of braces: the body of the for is the ;.
  2. With x = 5, the inner for does three laps (i = 2, 3, 4) without running anything, and reaches the block with i = 5.
  3. In the block, x % i is always x % x, which is 0: that is why every number comes out with is_prime = 0, even 5 and 7.
  4. Remove the ; and run it again: now 2, 3, 5 and 7 come out with 1.

Cheat sheet: loops in C

The essentials in 30 seconds: for when you know how many laps, while when you do not, do…while when it has to be done at least once (validating). The condition is checked before every lap (after, in do…while), and on exit the variable is the first value that made it false. break leaves the innermost loop; continue jumps to the next lap. Every scanf inside a loop gets checked.

I want to…CodeResult
Repeat n timesfor (i = 0; i < n; i++)n laps, i ends at n
Count from 1 to n, both includedfor (i = 1; i <= n; i++)n laps, i ends at n + 1
Count backwardsfor (i = n; i >= 1; i--)n, n − 1, …, 1
Go in steps of 2for (i = 0; i < n; i += 2)0, 2, 4…
Two variables at oncefor (i = 0, j = n; i < j; i++, j--)They cross in the middle
Repeat until a sentinelwhile (scanf("%d", &x) == 1 && x != 0)Stops at 0 or at letters
Ask for a value until it is validdo { … } while (bad value);At least once
Loop with the way out insidewhile (1) { … if (end) break; }Leaves through the break
Skip a lapif (…) continue;In a for, carries on with i++
Leave two loopsA flag in both conditionsfor (…; … && !done; …)
Empty the input after a failed scanfwhile ((c = getchar()) != '\n' && c != EOF);Throws away what was typed up to the Enter

Templates from the exercises:

// Validate a number (exercises 1, 2, 3 and 5)
do {
    printf("Value: ");
    if (scanf("%d", &value) != 1) {
        while ((c = getchar()) != '\n' && c != EOF);
        value = -1;                      // a value that falls into the error
    }
} while (value < MIN || value > MAX);

// Search with break (exercise 3)
found = 0;
for (i = 2; i * i <= x; i++) {
    if (x % i == 0) {
        found = 1;
        break;                           // only leaves this for
    }
}

// Several attempts with a flag (exercise 4)
for (attempt = 1; attempt <= MAX && !ok; attempt++) {
    /* ask and check; if it is right: ok = 1; */
}
if (ok) { /* success */ } else { /* locked */ }
SymptomLikely cause
The body runs only once with an odd value; after for (...)
The program hangs without printing anything; after while (...)
A question repeats without waiting for you to typescanf failed with letters and the input was not emptied
One lap too many (“Attempt 4 of 3”)<= with a counter that starts at 0
break does not stop the outer loopbreak only leaves the innermost one: use a flag
continue leaves the while stuckThe step is after the continue
Garbage result in an accumulatorThe starting value (0 or 1) before the loop is missing
Negative or absurd numbers in a factorialint overflow: use long long

Before handing in, ask yourself:

  1. How many laps does each loop do with the data in the task? (Count them with the < and <= rule.)
  2. What is the control variable worth on exit, and do I use it afterwards?
  3. Is there any ; right after a for (...) or a while (...)?
  4. What happens if the user types letters at every scanf?

That wraps up loops. The next topic is pointers in C, where a loop will walk through memory for real and an extra lap no longer prints a stop that does not exist, but reads memory that is not yours. And once you have the whole IC2 practical syllabus, test yourself with the IC2 practical mock exam.

Keep goingAll C articles

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 *