Skip to main content

Lecture 3: Loops

Prerequisites: Lecture 2 (Conditions — if/else, relational and logical operators, switch)

1. Overview

  • Programmes frequently need to perform the same action many times; this chapter introduces the repetition structure (loops) that eliminates redundant code and enables data-driven iteration.
  • After studying this chapter you will be able to choose among while, do-while, and for loops, control iteration with break and continue, nest loops for multi-dimensional tasks, and distinguish counter-controlled from event-controlled repetition.

2. Learning Outcomes

Upon completing this chapter, students will be able to:

  1. Explain the difference between entry-controlled loops (while, for) and the exit-controlled loop (do-while), and select the appropriate construct for a given problem.
  2. Use compound assignment operators (+=, -=, *=, /=, %=) and the increment/decrement operators (++, --) correctly, distinguishing prefix from postfix behaviour.
  3. Design counter-controlled loops (known iteration count) and event-controlled (sentinel) loops (unknown iteration count).
  4. Apply the for loop idiom for counting tasks and understand how its three clauses map onto initialisation, condition, and modification.
  5. Construct nested loops to solve two-dimensional problems such as pattern printing and multiplication tables.
  6. Use break to exit a loop early and continue to skip to the next iteration, including the deliberate use of infinite loops with break.
  7. Apply the principle of parameterisation to replace hard-coded limits with variables, making programmes more general and reusable.

3. Key Terms

Term (EN)中文Notes
repetition structure / loop循环结构The third fundamental control structure; repeats a block while a condition holds.
entry-controlled loop入口控制循环Condition tested before the body executes (while, for). Body may execute zero times.
exit-controlled loop出口控制循环Condition tested after the body executes (do-while). Body always executes at least once.
while loopwhile 循环Entry-controlled; most general form of loop.
do-while loopdo-while 循环Exit-controlled; guarantees at least one execution of the body.
for loopfor 循环Entry-controlled; bundles initialisation, condition, and modification into one header.
counter-controlled loop计数器控制循环Iteration count is known in advance (e.g., "repeat 10 times").
event-controlled / sentinel loop事件/哨兵控制循环Iteration ends when a special sentinel value is encountered; count is unknown in advance.
sentinel value哨兵值A marker value distinct from valid data that signals the end of input (e.g., −1).
compound assignment operator复合赋值运算符+=, -=, *=, /=, %= — shorthand for var = var op expr.
increment / decrement operator自增/自减运算符++ / --. Prefix form modifies before use; postfix form modifies after use.
nested loop嵌套循环A loop placed inside the body of another loop; the inner loop completes all iterations per outer iteration.
breakbreak 语句Immediately exits the innermost enclosing loop (or switch).
continuecontinue 语句Skips the remainder of the current iteration and jumps to the next condition check (or modification step in for).
infinite loop无限循环A loop whose condition never becomes false (e.g., while (1)); must contain a break or external termination.
parameterisation参数化Replacing hard-coded constants with variables so that a programme adapts to different inputs without rewriting.

4. Core Concepts

4.1 The Need for Repetition

Consider printing the integers 1 to 5. Without a loop the programme requires five separate printf calls:

printf("1\n");
printf("2\n");
printf("3\n");
printf("4\n");
printf("5\n");

If the requirement changes to printing 1 to 10 000, this approach is clearly unworkable. A loop lets us write the action once and execute it as many times as needed, driven by a condition that eventually becomes false.

All programmes can be written using three control structures: sequence (Lecture 0–1), selection (Lecture 2), and repetition (this lecture). Mastering loops completes the structural foundation upon which all subsequent topics build.

Takeaway: Loops eliminate code duplication and make programmes adaptable to varying data sizes.

4.2 The while Loop

The while loop is an entry-controlled (pre-test) loop. It evaluates the condition before each iteration; if the condition is false on the very first test, the body never executes at all.

while (condition)
{
statement;
}

The general pattern involves three components, though they appear in separate locations:

  1. Initialisation — set the loop variable before the while.
  2. Condition — tested at the top of each iteration.
  3. Modification — update the loop variable inside the body to ensure eventual termination.

Forgetting the modification step is the most common cause of infinite loops.

Minimal example File: p03_while.c Build: gcc p03_while.c -o p03_while Run: ./p03_while

