Skip to main content

Lecture 2: Conditions

Prerequisites: Lecture 1 (Variables, data types, scanf/printf, arithmetic operators)

1. Overview

  • Programs must make decisions; this chapter introduces the selection structure that lets a programme choose which statements to execute based on run-time conditions.
  • After studying this chapter you will be able to write programmes that branch, compare values, combine Boolean conditions, and dispatch on discrete values.
  • Chapter 1 covered sequential execution and arithmetic; this chapter adds selection. Chapter 3 will complete the trio with repetition (loops).

2. Learning Outcomes

Upon completing this chapter, students will be able to:

  1. Use relational operators (==, !=, >, <, >=, <=) to compare values and predict the integer result (0 or 1) of each comparison.
  2. Construct if, if-else, nested if, and if-else ladder structures to express single-branch, two-branch, and multi-branch logic.
  3. Apply logical operators (&&, ||, !) to form compound conditions, and evaluate such expressions with correct precedence.
  4. Write switch statements for multi-way branching on integer or character values, including intentional fall-through.
  5. Identify and avoid common pitfalls such as confusing = with ==, omitting braces, and forgetting break in switch cases.

3. Key Terms

Term (EN)中文Notes
relational operator关系运算符Returns 1 (true) or 0 (false). Do not confuse with arithmetic operators.
logical operator逻辑运算符&&, ||, ! — used to combine or negate conditions.
selection structure选择结构One of the three fundamental control structures (sequence, selection, repetition).
condition/Boolean expression条件/布尔表达式Any expression whose value is interpreted as true (non-zero) or false (zero).
if statementif 语句Executes a block only when the condition is true.
if-else statementif-else 语句Two-branch selection: one block for true, another for false.
if-else ladderif-else 阶梯Chain of else if clauses testing conditions top-to-bottom.
nested if嵌套 ifAn if (or if-else) placed inside another if or else block.
switch statementswitch 语句Multi-way branch on an integer or character value.
fall-through穿透Default switch behaviour when break is omitted; execution continues into the next case.
short-circuit evaluation短路求值&& stops if the left operand is false; || stops if the left operand is true.
precedence优先级The order in which operators are evaluated. Arithmetic > relational > logical > assignment.
associativity结合性Direction of evaluation for operators of equal precedence (left-to-right or right-to-left).

4. Core Concepts

4.1 Structured Programming and the Selection Structure

Every programme, regardless of complexity, can be expressed using exactly three control structures:

  1. Sequence — statements execute in the order in which they are written.
  2. Selection — the programme chooses among alternative courses of action.
  3. Repetition — an action is repeated while some condition remains true.

Chapters 0–1 dealt exclusively with sequential code. This chapter introduces selection; Chapter 3 will add repetition. Together the three structures provide a complete foundation for structured programming.

C implements selection through relational and logical operators, the if family of statements, and the switch statement.

Takeaway: Selection is the mechanism by which a programme reacts differently to different input.

4.2 Relational Operators

Relational operators compare two values and produce an int result: 1 if the relation holds (true) or 0 if it does not (false). C provides six relational operators:

OperatorMeaning
==equal to
!=not equal to
>greater than
<less than
>=greater than or equal to
<=less than or equal to

Because the result is an ordinary integer, it can be stored in a variable, printed, or used in further arithmetic.

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

#include <stdio.h>

int main(void)
{
int a = 10, b = 5;
int result = a > b; /* result receives 1 */

printf("a > b evaluates to %d\n", result);

if (a > b) {
printf("a is greater than b\n");
}

return 0;
}

What you should see: a > b evaluates to 1 followed by a is greater than b.

Takeaway: A relational expression is itself an integer expression whose value is either 0 or 1.

4.3 Precedence and Associativity

Relational operators sit below arithmetic operators in the precedence table but above logical operators and assignment. The full table relevant to this chapter is:

PrecedenceOperatorsAssociativity
1 (highest)()left-to-right
2unary +, -, !, (type), sizeofright-to-left
3*, /, %left-to-right
4binary +, -left-to-right
5<, <=, >, >=left-to-right
6==, !=left-to-right
7&&left-to-right
8||left-to-right
9 (lowest)=right-to-left

A practical consequence is that a + b > c is parsed as (a + b) > c, because addition (precedence 4) binds more tightly than > (precedence 5). Similarly, == and != have lower precedence than the ordering operators <, >, <=, >=.

Worked trace — Given a = 1, b = 4, c = 3:

