C / Basic: Read and Construct Programs / Lesson 4
Operators and Control Flow
Make arithmetic and decisions explicit, and use loop invariants to justify every iteration and boundary.
On this page
Lesson goals
- Trace precedence, short-circuiting, and branches.
- State a loop invariant and termination argument.
- Separate independent side effects into readable statements.
Values and changes are different
An expression can compute a value and can also change an object. Arithmetic precedence determines grouping, but does not generally determine the order in which operands are evaluated. Parentheses make grouping clear; they do not turn every operand evaluation into a left-to-right sequence. When several updates matter, write separate full statements.
For example, total = price * quantity + fee groups the multiplication first. It is still the programmerโs responsibility to prove the intermediate product and sum fit their types. Prefer names and intermediate variables that expose the domain calculation rather than packing input, increments, arithmetic, and output into one expression.
Conditions encode a decision policy
Zero is false and a nonzero scalar value is true in a controlling expression. Relational operators produce int zero or one. Logical && and
short-circuit: the right operand is evaluated only when needed. This permits a guard such as divisor != 0 && numerator / divisor > 2, although the division still needs other range checks for general signed operands.
A chain of else-if branches expresses an ordered classification. Put the most specific applicable rule before a broader rule when they overlap. In a switch, case labels are integral constant expressions, and execution can fall through unless control leaves the branch. Use explicit break or a clear documented intentional fallthrough.
Work through a classification
The score example deliberately checks the highest threshold first. With score 74, the first comparison fails and the second succeeds, so exactly one label is printed. This is a classification contract, not an input validator: a real application must reject scores outside its allowed domain before reaching these branches.
Test points immediately below, at, and above every threshold. For the first boundary, 84, 85, and 86 distinguish a >= rule from a > rule. Choosing only typical middle values can conceal a boundary mistake. A branch table stating interval and expected label often makes the intended behavior easier to review.
#include <stdio.h>
int main(void)
{
int score = 74;
if (score >= 85) puts("high");
else if (score >= 60) puts("pass");
else puts("retry");
return 0;
}
pass
74 is below 85 and at least 60, so the middle branch runs.
Explain a loop before running it
A for loop groups initialization, condition, and update. Its body runs only while the condition is true. A useful invariant describes the state at the start of each iteration and must hold initially and after every update. For a sum of the first i values, the invariant states exactly which values have already contributed.
The example starts with no values accumulated. Before iteration i, total equals the sum from one through i minus one; adding i preserves the claim for the next iteration. The finite bound five and increasing i prove termination. All intermediate values fit int, so this explanation needs neither wraparound nor a machine-specific integer width.
#include <stdio.h>
int main(void)
{
int total = 0;
for (int i = 1; i <= 5; ++i) {
total += i;
}
printf("total=%d\n", total);
return 0;
}
total=15
The five updates add 1, 2, 3, 4, and 5, producing 15.
Review exits and empty cases
A while loop can execute zero times; do-while executes its body at least once. break leaves the nearest loop or switch, while continue skips to the next iteration step. In a for loop, that includes the update expression. Check these paths when a loop maintains counters, owns resources, or consumes input.
Trace empty input, one item, the exact upper boundary, and an early failure. Avoid unsigned countdown conditions such as i >= 0, which remain true for every unsigned value. A forward half-open interval, beginning at zero and ending before count, usually makes array loops and their empty case easier to reason about.
Common errors and fixes
A boundary value enters the wrong branch.
- Why it happens
- A threshold uses > instead of >=, or broader rules appear first.
- How to fix it
- Write disjoint intervals and order the comparisons accordingly.
- Prevent it next time
- Test the three neighboring values around each threshold.
An unsigned countdown never ends.
- Why it happens
- The condition i >= 0 is always true for unsigned i.
- How to fix it
- Use a forward interval or a carefully guarded reverse loop.
- Prevent it next time
- Prove termination in the actual counter type.
Adding parentheses fails to stabilize a complex update.
- Why it happens
- Grouping was confused with evaluation sequencing.
- How to fix it
- Split dependent modifications into separate statements.
- Prevent it next time
- Allow at most one essential state change per expression during initial design.
Check your understanding
1. Change the summation to include one through six, then justify the result and termination.
Show worked solution
Use i <= 6. Before each iteration, total is the sum of all previous positive integers; the final update adds six to fifteen, producing twenty-one. After that update i becomes seven, the condition fails, and the loop ends. Check the arithmetic range again if the upper bound becomes externally supplied rather than the small constant six.
2. Classify scores 59, 60, 84, and 85, and propose one additional validation rule.
Show worked solution
The labels are retry, pass, pass, and high. The two exact thresholds belong to the higher category because the tests use >=. Before classification, reject scores less than zero or greater than one hundred if those are the application limits. Keeping rejection separate prevents a score such as 900 from being mistaken for an excellent valid result.
Key takeaways
- Precedence groups operations; sequencing governs their order.
- Use explicit domains and boundary tests for decisions.
- An invariant and a decreasing amount of remaining work explain a loop.