#include <stdio.h>

int main(void)
{
int i = 1; /* 1. Initialisation */
while (i <= 5) { /* 2. Condition */
printf("%d\n", i);
i = i + 1; /* 3. Modification */
}
return 0;
}

What you should see: The integers 1 through 5, each on its own line.

Takeaway: The while loop is the most general loop construct; when you are unsure which loop to use, while is always a safe choice.

4.3 Compound Assignment Operators

C provides shorthand operators that combine an arithmetic operation with assignment. They reduce verbosity and are especially convenient inside loop bodies.

OperatorExampleEquivalent
=a = ba = b
+=a += ba = a + b
-=a -= ba = a - b
*=a *= ba = a * b
/=a /= ba = a / b
%=a %= ba = a % b

Using the while example from §4.2, the modification i = i + 1 may equivalently be written i += 1.

Takeaway: Compound assignment operators are syntactic sugar; use them for clarity, but ensure you understand the equivalent longhand form.

4.4 Increment and Decrement Operators

The increment (++) and decrement (--) operators add or subtract 1 from a variable. Each has two forms:

  • Prefix (++i / --i): the variable is modified before its value is used in the surrounding expression.
  • Postfix (i++ / i--): the current value is used first, and the variable is modified afterwards.

When the operator appears as a standalone statement (e.g., i++; on its own line), the two forms are functionally identical. The distinction matters only when the expression's value is consumed in the same statement, such as printf("%d", count++) versus printf("%d", ++count).

Minimal example File: p03_incdec.c Build: gcc p03_incdec.c -o p03_incdec Run: ./p03_incdec

#include <stdio.h>

int main(void)
{
int count = 0;

printf("Postfix increment loop:\n");
while (count < 5) {
printf("Count: %d\n", count++); /* prints 0,1,2,3,4 */
}

count = 0;
printf("\nPrefix increment loop:\n");
while (count < 5) {
printf("Count: %d\n", ++count); /* prints 1,2,3,4,5 */
}

return 0;
}

What you should see: The postfix loop prints 0 through 4; the prefix loop prints 1 through 5.

Takeaway: Prefer using i++ or ++i as a standalone statement for the loop update; embedding it inside a complex expression is error-prone and hurts readability.

4.5 Counter-Controlled vs Event-Controlled Loops

Loops fall into two broad categories based on how termination is determined:

Counter-controlled loops iterate a known number of times. A counter variable is initialised, tested, and incremented on each pass. The class-average programme below illustrates this: we know there are exactly num_students grades to read.

Event-controlled (sentinel) loops iterate an unknown number of times. Termination is triggered by a special sentinel value — a value chosen to be outside the range of valid data. For example, using −1 as a sentinel when all legitimate grades are in [0, 100].

The sentinel approach has a standard pattern: read the first value before the loop, then test for the sentinel in the while condition, and read the next value at the end of the loop body.

Counter-controlled example (average of a fixed class size):

File: p03_counter.c Build: gcc p03_counter.c -o p03_counter Run: ./p03_counter

#include <stdio.h>

int main(void)
{
int num_students;
printf("Enter the number of students: ");
scanf("%d", &num_students);

float grade, total = 0.0f;
int i = 1;
while (i <= num_students) {
printf("Enter the grade of student %d (0-100): ", i);
scanf("%f", &grade);
total += grade;
i++;
}

float avg = total / num_students;
printf("The average of grades is %.1f\n", avg);
return 0;
}

Sentinel-controlled example (average of an unknown class size):

File: p03_sentinel.c Build: gcc p03_sentinel.c -o p03_sentinel Run: ./p03_sentinel

#include <stdio.h>

int main(void)
{
int i = 1;
float grade, total = 0.0f;

printf("Enter grades (0-100). Enter -1 to stop.\n");
printf("Enter the grade of student %d: ", i);
scanf("%f", &grade); /* priming read */

while (grade >= 0) {
total += grade;
i++;
printf("Enter the grade of student %d: ", i);
scanf("%f", &grade); /* update read */
}

float avg = total / (i - 1);
printf("The average of grades is %.1f\n", avg);
return 0;
}

Takeaway: Use a counter-controlled loop when the iteration count is known before the loop starts; use a sentinel loop when the count depends on run-time input.

4.6 Parameterisation and Abstraction