ExpressionStep-by-step evaluationResult
a + b + c1 + 4 + 38
a + b * c1 + (4 * 3) = 1 + 1213
b + 1 > 2(4 + 1) > 2 = 5 > 21 (true)
b + 1 > 2 == b > 2(5 > 2) == (4 > 2) = 1 == 11 (true)
b + 1 > 2 != c < 4(5 > 2) != (3 < 4) = 1 != 10 (false)

Note in the last two rows how > and < (precedence 5) are evaluated before == and != (precedence 6).

Takeaway: When in doubt, use parentheses to make evaluation order explicit; this also improves readability.

4.4 The if Statement

The if statement is the simplest selection construct. It evaluates a condition; if the condition is true (non-zero), the body executes. If false (zero), execution skips the body entirely.

if (condition)
{
statement;
}

The braces {} delimit a compound statement (block). Technically, when the body contains only a single statement the braces may be omitted, but our course coding standard requires braces in all cases for clarity and to prevent errors during later modification.

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

#include <stdio.h>

int main(void)
{
int age;

printf("Enter your age: ");
scanf("%d", &age);

if (age >= 18) {
printf("You are an adult.\n");
}

return 0;
}

What you should see: If the user enters 20, the programme prints You are an adult.; if the user enters 15, no additional output appears.

Takeaway: The if body is executed only when the condition evaluates to a non-zero value.

4.5 The if-else Statement

An if-else extends the simple if by providing an alternative block that executes when the condition is false. Exactly one of the two blocks will run.

if (condition)
{
/* executed when condition is true */
}
else
{
/* executed when condition is false */
}

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

#include <stdio.h>

int main(void)
{
int age;

printf("Enter your age: ");
scanf("%d", &age);

if (age >= 18) {
printf("You are an adult.\n");
} else {
printf("You are a minor.\n");
}

return 0;
}

What you should see: One of the two messages, depending on the value entered.

Takeaway: Use if-else when there are exactly two mutually exclusive outcomes.

4.6 Nested if Statements

A nested if is an if (or if-else) placed inside the body of another if or else. Nesting allows the programme to refine a decision after an initial test has narrowed the possibilities.

Always use braces and consistent indentation when nesting, so that the reader can immediately see which else pairs with which if.

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

#include <stdio.h>

int main(void)
{
int age;

printf("Enter your age: ");
scanf("%d", &age);

if (age >= 18) {
if (age >= 65) {
printf("You are a senior citizen.\n");
} else {
printf("You are an adult.\n");
}
} else {
printf("You are a minor.\n");
}

return 0;
}

What you should see: You are a minor. for ages below 18, You are an adult. for ages 18–64, and You are a senior citizen. for ages 65 and above.

Takeaway: Nesting is powerful but can quickly become hard to read; keep nesting shallow and consider the if-else ladder as an alternative when testing a single variable against several ranges.

4.7 The if-else Ladder

An if-else ladder is a chain of else if clauses that test conditions top-to-bottom. As soon as one condition evaluates to true, its block executes and the rest of the ladder is skipped. If no condition is true, the final else block (if present) is executed.

if (condition1) {
/* ... */
} else if (condition2) {
/* ... */
} else if (condition3) {
/* ... */
} else {
/* default action */
}

The ladder is logically equivalent to a set of nested if-else statements but is visually flatter, making it easier to read.

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

#include <stdio.h>

int main(void)
{
int choice;
printf("Enter a number (1, 2, or 3): ");
scanf("%d", &choice);

if (choice == 1) {
printf("You have created the world.\n");
} else if (choice == 2) {
printf("You are maintaining the world.\n");
} else if (choice == 3) {
printf("You are about to destroy the world.\n");
} else {
printf("Invalid choice. Please enter 1, 2, or 3.\n");
}

return 0;
}

What you should see: The message corresponding to the entered number, or the invalid-choice message.

Takeaway: The if-else ladder is the standard idiom for multi-branch selection on ranges or complex conditions.

4.8 Logical Operators

Logical operators combine or negate Boolean conditions to form compound expressions. C provides three:

OperatorNameArityBehaviour
&&ANDbinaryTrue only if both operands are true
||ORbinaryTrue if at least one operand is true
!NOTunaryInverts the truth value

In C, any non-zero value is considered true, and zero is false. The result of a logical operator is always 0 or 1.

Short-circuit evaluation is a critical property:

  • For &&: if the left operand evaluates to false, the right operand is not evaluated, because the overall result is already known to be false.
  • For ||: if the left operand evaluates to true, the right operand is not evaluated, because the overall result is already known to be true.

