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:
- Use relational operators (
==,!=,>,<,>=,<=) to compare values and predict the integer result (0 or 1) of each comparison. - Construct
if,if-else, nestedif, andif-elseladder structures to express single-branch, two-branch, and multi-branch logic. - Apply logical operators (
&&,||,!) to form compound conditions, and evaluate such expressions with correct precedence. - Write
switchstatements for multi-way branching on integer or character values, including intentional fall-through. - Identify and avoid common pitfalls such as confusing
=with==, omitting braces, and forgettingbreakinswitchcases.
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 statement | if 语句 | Executes a block only when the condition is true. |
if-else statement | if-else 语句 | Two-branch selection: one block for true, another for false. |
if-else ladder | if-else 阶梯 | Chain of else if clauses testing conditions top-to-bottom. |
nested if | 嵌套 if | An if (or if-else) placed inside another if or else block. |
switch statement | switch 语句 | 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:
- Sequence — statements execute in the order in which they are written.
- Selection — the programme chooses among alternative courses of action.
- 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:
| Operator | Meaning |
|---|---|
== | 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:
| Precedence | Operators | Associativity |
|---|---|---|
| 1 (highest) | () | left-to-right |
| 2 | unary +, -, !, (type), sizeof | right-to-left |
| 3 | *, /, % | left-to-right |
| 4 | binary +, - | 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:
| Expression | Step-by-step evaluation | Result |
|---|---|---|
a + b + c | 1 + 4 + 3 | 8 |
a + b * c | 1 + (4 * 3) = 1 + 12 | 13 |
b + 1 > 2 | (4 + 1) > 2 = 5 > 2 | 1 (true) |
b + 1 > 2 == b > 2 | (5 > 2) == (4 > 2) = 1 == 1 | 1 (true) |
b + 1 > 2 != c < 4 | (5 > 2) != (3 < 4) = 1 != 1 | 0 (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:
| Operator | Name | Arity | Behaviour |
|---|---|---|---|
&& | AND | binary | True only if both operands are true |
|| | OR | binary | True if at least one operand is true |
! | NOT | unary | Inverts 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
caselabel must be an integer constant expression (a literal orconstinteger, not a variable). - The
defaultlabel is optional but strongly recommended; it handles unexpected values. - The
breakstatement transfers control out of theswitch. 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:
- Read the year from standard input.
- Apply the leap-year rule using logical operators:
(year % 4 == 0 && year % 100 != 0) || (year % 400 == 0). - 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:
- Read the mark.
- Validate that the mark lies within [0, 100].
- Use an
if-elseladder to determine the grade. - 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:
- Read the month and year.
- Use a
switchstatement to determine the day count, with fall-through for months that share the same count. - For February, apply the leap-year rule.
- 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
-
Using
=instead of==in a condition:if (x = 5)assigns 5 toxand always evaluates to true (since 5 is non-zero). The correct test isif (x == 5). A defensive technique is to writeif (5 == x), so that an accidental=causes a compiler error (you cannot assign to a literal). -
Omitting braces on
if/elseblocks: Without braces, only the first statement afterifis controlled by the condition. Adding a second statement later will silently break the logic. Always use braces, even for single-statement bodies. -
Forgetting
breakinswitchcases: Withoutbreak, execution falls through into the next case. Unless fall-through is intentional and documented, this produces incorrect results. Always includebreakat the end of every case and add a comment when fall-through is deliberate. -
Incorrect range tests with
&&: Writing10 < x < 100does not test whetherxis 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 isx > 10 && x < 100. -
Misunderstanding operator precedence: For example,
!x == 0is 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-elseto 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: 10→The 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: e→e is a vowel.Acceptance: Handles all ten vowel characters and reports consonants correctly. -
P4 — Range Checker (Nested
if): Read an integer and use nestedifstatements 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: 50→50 is within the range 10 to 100.;Enter a number: 101→101 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 12→All numbers are positive.Acceptance: Correct for mixed positive/non-positive inputs. -
P6 — Day of the Week: Read an integer 1–7 and use a
switchto print the corresponding day name (1 = Monday, …, 7 = Sunday). Print an error for out-of-range input. Input/Output:Enter a number (1-7): 3→The day is Wednesday.Challenge: Investigate how anenumis 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: 18→18 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
switchstatement (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: 2000→February 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 theswitch, what edge cases you handled, and any difficulties you encountered.
9. Further Reading
- Deitel & Deitel, C How to Program (9th ed.): Chapter 3 — Structured Program Development in C.
- King, C Programming: A Modern Approach (2nd ed.): Chapter 5 — Selection Statements.
- 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)
| Level | Operators | Associativity | Category |
|---|---|---|---|
| 1 | () | L→R | Grouping |
| 2 | + - ! (type) sizeof (unary) | R→L | Unary |
| 3 | * / % | L→R | Multiplicative |
| 4 | + - (binary) | L→R | Additive |
| 5 | < <= > >= | L→R | Relational |
| 6 | == != | L→R | Equality |
| 7 | && | L→R | Logical AND |
| 8 | || | L→R | Logical OR |
| 9 | = | R→L | Assignment |
B. Expression Evaluation Exercise (Solutions)
Given int a = 1, b = 0, c = 7:
| Expression | Numeric Value | True/False |
|---|---|---|
a | 1 | T |
b | 0 | F |
c | 7 | T |
a + b | 1 | T |
a && b | 0 | F |
a || b | 1 | T |
!c | 0 | F |
!!c | 1 | T |
a && !b | 1 | T |
a < b && b < c | 0 | F |
a > b && b < c | 1 | T |
a >= b || b > c | 1 | T |