The first version of the class-average programme hard-coded the class size as 5. If the class size changes, the source code must be edited and recompiled. By replacing the literal 5 with a variable num_students read from the user, the programme becomes general-purpose — this is parameterisation.

Parameterisation is a manifestation of abstraction, a fundamental principle in software engineering. Abstracted code can be reused across different contexts without modification. Whenever you find yourself writing a magic number in a loop bound or a calculation, ask: "Could this be a variable instead?"

Takeaway: Always parameterise loop bounds and other constants so that programmes adapt to different inputs without source-code changes.

4.7 The do-while Loop

The do-while loop is an exit-controlled (post-test) loop. The body executes first, and the condition is tested afterwards. This guarantees that the body runs at least once, regardless of the condition's initial value.

do
{
statement;
}
while (condition);

Note the semicolon after the closing parenthesis — a frequent source of syntax errors.

The do-while is most naturally used when the first iteration must happen unconditionally — for example, displaying a menu and reading a choice before it is meaningful to test whether the choice is valid.

Minimal example File: p03_dowhile.c Build: gcc p03_dowhile.c -o p03_dowhile Run: ./p03_dowhile

#include <stdio.h>

int main(void)
{
int i = 1;
do {
printf("%d\n", i);
i++;
} while (i <= 5);
return 0;
}

What you should see: The integers 1 through 5.

Sentinel version using do-while — the sentinel-controlled average can also be written with do-while, though the post-processing is slightly more involved because the sentinel value is added to the total before the test:

do {
printf("Enter the grade of student %d: ", i);
scanf("%f", &grade);
total += grade;
i++;
} while (grade >= 0);
/* Correct for the sentinel that was added: */
float avg = (total - grade) / (i - 2);

This works, but the need to subtract the sentinel afterwards makes the while-with-priming-read pattern generally cleaner for sentinel loops.

Takeaway: Use do-while when the body must execute at least once; otherwise prefer while or for.

4.8 The for Loop

The for loop bundles all three loop-control components — initialisation, condition, and modification — into a single header:

for (initialisation; condition; modification)
{
statement;
}

Execution proceeds as follows:

  1. The initialisation executes exactly once.
  2. The condition is evaluated. If false, the loop terminates immediately.
  3. If true, the body executes.
  4. The modification executes.
  5. Control returns to step 2.

The for loop is the idiomatic choice for counter-controlled repetition. It keeps the loop variable's lifecycle compact and visible in one place, reducing the risk of forgetting the modification step.

Minimal example File: p03_for.c Build: gcc p03_for.c -o p03_for Run: ./p03_for

#include <stdio.h>

int main(void)
{
for (int i = 1; i <= 5; i++) {
printf("%d\n", i);
}
return 0;
}

What you should see: The integers 1 through 5.

The modification clause is not limited to i++. Any expression is valid; for example, i += 2 produces even numbers:

for (int i = 0; i <= 10; i += 2) {
printf("%d\n", i); /* prints 0, 2, 4, 6, 8, 10 */
}

Equivalence with while: Any for loop can be rewritten as a while loop (and vice versa). If the initialisation and modification clauses are omitted, the for degenerates into a while:

int i = 1;
for (; i <= 5; ) { /* equivalent to while (i <= 5) */
printf("%d\n", i);
i++;
}

Takeaway: Use for when the iteration count is determined by a simple counter; use while when termination depends on a more complex or event-driven condition.

4.9 Nested Loops

A nested loop is a loop placed inside the body of another loop. For each single iteration of the outer loop, the inner loop runs through all of its iterations from start to finish. If the outer loop iterates m times and the inner loop iterates n times, the body of the inner loop executes m × n times in total.

Nested loops are essential for problems involving two-dimensional structures: printing patterns, processing matrices, generating multiplication tables, and comparing elements pairwise.

Minimal example — number triangle File: p03_nested.c Build: gcc p03_nested.c -o p03_nested Run: ./p03_nested

#include <stdio.h>

int main(void)
{
int n;
printf("Enter the number of rows: ");
scanf("%d", &n);

for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++) {
printf("%d", j);
}
printf("\n");
}
return 0;
}

What you should see (for n = 4):

1
12
123
1234

