Lesson 3 — Operations

~90–120 min · ← Lesson 2 · Lesson 4 — Conversion →

Six operator families + the rule that governs them all. Same beats as always: explanation → syntax → example → explained → practice.


3.1 Arithmetic operators

Explanation. The five workhorses: + − * / % (modulo = remainder). The one that bites everyone: / depends on operand types — two ints → integer division (fraction truncated); either operand double → decimal division. % is the remainder of integer division — the operator of even/odd tests, digit extraction, and cyclic patterns.

Syntax / Example.

// arith.cpp
#include <iostream>

int main() {
    std::cout << 7 + 3   << "\n";   // 10
    std::cout << 7 / 2   << "\n";   // 3      integer division
    std::cout << 7.0 / 2 << "\n";   // 3.5    one double promotes the lot
    std::cout << 7 % 2   << "\n";   // 1      remainder
    std::cout << 7 % 2 == 0 << "\n";// ??     see Explained!
    return 0;
}

Explained. The last line prints 0, not false-ness you might expect — because % binds tighter than ==, it parses as (7 % 2) == 0 → 1 == 0 → 0. Accidentally correct here, but the pattern — mixing arithmetic and comparison without parentheses — is how precedence bugs are born (§3.6). Also meet the negative-remainder fact now: -7 % 2 is -1 in C++ (sign follows the dividend) — remember it for Unit 04’s even/odd checks. And x / 0? Integer division by zero is undefined behaviour; x / 0.0 yields infinity — neither is ever what you meant; validate divisors (Set D, Scenario 16 showed how).

Practice. Exercise 20 · predictions P11, P12.


3.2 Relational operators

Explanation. Six comparisons producing a bool: == equal, != not equal, < less, <= less-or-equal, > greater, >= greater-or-equal. The universal beginner bug: = (assignment) vs == (comparison) — one character, opposite universes.

Syntax / Example.

// relations.cpp
#include <iostream>

int main() {
    int marks = 50;
    std::cout << (marks >= 50) << "\n";   // 1  boundary is inclusive
    std::cout << (marks >  50) << "\n";   // 0  strict — different question!
    std::cout << (marks == 50) << "\n";   // 1  comparison
    std::cout << (marks =  0)  << "\n";   // 0  ASSIGNMENT! marks is now 0
    std::cout << marks        << "\n";    // 0  …destroyed by the line above
    return 0;
}

Explained. Two lessons hide here. (1) Boundaries: >= and > are different questions — “50 or more” must be >= (the Problem-Solving module’s boundary discipline, now in syntax). (2) The = vs == trap: marks = 0 stores 0 and the expression’s value is the stored value (0 — falsy). In an if this silently corrupts the variable and flips the logic; -Wall warns (suggest parentheses around assignment used as truth value) — another reason warnings are must-fix. Parentheses around every comparison inside output/conditions, per the example, also keep << from biting you (it outranks comparisons — see §3.6’s table).

Practice. Exercise 21 · debugging D5 (the one-character bug).


3.3 Logical operators

Explanation. Three operators combine booleans: && (AND — true only if both true), || (OR — true if either), ! (NOT — flips). Both && and || short-circuit: if the left side decides the answer, the right side is never evaluated at all.

Syntax / Example.

// logic.cpp
#include <iostream>

int main() {
    int age = 20;
    bool hasID = false;
    std::cout << ((age >= 18) && hasID)  << "\n";   // 0  both required
    std::cout << ((age >= 18) || hasID)  << "\n";   // 1  either suffices
    std::cout << (!hasID)                << "\n";   // 1  flip

    // short-circuit in action:
    int divisor = 0;
    bool safe = (divisor != 0) && (100 / divisor > 2);
    std::cout << safe << "\n";                      // 0 — and NO crash:
    return 0;
}

Explained. The last block is the professional pattern safe = (divisor != 0) && (…division…): because && checks left first and short-circuits, the dangerous division never runs when the divisor is 0. Put the cheap guard on the left, the expensive/risky check on the right — the ordering is the design. (Negations flip AND↔OR: “not (a and b)” = “not-a or not-b” — the Problem-Solving module’s Scenario 9/16/20 conditions, now with their C++ names — De Morgan’s laws, formally in Unit 04.)

