Operators and expressions

C++ · Essentials · 10 min

Expressions are the bulk of any program, and a few C++ operator rules (precedence, signed/unsigned mixing) cause classic bugs.

The arithmetic operators are + - * / %, with compound forms like += and *=. % is the remainder; with negative operands division truncates toward zero, so -7 / 2 is -3 and -7 % 2 is -1.

++i increments and yields the new value; i++ yields the old value. On their own as statements they're equivalent; prefer ++i as a habit (for iterators it can be cheaper).

Comparisons (== != < <= > >=) yield bool. The logical operators &&, || and ! short-circuit: in count != 0 && total / count > 10 the division is never evaluated when count is 0. That is guaranteed and widely relied upon.

The bitwise operators & | ^ ~ << >> work on the bits of integers; use unsigned types for bit manipulation. Watch precedence: == binds tighter than &, so flags & mask == 0 means flags & (mask == 0). When in doubt, add parentheses.

The conditional operator cond ? a : b picks one of two values and is handy for initializing const variables.

Mixed-type expressions are converted to a common type. The nasty case is signed with unsigned: the signed value is converted to unsigned, so -1 < 0u is false because -1 becomes 4294967295. Compilers warn about this (-Wall enables -Wsign-compare); C++20 adds std::cmp_less and friends in <utility> that compare mixed integers correctly. This bites most often when comparing an int index with v.size().

Finally, = assigns and == compares. if (x = 5) compiles (with a warning) and is always true.

Example

#include <iostream>
#include <utility>

int main() {
    int a = 17, b = 5;
    std::cout << a / b << ' ' << a % b << '\n';     // 3 2
    std::cout << -7 / 2 << ' ' << -7 % 2 << '\n';   // -3 -1 (truncates toward zero)

    int i = 5;
    int x = i++;   // x = 5, then i = 6
    int y = ++i;   // i = 7, then y = 7
    std::cout << x << ' ' << y << '\n';             // 5 7

    int count = 0, total = 50;
    if (count != 0 && total / count > 10) { }       // short-circuit: no division by zero

    unsigned flags = 0b1010;
    bool bit1 = (flags & 0b0010) != 0;              // parentheses needed: != binds tighter
    std::cout << bit1 << ' ' << (flags >> 1) << '\n';   // 1 5

    std::cout << (-1 < 0u) << '\n';                 // 0 (!) -1 converts to a huge unsigned
    std::cout << std::cmp_less(-1, 0u) << '\n';     // 1, C++20 safe comparison

    int score = 72;
    const char* grade = score >= 60 ? "pass" : "fail";
    std::cout << grade << '\n';                     // pass
}

Remember

  • && and || short-circuit, which makes guards like p && p->ok safe
  • Comparing signed with unsigned converts the signed side: -1 < 0u is false
  • == binds tighter than &, |, ^: parenthesise bit tests

Watch out for

  • Comparing int i with v.size() when i can be negative — use std::ssize(v) or std::cmp_less.

Check yourself

  1. What does `std::cout << (5 & 4 == 4);` print?
  2. After `int i = 3; int j = i++ * 2;`, what are i and j?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: en.cppreference.com/w/cpp/language/expressions