Combining different loop types — the following fragment uses a for as the outer loop, a while as the inner loop, and a do-while to print a separator. This demonstrates that any loop type may be nested within any other:

int num, multiplier, max_num = 25;
for (num = 1; num <= max_num; num++) {
printf("Multiplication Table for %d:\n", num);
multiplier = 1;
while (multiplier <= max_num) {
printf("%d * %d = %2d\n", num, multiplier, num * multiplier);
multiplier++;
}
int count = 0;
do {
printf("-");
count++;
} while (count < 16);
printf("\n");
}

Takeaway: Keep nesting shallow (ideally two levels) and use meaningful variable names for each level to maintain readability.

4.10 break and continue

Two statements modify the normal flow of a loop:

  • break — immediately exits the innermost enclosing loop. Execution continues with the first statement after the loop. (We met break in switch statements in Lecture 2; it works identically in loops.)
  • continue — skips the remaining statements in the current iteration and jumps directly to the next condition evaluation (for while and do-while) or to the modification step (for for).

Minimal example File: p03_breakcont.c Build: gcc p03_breakcont.c -o p03_breakcont Run: ./p03_breakcont

#include <stdio.h>

int main(void)
{
printf("break example:\n");
for (int i = 1; i <= 5; i++) {
if (i == 3) {
break; /* exits the loop entirely */
}
printf("%d\n", i);
}
/* Output: 1, 2 */

printf("\ncontinue example:\n");
for (int i = 1; i <= 5; i++) {
if (i == 3) {
continue; /* skips to i++ */
}
printf("%d\n", i);
}
/* Output: 1, 2, 4, 5 */

return 0;
}

What you should see: The break section prints 1 and 2 only. The continue section prints 1, 2, 4, 5 (skipping 3).

Takeaway: Use break sparingly to handle early-exit conditions; use continue to skip unwanted iterations without increasing indentation depth.

4.11 Infinite Loops and Deliberate Use of break

An infinite loop is a loop whose condition never becomes false. In C the canonical form is while (1) (or equivalently for (;;)). On its own an infinite loop hangs the programme, but combined with an internal break it provides an elegant pattern for event-driven input processing:

while (1) {
/* prompt and read */
if (exit_condition) {
break;
}
/* process */
}

This pattern is often cleaner than a sentinel while loop because it avoids the priming read and the duplicated scanf call.

Minimal example File: p03_infinite.c Build: gcc p03_infinite.c -o p03_infinite Run: ./p03_infinite

#include <stdio.h>

int main(void)
{
int num;

while (1) {
printf("Enter a positive number [0 to stop]: ");
scanf("%d", &num);

if (num == 0) {
printf("Exiting loop...\n");
break;
}
printf("You entered: %d\n", num);
}
return 0;
}

What you should see: The programme repeatedly prompts for input, echoing each value, until the user enters 0.

Takeaway: Infinite loops with break are a legitimate and often preferred idiom for interactive programmes; the key is ensuring that the break condition is always reachable.

5. Worked Examples

Example 1: Factorial Calculation

Problem: Read a non-negative integer N from the user and compute N! (N factorial). Recall that N! = 1 × 2 × 3 × … × N, with 0! = 1 by convention.

Steps:

  1. Read N.
  2. Initialise result = 1.
  3. Use a for loop from 1 to N, multiplying result by the loop counter on each iteration.
  4. Print the result.

Reference code File: p03_ex1.c Build: gcc p03_ex1.c -o p03_ex1 Run: ./p03_ex1

#include <stdio.h>

int main(void)
{
int n;
printf("Enter a number: ");
scanf("%d", &n);

long result = 1;
for (int i = 1; i <= n; i++) {
result *= i;
}

printf("Factorial of %d is %ld\n", n, result);
return 0;
}

Output:

Enter a number: 5
Factorial of 5 is 120

Why it works: The for loop is a natural fit because we know exactly how many multiplications are required (N). The variable result acts as an accumulator, a common loop pattern in which a running total (or product) is built up across iterations. We use long to accommodate the rapid growth of factorial values.

Example 2: Reverse a Number

Problem: Read an integer and print its digits in reverse order.

Steps:

  1. Read the number.
  2. In each iteration: extract the last digit with num % 10, print (or accumulate) it, then remove the last digit with num /= 10.
  3. Repeat while num is non-zero.