Practice. Exercise 22 · prediction P13.


3.4 Increment / decrement

Explanation. ++ adds one, -- subtracts one — the loop-counter shorthand. Each has two positions: prefix (++x — then use) and postfix (x++ — then increment: use first, bump after). In a standalone statement the two are identical; inside a larger expression they differ — and that difference is a readability hazard the course rules accordingly: standalone statements only.

Syntax / Example.

// incdec.cpp
#include <iostream>

int main() {
    int count = 5;
    count++;                        // standalone: now 6 (position irrelevant)
    std::cout << count << "\n";     // 6

    int a = 5;
    int b = a++;                    // postfix: b gets 5, THEN a becomes 6
    std::cout << a << " " << b << "\n";   // 6 5

    int c = 5;
    int d = ++c;                    // prefix: c becomes 6, THEN d gets 6
    std::cout << c << " " << d << "\n";   // 6 6
    return 0;
}

Explained. Read a++ aloud as “a, then plus”: the old value is what the expression yields. ++a is “plus a”: bump first. The b/d pair is the whole lesson — memorise the two outputs (5 then 6) and you’ll never be surprised by a seeded bug again. The course style: count++; alone on its line, always.

Practice. Exercise 23 · prediction P14.


3.5 Compound assignment

Explanation. op= operators abbreviate “update in place”: x += 5 means x = x + 5; also -= *= /= %=. Same value, fewer repeated names — and it scales to code you haven’t written yet (Unit 06: total += mark inside a loop is the accumulator idiom).

Syntax / Example.

// compound.cpp
#include <iostream>

int main() {
    int total = 0;
    total += 20;        // total = total + 20  → 20
    total += 35;        // → 55
    total *= 2;         // → 110
    total -= 10;        // → 100
    total /= 3;         // integer division → 33 (fraction discarded!)
    std::cout << total << "\n";
    return 0;
}

Explained. Every line is “read, operate, store back” in one token — and the last line repeats Lesson 2’s warning in compound clothes: /= between ints is integer division (110/3 → 33). Compound operators bind at assignment precedence (bottom of §3.6’s table): x *= y + 1 means x = x * (y + 1) — the whole right side first.

Practice. Exercise 24 · challenge C7.


3.6 Operator precedence

Explanation. When operators mix, precedence decides who goes first; associativity decides left-to-right vs right-to-left among equals. The memory-saving core for this course (highest first):

Level Operators Notes
1 ++ -- (postfix) highest
2 ++ -- (prefix), !, unary -  
3 * / % before + and −
4 + -  
5 < <= > >=  
6 == !=  
7 && before ||
8 \|\|  
9 =, +=, -=, … assignment: right-to-left, lowest

Example.

// precedence.cpp
#include <iostream>

int main() {
    std::cout << 2 + 3 * 4        << "\n";  // 14   * before +
    std::cout << (2 + 3) * 4      << "\n";  // 20   parentheses win
    std::cout << 10 - 4 - 3       << "\n";  // 3    left-to-right
    std::cout << 2 + 3 < 5 + 1    << "\n";  // ??   (2+3)<(5+1) → 0
    std::cout << (2 + 3 < 5) + 1  << "\n";  // 1    bool promotes to int
    return 0;
}

Explained. Row 4 parses as (2+3) < (5+1) — arithmetic before comparison — giving 5 < 6 → 1… wait, 5 < 6 is true → 1; run it and check your trace. Row 5 shows comparison results participating in arithmetic (true → 1). The course’s standing rule beats memorising all 12 levels: parentheses for anything beyond * / % over + - — free correctness, free readability, zero cost. When in doubt, parenthesise; when not in doubt, parenthesise anyway.

Practice. predictions P11–P15 are precedence drills by design · debugging D6 (the classic average expression).


Check yourself (Lesson 3)

Next: Lesson 4 — Type Conversion & Common Mistakes: how values change types, and how they bite.

← Lesson 2 · Module home