This is not merely an optimisation; it has observable side-effects when the right operand contains function calls or increments.

Precedence among logical operators: ! (highest) > && > || (lowest). All three rank below relational operators and above assignment.

Truth tables (using 0 and 1):

&& | 0 | 1 || | 0 | 1 ! | 0 | 1
----+---+--- ----+---+--- ---+---+---
0 | 0 | 0 0 | 0 | 1 | 1 | 0
1 | 0 | 1 1 | 1 | 1

Takeaway: Logical operators turn simple relational tests into arbitrarily complex decision criteria.

4.9 Logical AND (&&)

The && operator requires both operands to be true for the result to be true. A common use is testing whether a value falls within a range.

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

#include <stdio.h>

int main(void)
{
int age;
printf("Enter your age: ");
scanf("%d", &age);

if (age >= 13 && age < 20) {
printf("You are a teenager.\n");
} else {
printf("You are not a teenager.\n");
}

return 0;
}

What you should see: You are a teenager. for any age in [13, 19]; otherwise the alternative message.

Takeaway: && is the natural way to express "value is between A and B".

4.10 Logical OR (||)

The || operator requires at least one operand to be true. It is commonly used to test whether a value matches any member of a set.

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

#include <stdio.h>

int main(void)
{
char ch;
printf("Enter a character: ");
scanf("%c", &ch);

if (ch == 'a' || ch == 'e' || ch == 'i' ||
ch == 'o' || ch == 'u' ||
ch == 'A' || ch == 'E' || ch == 'I' ||
ch == 'O' || ch == 'U') {
printf("%c is a vowel.\n", ch);
} else {
printf("%c is not a vowel.\n", ch);
}

return 0;
}

What you should see: A message indicating whether the entered character is a vowel.

Takeaway: || lets you test membership in a set of discrete values.

4.11 Logical NOT (!)

The ! operator inverts a truth value: true becomes false and vice versa. Because C treats zero as false and any non-zero value as true, ! applied to a non-zero value yields 0, and ! applied to 0 yields 1.

A practical idiom: number % 2 yields 0 (false) for even numbers and a non-zero value (true) for odd numbers. Therefore !(number % 2) is true precisely when number is even.

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

#include <stdio.h>

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

if (!(number % 2)) {
printf("The number is even.\n");
} else {
printf("The number is odd.\n");
}

return 0;
}

What you should see: The number is even. or The number is odd. as appropriate.

Takeaway: ! is useful for negating a condition that is easier to express in the positive.

4.12 The switch Statement

A switch statement provides multi-way branching based on the value of an integer or character expression. It compares the expression against a list of case labels; when a match is found, execution begins at that label and continues until a break statement (or the end of the switch) is reached.

switch (expression)
{
case value1:
/* statements */
break;
case value2:
/* statements */
break;
/* ... */
default:
/* executed if no case matches */
}

Key points:

  • Each case label must be an integer constant expression (a literal or const integer, not a variable).
  • The default label is optional but strongly recommended; it handles unexpected values.
  • The break statement transfers control out of the switch. Without it, execution falls through into the next case.

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

#include <stdio.h>

int main(void)
{
int choice;
printf("Enter a number (1, 2, or 3): ");
scanf("%d", &choice);

switch (choice) {
case 1:
printf("You have created the world.\n");
break;
case 2:
printf("You are maintaining the world.\n");
break;
case 3:
printf("You are about to destroy the world.\n");
break;
default:
printf("Invalid choice. Please enter 1, 2, or 3.\n");
}

return 0;
}

What you should see: The message corresponding to the entered number.

Takeaway: Prefer switch over an if-else ladder when branching on a single discrete-valued variable; it expresses intent more clearly.

4.13 Fall-Through in switch

When a break is omitted at the end of a case, execution falls through into the subsequent case. This is the default behaviour in C and can be used intentionally, but it is also a frequent source of bugs.

Intentional fall-through example — a countdown:

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

#include <stdio.h>

int main(void)
{
int number;
printf("Enter a number between 1 and 3: ");
scanf("%d", &number);

printf("Countdown starting from %d:\n", number);
switch (number) {
case 3:
printf("3\n");
/* fall through */
case 2:
printf("2\n");
/* fall through */
case 1:
printf("1\n");
break;
default:
printf("Number out of range.\n");
}

return 0;
}

What you should see: If the user enters 3, the output is 3, 2, 1 in sequence. If the user enters 2, the output is 2, 1.