Reference code File: p03_ex2.c Build: gcc p03_ex2.c -o p03_ex2 Run: ./p03_ex2

#include <stdio.h>

int main(void)
{
int num;
printf("Enter a number: ");
scanf("%d", &num);

int reversed = 0;
while (num != 0) {
int digit = num % 10;
reversed = reversed * 10 + digit;
num /= 10;
}

printf("Reversed number: %d\n", reversed);
return 0;
}

Output:

Enter a number: 1234
Reversed number: 4321

Why it works: The modulus operator % 10 isolates the units digit, and integer division / 10 shifts all remaining digits one place to the right. The while loop terminates naturally when all digits have been consumed (i.e., num becomes 0). This is an event-controlled loop — we do not know in advance how many digits the number contains.

Example 3: First N Prime Numbers

Problem: Read an integer N and print the first N prime numbers. A prime number is greater than 1 and divisible only by 1 and itself.

Steps:

  1. Read N.
  2. Start testing candidates from 2. For each candidate, check divisibility by all integers from 2 up to its square root (trial division).
  3. If no divisor is found, the candidate is prime — print it and increment a counter.
  4. Continue until the counter reaches N.

Reference code File: p03_ex3.c Build: gcc p03_ex3.c -o p03_ex3 Run: ./p03_ex3

#include <stdio.h>

int main(void)
{
int n;
printf("Enter the number of prime numbers to display: ");
scanf("%d", &n);

int count = 0;
int candidate = 2;

printf("First %d prime numbers:", n);
while (count < n) {
int is_prime = 1;
for (int d = 2; d * d <= candidate; d++) {
if (candidate % d == 0) {
is_prime = 0;
break; /* no need to test further */
}
}
if (is_prime) {
printf(" %d", candidate);
count++;
}
candidate++;
}
printf("\n");
return 0;
}

Output:

Enter the number of prime numbers to display: 5
First 5 prime numbers: 2 3 5 7 11

Why it works: The outer while loop is event-controlled — it runs until we have found N primes. The inner for loop is counter-controlled — it tests divisors from 2 up to √candidate. The break inside the inner loop provides an early exit as soon as a factor is discovered, avoiding unnecessary computation. The condition d * d <= candidate is equivalent to d <= √candidate but avoids floating-point arithmetic.

6. Common Mistakes

  1. Forgetting the modification step in a while loop: If the loop variable is never updated, the condition never becomes false and the programme hangs in an infinite loop. Always verify that each while body modifies a variable that appears in the condition.

  2. Off-by-one errors in loop bounds: Using < instead of <= (or vice versa) causes the loop to execute one too few or one too many times. Trace through the first and last iterations on paper to verify correctness.

  3. Confusing prefix and postfix ++/-- inside expressions: printf("%d", count++) prints the old value, whereas printf("%d", ++count) prints the new value. When the distinction matters, prefer separating the increment from the expression: increment on its own line, then use the variable.

  4. Missing the semicolon after do-while: The construct do { ... } while (condition) requires a trailing semicolon. Omitting it is a syntax error that may produce a confusing diagnostic.

  5. Placing a semicolon directly after for (...) or while (...): Writing for (i = 0; i < 5; i++); creates an empty loop body (the semicolon is the body), so the block {...} that follows executes only once. This is a silent bug — the compiler may not warn.

  6. Modifying the loop counter inside a for body: Although legal, changing the counter variable inside the body (in addition to the modification clause) leads to confusing and error-prone code. Let the for header be the sole authority on the counter's evolution.

  7. Using break or continue without understanding scope: Both statements affect only the innermost enclosing loop. In nested loops, a break exits only the inner loop; the outer loop continues normally. If you need to exit multiple levels, consider a flag variable or restructuring the logic.

7. Practice

7.1 Basic

  • P1 — Print Numbers with while: Write a programme that prints the integers from 1 to 10 using a while loop. Output: 1 2 3 4 5 6 7 8 9 10 Acceptance: All ten numbers appear on one line, separated by spaces.

  • P2 — Sum of N Natural Numbers: Ask the user for a positive integer N and compute the sum 1 + 2 + … + N using a while loop. Input/Output: Enter a number: 5Sum of first 5 natural numbers is 15 Acceptance: Correct for any positive N.

  • P3 — Print Even Numbers: Print all even numbers between 1 and 20 (inclusive). Output: 2 4 6 8 10 12 14 16 18 20 Acceptance: Correct list; implemented with a loop (not 10 printf calls).

