Skip to content

5. Booleans & Conditionals

What & why

Programs make decisions: if this, do that. That requires expressions that evaluate to true or false, and statements that branch on them.

Comparison operators

a == b     // equal
a != b     // not equal
a < b      // less than
a <= b     // less than or equal
a > b
a >= b

They produce a bool. std::string compares alphabetically (lexicographically):

std::string{"apple"} < std::string{"banana"}   // true
3 == 3.0                                       // true (3 is converted to double)

Printing a bool with std::cout shows 1 or 0. Use std::cout << std::boolalpha; once to get true/false.

Logical operators: &&, ||, !

age >= 18 && has_ticket
is_weekend || is_holiday
!is_logged_in
  • && — true only if both sides are true
  • || — true if either side is true
  • ! — flips the value

(and, or, not are valid alternative spellings, but rarely used.)

Short-circuiting

&& and || stop as soon as the answer is known. That makes guards safe:

if (!name.empty() && name[0] == 'A') { ... }   // name[0] only runs if not empty
if (ptr != nullptr && ptr->ready) { ... }       // no null dereference

No chained comparisons

This compiles, but it is wrong:

if (0 <= score <= 100)   // parsed as (0 <= score) <= 100 → bool (0 or 1) <= 100 → always true

Write both halves:

if (0 <= score && score <= 100)

if / else if / else

int temp = 22;

if (temp < 0) {
    std::cout << "freezing\n";
} else if (temp < 15) {
    std::cout << "cold\n";
} else if (temp < 25) {
    std::cout << "comfortable\n";
} else {
    std::cout << "hot\n";
}
  • The condition goes in parentheses.
  • Only the first matching branch runs.
  • Braces are optional for a single statement — always use them anyway. Missing braces cause real bugs:
if (logged_in)
    load_profile();
    show_dashboard();   // ALWAYS runs — indentation means nothing to the compiler

Truthiness

Numbers and pointers convert to bool: zero/null is false, anything else is true.

int count = 0;
if (count) { ... }         // false

std::string s;
if (s) { ... }             // compile ERROR: std::string doesn't convert to bool
if (!s.empty()) { ... }    // correct

Be explicit when it helps: if (count != 0) reads better than if (count) for numbers.

if with an initializer (C++17)

Limit a variable's scope to the if:

if (auto pos = text.find(':'); pos != std::string::npos) {
    std::cout << "key: " << text.substr(0, pos) << '\n';
}
// pos doesn't exist here

Guard clauses

Return early to avoid deep nesting:

std::string process(int quantity, double price) {
    if (quantity <= 0) {
        return "invalid quantity";
    }
    if (price < 0) {
        return "invalid price";
    }
    // main logic here, not indented 3 levels deep
    return "processed";
}

The conditional (ternary) operator

std::string label = (n % 2 == 0) ? "even" : "odd";
double discount = is_member ? 0.1 : 0.0;

Use it for simple value selection, not for side effects.

switch

For comparing one integer-like value (int, char, enum) against constants:

char op = '+';
int a = 6, b = 3;

switch (op) {
    case '+':
        std::cout << a + b << '\n';
        break;
    case '-':
        std::cout << a - b << '\n';
        break;
    case '*':
    case 'x':                       // two labels, same code
        std::cout << a * b << '\n';
        break;
    default:
        std::cout << "unknown operator\n";
}
  • Forget break and execution "falls through" into the next case. If you mean it, mark it with [[fallthrough]];.
  • switch doesn't work on std::string. Use if/else if for strings.
  • Declaring a variable inside a case needs its own braces: case 1: { int x = 5; ... break; }.

Common mistakes

  • = instead of ==: if (x = 5) assigns 5 and is always true. -Wall warns you — read warnings!
  • 0 <= x <= 100 — compiles, always true. Use &&.
  • Missing braces on multi-line bodies.
  • Missing break in a switch.
  • if (a || b == c) means a || (b == c). You probably want a == c || b == c.
  • A stray semicolon: if (ok); { ... } — the ; is the entire if-body; the block always runs.
  • Comparing doubles with == (see lesson 3).

Exercises

  1. Read a year and print whether it's a leap year. (Divisible by 4, except centuries, unless divisible by 400.)
  2. Read a numeric grade 0–100 and print a letter: A (≥90), B (≥80), C (≥70), D (≥60), F otherwise. Reject values outside 0–100.
  3. Hardcode a username and password, read both from the user, and print "Access granted" only if both match.
  4. Read three side lengths and print whether they form a triangle, and if so whether it's equilateral, isosceles, or scalene.
  5. Rewrite with guard clauses:
    bool can_vote(int age, bool citizen, bool registered) {
        if (age >= 18) {
            if (citizen) {
                if (registered) {
                    return true;
                }
            }
        }
        return false;
    }
    
  6. Build a tiny calculator: read number op number (e.g. 3 + 4) with std::cin >> a >> op >> b and use a switch on op. Handle division by zero.

Recap

  • Comparisons and && / || / ! produce bool; && and || short-circuit.
  • No chained comparisons — 0 <= x && x <= 100.
  • if / else if / else — always use braces.
  • if (init; cond) keeps helper variables scoped.
  • cond ? a : b for simple choices; switch for integer/char/enum constants — don't forget break.

Next: Loops →