Takeaway: Intentional fall-through should be marked with a comment (/* fall through */) so that readers know the omission of break is deliberate.

5. Worked Examples

Example 1: Leap Year Checker

Problem: Write a programme that reads a year from the user and determines whether it is a leap year. A year is a leap year if it is divisible by 4, but not by 100, unless it is also divisible by 400.

Steps:

  1. Read the year from standard input.
  2. Apply the leap-year rule using logical operators: (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0).
  3. Print the result.

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

#include <stdio.h>

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

if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) {
printf("%d is a leap year.\n", year);
} else {
printf("%d is not a leap year.\n", year);
}

return 0;
}

Output:

Enter a year: 2000
2000 is a leap year.
Enter a year: 1900
1900 is not a leap year.

Why it works: The parenthesised sub-expressions ensure correct evaluation order. The first clause (% 4 == 0 && % 100 != 0) handles ordinary leap years. The second clause (% 400 == 0) handles the century exception. The || means that satisfying either clause is sufficient.

Example 2: Grading System

Problem: Read a mark in the range 0–100 and assign a letter grade: A (90–100), B (80–89), C (70–79), D (60–69), F (below 60). The programme should also reject marks outside 0–100.

Steps:

  1. Read the mark.
  2. Validate that the mark lies within [0, 100].
  3. Use an if-else ladder to determine the grade.
  4. Print the result.

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

#include <stdio.h>

int main(void)
{
int mark;
printf("Enter your mark: ");
scanf("%d", &mark);

if (mark < 0 || mark > 100) {
printf("Invalid mark.\n");
} else if (mark >= 90) {
printf("Your grade is A.\n");
} else if (mark >= 80) {
printf("Your grade is B.\n");
} else if (mark >= 70) {
printf("Your grade is C.\n");
} else if (mark >= 60) {
printf("Your grade is D.\n");
} else {
printf("Your grade is F.\n");
}

return 0;
}

Output:

Enter your mark: 85
Your grade is B.

Why it works: Because the ladder is evaluated top-to-bottom and each else if is reached only when all previous conditions were false, the test mark >= 80 implicitly means mark >= 80 && mark < 90. This eliminates redundant upper-bound checks.

Example 3: Days in a Month

Problem: Read a month (1–12) and a year, then print the number of days in that month. February has 28 days normally and 29 in a leap year.

Steps:

  1. Read the month and year.
  2. Use a switch statement to determine the day count, with fall-through for months that share the same count.
  3. For February, apply the leap-year rule.
  4. Print the result.

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

#include <stdio.h>

int main(void)
{
int month, year, days;

printf("Enter month (1-12): ");
scanf("%d", &month);
printf("Enter year: ");
scanf("%d", &year);

switch (month) {
case 1: case 3: case 5: case 7:
case 8: case 10: case 12:
days = 31;
break;
case 4: case 6: case 9: case 11:
days = 30;
break;
case 2:
if ((year % 4 == 0 && year % 100 != 0) ||
(year % 400 == 0)) {
days = 29;
} else {
days = 28;
}
break;
default:
printf("Invalid month.\n");
return 1;
}

printf("That month in %d has %d days.\n", year, days);
return 0;
}

Output:

Enter month (1-12): 2
Enter year: 2000
That month in 2000 has 29 days.

Why it works: The switch uses intentional fall-through to group months with the same day count (e.g., all 31-day months share one block). February is handled separately with an embedded if for the leap-year test.

6. Common Mistakes

  1. Using = instead of == in a condition: if (x = 5) assigns 5 to x and always evaluates to true (since 5 is non-zero). The correct test is if (x == 5). A defensive technique is to write if (5 == x), so that an accidental = causes a compiler error (you cannot assign to a literal).

  2. Omitting braces on if/else blocks: Without braces, only the first statement after if is controlled by the condition. Adding a second statement later will silently break the logic. Always use braces, even for single-statement bodies.

  3. Forgetting break in switch cases: Without break, execution falls through into the next case. Unless fall-through is intentional and documented, this produces incorrect results. Always include break at the end of every case and add a comment when fall-through is deliberate.

  4. Incorrect range tests with &&: Writing 10 < x < 100 does not test whether x is between 10 and 100. C parses this as (10 < x) < 100, which first produces 0 or 1, then compares that to 100 — always yielding true. The correct form is x > 10 && x < 100.

  5. Misunderstanding operator precedence: For example, !x == 0 is parsed as (!x) == 0 (because ! has higher precedence than ==), not !(x == 0). When combining logical and relational operators, use explicit parentheses to convey intent.

