For a program to do more than run top to bottom in a straight line, we need conditions that branch and loops that repeat. These are the basic building blocks of every algorithm in software. In this lesson we get comfortable with control flow in C.
1. Branching (conditionals)
Branching lets a program decide which piece of code to run, based on whether a condition comes out true or false.
Using if-else
This is the most common branching structure. The condition inside if must produce a
logical value: true (non-zero) or false (zero).
#include <stdio.h>
int main() {
double score;
printf("Nhap diem cua ban: ");
scanf("%lf", &score);
if (score >= 9.0) {
printf("Hoc sinh xuat sac\n");
} else if (score >= 8.0) {
printf("Hoc sinh gioi\n");
} else if (score >= 6.5) {
printf("Hoc sinh kha\n");
} else if (score >= 5.0) {
printf("Hoc sinh trung binh\n");
} else {
printf("Chua dat yeu cau\n");
}
return 0;
}
Using switch-case
When there are many branches keyed on the specific value of an integer or character,
switch-case is a cleaner alternative to a long if-else if chain. Do not
forget the break statement that ends each branch.
#include <stdio.h>
int main() {
int choice;
printf("1. Choi game moi\n");
printf("2. Tai game cu\n");
printf("3. Thoat\n");
printf("Nhap lua chon: ");
scanf("%d", &choice);
switch(choice) {
case 1:
printf("Dang khoi tao game moi...\n");
break;
case 2:
printf("Dang tai du lieu game cu...\n");
break;
case 3:
printf("Dang thoat game...\n");
break;
default:
printf("Lua chon khong hop le.\n");
}
return 0;
}
switch-casebreak at the end of a case, execution keeps
running straight down into the following cases, one after another, until it meets a
break or reaches the end of the switch. See
the C switch statement on cppreference.com.Type restrictions: unlike modern languages such as JavaScript, Python or Java, the controlling expression of a C
switch
only accepts integers or characters (int, char,
enum). Floating point types (float, double) and strings are
not supported at all.
C. Short-circuit evaluation
When evaluating a compound logical expression using && (AND) or
|| (OR), C applies short-circuit evaluation:
-
For
A && B: ifAevaluates to false (0),Bis never evaluated at all, because the whole expression is certain to be false. -
For
A || B: ifAevaluates to true (non-zero),Bis never evaluated, because the whole expression is certain to be true.
A safety use: this technique lets you write notably safer code, for example
if (ptr != NULL && *ptr == 10). If ptr is NULL, the second condition
β dereferencing *ptr, which would crash the program β is never reached.
The trap: avoid putting functions with side effects on the right-hand side, for
example if (x == 1 && saveData()). If x != 1, then
saveData() never runs at all.
D. What the compiler really does: jump tables
Why is switch-case faster than a long if-else if chain once there are dozens
of options? When the case constants sit close together (1, 2, 3, 4, 5 and so on), the C compiler does
not emit a sequence of comparisons. Instead it builds a jump table β an array of code
addresses in memory. At run time the CPU simply uses the value as an index into that array, reads the
address of the matching branch and jumps straight there. That is \(O(1)\), far cheaper than performing
\(n\) comparisons the way if-else would.
2. Loops
Loops run a block of code over and over without you having to copy it out repeatedly.
The for loop
Use it when you know exactly how many iterations you need. It has three parts: initialisation; loop condition; step expression.
#include <stdio.h>
int main() {
// In cac so tu 1 den 5
for (int i = 1; i <= 5; i++) {
printf("i = %d\n", i);
}
return 0;
}
The while and do-while loops
while: tests the condition first, and only runs the body if it holds.do-while: runs the body at least once first, and only then tests the condition.
#include <stdio.h>
int main() {
int count = 5;
while (count > 0) {
printf("count = %d\n", count);
count--;
}
int input;
// do-while phu hop de kiem tra tinh hop le cua du lieu nhap vao
do {
printf("Nhap mot so lon hon 10: ");
scanf("%d", &input);
} while (input <= 10);
printf("Thanh cong! So ban nhap la: %d\n", input);
return 0;
}
Steering a loop from inside: break and continue
All three loop forms test their condition at the top or the bottom of each pass. But often you need to decide in the middle of the body: you found what you were looking for and want out, or you hit an invalid element and want to skip it and carry on. C has one statement for each of those.
-
breakβ leaves the loop entirely, abandoning every remaining iteration. You met it inswitch-caseabove; inside a loop it does exactly the same kind of thing. -
continueβ skips the rest of the current pass and jumps to the next one. The loop keeps running; this particular round just stops early.
#include <stdio.h>
int main() {
int numbers[] = {4, 7, -2, 9, 0, 13, 6};
int count = 7;
// continue: skip the negative values, keep going
printf("Positive values: ");
for (int i = 0; i < count; i++) {
if (numbers[i] < 0) {
continue; // skip THIS round only
}
printf("%d ", numbers[i]);
}
printf("\n");
// break: stop at the first zero
printf("Before the first zero: ");
for (int i = 0; i < count; i++) {
if (numbers[i] == 0) {
break; // leave the loop entirely
}
printf("%d ", numbers[i]);
}
printf("\n");
return 0;
}
Run it and the two printed lines differ clearly: the first is 4 7 9 0 13 6 β the negative
value was skipped but the loop still walked the whole array; the second is 4 7 -2 9 β it
stopped dead at the zero and never looked at 13 or 6. Same array, same
for structure, differing in exactly this:
continue skips one pass, break skips all the rest.
continue inside while can hangfor loop the step expression (i++) still runs when you call
continue, so the loop always advances. In a while loop, though, the
counter is usually incremented at the end of the body β and if continue jumps
over that line, the counter never moves and the program loops forever. When using
continue inside while, increment the counter before the
continue.
The goto statement and the resource cleanup pattern
goto is usually treated as a trap, because it makes it easy to produce spaghetti code
whose flow nobody can follow. Yet in systems-level C β the Linux kernel source, for instance β
goto is used very widely for
cleaning up resources at a single exit point when an error happens partway through,
avoiding repeated blocks of memory-freeing and file-closing code.
#include <stdio.h>
#include <stdlib.h>
int process_data() {
int *buffer1 = (int*) malloc(100 * sizeof(int));
if (buffer1 == NULL) goto cleanup_none;
int *buffer2 = (int*) malloc(200 * sizeof(int));
if (buffer2 == NULL) goto cleanup_buf1;
// Simulate an error happening midway through processing
int error_occurred = 1;
if (error_occurred) goto cleanup_all;
// Everything succeeded
free(buffer2);
free(buffer1);
return 0;
// A single, central cleanup point
cleanup_all:
printf("Error occurred, releasing both buffers...\n");
free(buffer2);
cleanup_buf1:
printf("Releasing buffer 1...\n");
free(buffer1);
cleanup_none:
return -1;
}
This is one of the very few cases where goto counts as clean code. The key point: every
error branch jumps to the same place, and the labels are ordered
in reverse of the allocation order β so each label releases exactly what had been allocated
before it. Written with nested if statements instead, the freeing code is duplicated in
every branch, and forgetting it in just one of them leaks memory.
3. A real algorithm: testing for a prime number
Let's combine if-else with a for loop to solve a classic problem: deciding
whether a number n is prime.
A prime number is greater than 1 and divisible only by 1 and itself.
#include <stdio.h>
#include <math.h>
int main() {
int n;
printf("Nhap vao mot so nguyen: ");
scanf("%d", &n);
if (n < 2) {
printf("%d khong phai la so nguyen to\n", n);
return 0;
}
int isPrime = 1; // 1 nghia la dung, 0 nghia la sai
// Lap tu 2 den can bac hai cua n de kiem tra chia het
// Su dung sqrt() trong math.h giup thuat toan chay nhanh hon rat nhieu
for (int i = 2; i <= sqrt(n); i++) {
if (n % i == 0) {
isPrime = 0; // Co uoc so khac -> khong phai so nguyen to
break; // Ngat luong lap som vi khong can kiem tra them nua
}
}
if (isPrime == 1) {
printf("%d la so nguyen to\n", n);
} else {
printf("%d khong phai la so nguyen to\n", n);
}
return 0;
}
π₯ Download the sample source: prime_check.c
A small challenge for you
Write a program that prints every prime number below 100 using nested loops β the outer loop walking from 2 to 99, and the inner loop testing each candidate for primality.
while loop and a do-while loop in C?
Comments