7.2 Required

  • P4 — Multiplication Table: Ask the user for a number N and print its multiplication table from 1 to 9 using a do-while loop. Input/Output: Enter a number: 44 x 1 = 4, 4 x 2 = 8, …, 4 x 9 = 36. Acceptance: Correct table; uses do-while.

  • P5 — Factorial Calculation: Compute the factorial of a given non-negative integer N using a for loop. Input/Output: Enter a number: 5Factorial of 5 is 120 Acceptance: Handles N = 0 (result is 1) through at least N = 12 without overflow.

  • P6 — Reverse a Number: Read an integer and print its digits in reverse order. Input/Output: Enter a number: 1234Reversed number: 4321 Hint: Use % 10 to extract the last digit and / 10 to remove it. Acceptance: Correct for any positive integer.

  • P7 — Using break to Exit an Infinite Loop: Write a programme that runs an infinite loop, reading integers from the user. If the user enters −1, break out of the loop and print Loop terminated. Input/Output: User enters 1, 2, 4, -1 → programme echoes each value, then prints Loop terminated. Acceptance: Loop exits only on −1; other negative numbers are echoed normally.

  • P8 — Skipping Even Numbers with continue: Print the integers from 1 to 10, but use continue to skip even numbers. Output: 1 3 5 7 9 Acceptance: Only odd numbers appear; implemented using continue.

7.3 Optional

  • P9 — First N Prime Numbers: Ask the user for N and print the first N prime numbers using nested loops and the trial-division method. Input/Output: Enter the number of prime numbers to display: 5First 5 prime numbers: 2 3 5 7 11 Hint: A number is prime if it has no divisor between 2 and its square root. Acceptance: Correct for N up to at least 50.

  • P10 — Right-Angled Triangle Pattern: Read the number of rows N and print a right-angled triangle of * characters using nested for loops. Input/Output: Enter the number of rows: 5

    *
    * *
    * * *
    * * * *
    * * * * *

    Acceptance: Correct pattern for any N ≥ 1.

8. Mini Task

Goal: Build a number-guessing game. The programme selects a secret number (hard-code it, or use rand() if you wish to experiment) in the range 1–100. The user repeatedly guesses; after each guess the programme reports "Too high", "Too low", or "Correct!". Track the number of attempts and print it when the user succeeds. Use an infinite loop with break.

Deliverables:

  • README.md: Compilation and run instructions; a sample session showing 4–5 guesses.
  • src/guess.c: The source file.
  • report.md: A brief summary (200–300 words) describing which loop construct you chose, why, and how you handled invalid input.

9. Further Reading

  1. Deitel & Deitel, C How to Program (9th ed.): Chapter 4 — C Program Control.
  2. King, C Programming: A Modern Approach (2nd ed.): Chapter 6 — Loops.
  3. ISO/IEC 9899:2018 (C18 Standard): §6.8.5 — Iteration statements (while, do, for); §6.8.6.3 — The break statement; §6.8.6.2 — The continue statement.

10. Appendix

A. Loop Selection Guide

SituationRecommended loopReason
Iteration count known before the loop startsforAll three control clauses in one header; intent is clear.
Iteration count unknown; body may not need to executewhilePre-test ensures zero iterations are possible.
Iteration count unknown; body must run at least once (e.g., menu, input validation)do-whilePost-test guarantees first execution.
Event-driven exit from the middle of the bodywhile (1) + breakAvoids duplicated priming reads; clean separation of exit logic.

B. Tracing Pre/Postfix in a Loop Condition

Consider:

int count = 0;
while (count++ < 5) {
printf("Count: %d\n", count);
}

At each evaluation of the condition, count is compared before the increment (postfix), but the printf inside the body sees the already-incremented value:

IterationCondition test (count before ++)count inside body
10 < 5 → true1
21 < 5 → true2
32 < 5 → true3
43 < 5 → true4
54 < 5 → true5
65 < 5 → false(body not entered)

The loop body executes 5 times, printing 1 through 5. Although this pattern works, it sacrifices clarity; separating the increment from the condition is generally preferable.