7. Practice

7.1 Basic

  • P1 — Zero Checker: Write a programme that prompts the user to input an integer and uses an if-else to check whether the number is zero. Print an appropriate message. Input/Output: Enter an integer: -5-5 is not zero. Acceptance: Correct output for zero and non-zero inputs.

  • P2 — Equality Checker: Read two integers and use == to check if they are equal. Print whether they are equal or not. Input/Output: Enter first number: 10 / Enter second number: 10The numbers are equal. Acceptance: Correct for equal and unequal pairs.

7.2 Required

  • P3 — Vowel Checker: Prompt the user to enter a character. Use || to determine whether it is a vowel (both uppercase and lowercase). Print the result. Input/Output: Enter a character: ee is a vowel. Acceptance: Handles all ten vowel characters and reports consonants correctly.

  • P4 — Range Checker (Nested if): Read an integer and use nested if statements to check whether it falls within 10 to 100 (inclusive). If out of range, distinguish whether the number is less than 10 or greater than 100. Input/Output: Enter a number: 5050 is within the range 10 to 100.; Enter a number: 101101 is greater than 100. Acceptance: Three distinct messages for below-range, in-range, and above-range.

  • P5 — All Positive Checker: Read three integers and use && to check whether all three are positive. Print an appropriate message. Input/Output: Enter three numbers: 5 8 12All numbers are positive. Acceptance: Correct for mixed positive/non-positive inputs.

  • P6 — Day of the Week: Read an integer 1–7 and use a switch to print the corresponding day name (1 = Monday, …, 7 = Sunday). Print an error for out-of-range input. Input/Output: Enter a number (1-7): 3The day is Wednesday. Challenge: Investigate how an enum is defined in C and rewrite the solution using one. Acceptance: All seven days plus the error case.

  • P7 — Divisibility Checker: Read an integer and report whether it is divisible by 2, by 3, and by 6. Print a separate message for each check. Input/Output: Enter a number: 1818 is divisible by 2. / 18 is divisible by 3. / 18 is divisible by 6. Acceptance: Correct for numbers divisible by some but not all of 2, 3, 6.

7.3 Optional

  • P8 — Days in a Month (Full): Read a month (1–12) and a year. Use a switch statement (with fall-through for months sharing the same count) and the leap-year rule to print the number of days in that month. Input/Output: Enter month (1-12): 2 / Enter year: 2000February 2000 has 29 days. Acceptance: Correct for all twelve months, including February in both leap and non-leap years.

  • P9 — Expression Evaluator: Given int a = 5, b = 7, c = 17, evaluate each of the following by hand first, then write a programme to verify: c / b == 2, c % b <= a % b, b + c / a != c - a, (b < c) && (c == 7), (c + 1 - b == 0) || (b == 5). Acceptance: Programme output matches hand-calculated results.

8. Mini Task

Goal: Build a simple command-line calculator that reads two integers and an operator (+, -, *, /, %), performs the requested operation, and prints the result. Use a switch on the operator character. Handle division by zero and invalid operators gracefully.

Deliverables:

  • README.md: Explain how to compile, run, and test the calculator, including edge cases.
  • src/calculator.c: The source file.
  • report.md: A brief summary (200–300 words) describing how you structured the switch, what edge cases you handled, and any difficulties you encountered.

9. Further Reading

  1. Deitel & Deitel, C How to Program (9th ed.): Chapter 3 — Structured Program Development in C.
  2. King, C Programming: A Modern Approach (2nd ed.): Chapter 5 — Selection Statements.
  3. ISO/IEC 9899:2018 (C18 Standard): §6.8.4 — Selection statements; §6.5.8–6.5.9 — Relational and equality operators; §6.5.13–6.5.14 — Logical AND / OR operators.

10. Appendix

A. Complete Operator Precedence Table (This Chapter)

LevelOperatorsAssociativityCategory
1()L→RGrouping
2+ - ! (type) sizeof (unary)R→LUnary
3* / %L→RMultiplicative
4+ - (binary)L→RAdditive
5< <= > >=L→RRelational
6== !=L→REquality
7&&L→RLogical AND
8||L→RLogical OR
9=R→LAssignment

B. Expression Evaluation Exercise (Solutions)

Given int a = 1, b = 0, c = 7:

ExpressionNumeric ValueTrue/False
a1T
b0F
c7T
a + b1T
a && b0F
a || b1T
!c0F
!!c1T
a && !b1T
a < b && b < c0F
a > b && b < c1T
a >= b || b > c1T