Operators

Operators are the symbols that do the actual work on your variables — arithmetic, comparisons, and the logic that decides what a program does next. Most of them behave exactly the way you'd expect from arithmetic class; the one that trips people up is division, because C++ treats it differently depending on the types involved.

Arithmetic and the integer division trap

The five arithmetic operators are +, -, *, /, and % (remainder). When both operands of / are integers, C++ performs integer division — it throws away any fractional part rather than rounding:

</> arithmetic.cpp
#include <iostream>

int main() {
    int a = 17, b = 5;
    std::cout << "a + b = " << a + b << std::endl;
    std::cout << "a - b = " << a - b << std::endl;
    std::cout << "a * b = " << a * b << std::endl;
    std::cout << "a / b = " << a / b << std::endl;
    std::cout << "a % b = " << a % b << std::endl;
    return 0;
}
Terminal output
a + b = 22
a - b = 12
a * b = 85
a / b = 3
a % b = 2

17 divided by 5 is 3.4, but a / b printed 3 — the compiler saw two int operands and produced an int result, dropping everything after the decimal point rather than rounding to the nearest whole number. a % b gives you back that dropped remainder: 5 goes into 17 three times with 2 left over. If you actually want the fractional answer, at least one operand needs to be a floating-point type — you can force that with static_cast:

</> precise-division.cpp
#include <iostream>

int main() {
    int a = 17, b = 5;
    double result = static_cast<double>(a) / b;
    std::cout << "Precise division: " << result << std::endl;
    return 0;
}
Terminal output
Precise division: 3.4

static_cast<double>(a) converts just a to a double before the division happens; because one operand is now a double, C++ promotes b to a double too and performs floating-point division on both. This is one of the most common real bugs in C++ code — a percentage or an average silently truncated to a whole number because both sides of a division happened to be integers.

Increment, decrement, and compound assignment

Rather than writing score = score + 5, C++ lets you write score += 5; every arithmetic operator has a compound form (-=, *=, /=, %=). For adding or subtracting exactly one, there's a further shorthand: ++ and --. Where they get interesting is that each comes in two flavors — count++ ("post-increment") returns the value before incrementing, while ++count ("pre-increment") increments first and returns the new value:

</> increment.cpp
#include <iostream>

int main() {
    int score = 10;
    score += 5;
    score *= 2;
    std::cout << "Score: " << score << std::endl;

    int count = 0;
    std::cout << "count++ gives " << count++ << std::endl;
    std::cout << "now count is " << count << std::endl;
    std::cout << "++count gives " << ++count << std::endl;
    return 0;
}
Terminal output
Score: 30
count++ gives 0
now count is 1
++count gives 2

In a bare statement like count++; on its own line, the two forms behave identically — the difference only shows up when you use the expression's result directly, as in the middle of a cout chain or a loop condition. In C-style for loops you'll see i++ used out of habit far more than the distinction is actually needed.

Relational and logical operators

Comparisons (==, !=, <, >, <=, >=) always produce a bool. You combine them with && (and), || (or), and ! (not) to build up more complex conditions:

</> car-rental.cpp
#include <iostream>

int main() {
    int age = 20;
    bool hasLicense = true;

    bool canRentNoSurcharge = (age >= 21) && hasLicense;
    bool canRentWithSurcharge = (age >= 18) && hasLicense;

    std::cout << "Can rent without surcharge: " << canRentNoSurcharge << std::endl;
    std::cout << "Can rent with surcharge: " << canRentWithSurcharge << std::endl;
    return 0;
}
Terminal output
Can rent without surcharge: 0
Can rent with surcharge: 1

At 20, the renter fails the age >= 21 check, so canRentNoSurcharge is false (printed as 0); they do clear the lower age >= 18 bar, so canRentWithSurcharge comes out true. && and || also short-circuit — if the left side of && is already false, C++ never bothers evaluating the right side, since the answer can't change. That's not just an optimization; it's safe to rely on, and it's why you'll often see a null-check written as ptr != nullptr && ptr->isValid() — the second check only runs once the first has confirmed it's safe to.

Note: don't confuse = (assignment) with == (comparison). if (score = 100) compiles — it assigns 100 to score and then evaluates the assignment's result, which is truthy — but it almost certainly isn't what you meant. Most compilers will warn about this if you enable warnings with -Wall, which is worth turning on for exactly this reason.