C++ Operators

An operator in C++ is a symbol that tells the compiler to perform a specific mathematical, logical, or relational operation on one or more values, called operands. Operators are the building blocks of every expression you write, from simple arithmetic like a + b to complex conditions that control how your program branches and loops. Understanding exactly what each operator does, what type it produces, and in what order operators are evaluated is essential for writing correct C++ code and for avoiding a long list of classic beginner bugs.

Overview: How Operators Work

Every operator in C++ takes one, two, or three operands and combines them into a single resulting value. Operators are classified by how many operands they take:

  • Unary operators act on a single operand, such as -x (negation) or !flag (logical NOT).
  • Binary operators act on two operands, such as a + b or a && b. Most operators in C++ are binary.
  • Ternary operator acts on three operands: the conditional operator condition ? valueIfTrue : valueIfFalse is the only one in the language.

Under the hood, an operator is really just syntactic sugar for a computation the compiler translates into machine instructions (or, for user-defined types, into a function call to an overloaded operator function). For built-in types like int and double, arithmetic operators map almost directly to CPU instructions such as add, sub, imul, and idiv. The result type of an expression depends on the operand types: mixing an int and a double promotes the whole expression to double before the operation happens, which is why 5 / 2 gives 2 (integer division, the fractional part is discarded) while 5.0 / 2 gives 2.5.

C++ operators fall into several categories, summarized below.

Category Operators Purpose
Arithmetic + - * / % Perform numeric calculations
Relational == != > < >= <= Compare two values, produce a bool
Logical && || ! Combine or negate boolean expressions
Assignment = += -= *= /= %= Store a value into a variable
Increment/Decrement ++ -- Add or subtract 1 from a variable
Bitwise & | ^ ~ << >> Manipulate individual bits
Miscellaneous sizeof ?: , Query size, conditional expression, sequencing

Syntax

Most C++ operators are written between their operands (infix notation):

operand1 operator operand2;
  • operand1 / operand2 — the values being operated on (variables, literals, or expressions).
  • operator — the symbol specifying the operation, e.g. +, ==, &&.
  • The whole expression evaluates to a single value, which can be stored, printed, or used inside another expression.

Unary operators are written immediately before (or, for ++/--, optionally after) a single operand: -x, !done, x++, ++x.

Examples

Example 1: Arithmetic Operators

#include <iostream>
using namespace std;

int main() {
    int a = 17;
    int b = 5;

    cout << "a + b = " << (a + b) << endl;
    cout << "a - b = " << (a - b) << endl;
    cout << "a * b = " << (a * b) << endl;
    cout << "a / b = " << (a / b) << endl;
    cout << "a % b = " << (a % b) << endl;

    double x = 17.0;
    double y = 5.0;
    cout << "x / y = " << (x / y) << endl;

    return 0;
}

Output:

a + b = 22
a - b = 12
a * b = 85
a / b = 3
a % b = 2
x / y = 3.4

This example shows the five arithmetic operators. Notice that a / b with two int operands performs integer division, truncating the result toward zero (17 / 5 mathematically is 3.4, but the fractional part is dropped, giving 3). The modulus operator % returns the remainder of that division (17 = 5 × 3 + 2, so the remainder is 2). Modulus only works on integer types — you cannot apply % directly to double values. When both operands are double, division keeps the fractional part, as shown by x / y = 3.4.

Example 2: Relational and Logical Operators

#include <iostream>
using namespace std;

int main() {
    int age = 20;
    bool hasSubscription = true;
    bool isBanned = false;

    bool canWatch = (age >= 18) && hasSubscription && !isBanned;

    cout << "Age >= 18: " << (age >= 18) << endl;
    cout << "Has subscription: " << hasSubscription << endl;
    cout << "Is banned: " << isBanned << endl;
    cout << "Can watch: " << canWatch << endl;

    int score = 72;
    if (score >= 90) {
        cout << "Grade: A" << endl;
    } else if (score >= 70) {
        cout << "Grade: B" << endl;
    } else {
        cout << "Grade: C or below" << endl;
    }

    return 0;
}

Output:

Age >= 18: 1
Has subscription: 1
Is banned: 0
Can watch: 1
Grade: B

Relational operators (>=, ==, and so on) always produce a bool, which cout prints as 1 for true and 0 for false by default. Logical operators combine those boolean results: && (AND) requires every operand to be true, || (OR) requires at least one to be true, and ! (NOT) flips a boolean. The canWatch expression reads naturally: the age check AND the subscription check AND NOT banned. The chained if/else if shows relational operators driving control flow to pick a grade.

Example 3: Increment, Compound Assignment, and Short-Circuit Evaluation

#include <iostream>
using namespace std;

int main() {
    int i = 5;
    cout << "i++ gives: " << i++ << endl;
    cout << "after i++, i = " << i << endl;

    int j = 5;
    cout << "++j gives: " << ++j << endl;
    cout << "after ++j, j = " << j << endl;

    int total = 10;
    total += 5;
    total *= 2;
    total -= 3;
    total /= 3;
    cout << "total after compound ops = " << total << endl;

    int calls = 0;
    bool result = (calls++ > 0) && (calls++ > 0);
    cout << "calls after short-circuit AND: " << calls << endl;
    cout << "result: " << result << endl;

    return 0;
}

Output:

i++ gives: 5
after i++, i = 6
++j gives: 6
after ++j, j = 6
total after compound ops = 9
calls after short-circuit AND: 1
result: 0

i++ (post-increment) returns the original value of i and then increments it, so the printed value is 5 even though i becomes 6 immediately afterward. ++j (pre-increment) increments first and returns the new value, so it prints 6 right away. The compound assignment operators (+=, *=, -=, /=) apply an operation and store the result back into the same variable in one step: starting from 10, the chain produces 15 → 30 → 27 → 9. Finally, && demonstrates short-circuit evaluation: because calls++ > 0 evaluates to false the first time (post-increment returns 0, and 0 > 0 is false), C++ never evaluates the right-hand side at all — the second calls++ never runs, so calls ends at 1, not 2.

Under the Hood: Precedence and Associativity

When an expression contains multiple operators, the compiler must decide which one to apply first. This is governed by two rules: precedence (which operator binds tighter) and associativity (the order in which operators of equal precedence are applied — left-to-right or right-to-left). For example, in 2 + 3 * 4, multiplication has higher precedence than addition, so the compiler computes 3 * 4 first, giving 14, not 20. A simplified precedence table, from highest to lowest:

Precedence Operators Associativity
Highest () ++ -- (postfix) Left to right
High ++ -- (prefix), ! - (unary), sizeof Right to left
Medium-high * / % Left to right
Medium + - (binary) Left to right
Medium-low < <= > >= Left to right
Low == != Left to right
Lower && Left to right
Lowest (before assignment) || Left to right
Near bottom = += -= *= /= Right to left

Assignment is right-associative, which is why a = b = c; works: it is evaluated as a = (b = c);, assigning c to b first and then that result to a. When in doubt about precedence, use parentheses — they cost nothing at runtime and make intent explicit to future readers.

Common Mistakes

Mistake 1: Using = instead of == in a condition.

int x = 5;
if (x = 10) {
    cout << "This always runs, and x is now 10!";
}

This compiles because x = 10 is a valid expression that assigns 10 to x and evaluates to 10, which is truthy — so the branch always executes and silently overwrites x. The fix is to use the comparison operator:

int x = 5;
if (x == 10) {
    cout << "This only runs if x was already 10.";
}

Mistake 2: Expecting integer division to produce a fraction.

int total = 7;
int count = 2;
double average = total / count; // still integer division first!
cout << average; // prints 3, not 3.5

The division happens on two int operands and truncates to 3 before the result is converted to double for storage. Fix it by casting at least one operand to a floating-point type before the division occurs:

int total = 7;
int count = 2;
double average = static_cast<double>(total) / count;
cout << average; // prints 3.5

Mistake 3: Missing parentheses around mixed bitwise/relational expressions.

int flags = 6;
int MASK = 2;
if (flags & MASK == 2) { /* not what you expect */ }

Because == has higher precedence than &, this is parsed as flags & (MASK == 2), not (flags & MASK) == 2. Always parenthesize bitwise expressions mixed with comparisons:

int flags = 6;
int MASK = 2;
if ((flags & MASK) == 2) { /* correct comparison */ }

Best Practices

  • Use parentheses to make precedence explicit whenever an expression mixes more than one operator category, even if you know the default precedence — it helps every future reader.
  • Never rely on the evaluation order of side effects within a single expression (e.g. i++ + i++) — the standard does not guarantee an order for many such cases, and the result can be compiler-dependent or undefined.
  • Prefer compound assignment operators (+=, *=, etc.) over the expanded form — they are more concise and, for user-defined types, often more efficient.
  • Cast explicitly with static_cast<double>(...) when you need floating-point division between integer variables, rather than relying on implicit conversions.
  • Reach for prefix ++x over postfix x++ when you don’t need the old value, especially with iterators and custom types, since postfix must keep a copy of the original.
  • Use && and || short-circuiting intentionally, e.g. ptr != nullptr && ptr->isValid(), to guard against evaluating an unsafe right-hand operand.
  • Don’t confuse the bitwise operators (&, |) with the logical operators (&&, ||) — bitwise operators do not short-circuit and operate on individual bits, not truthiness.

Practice Exercises

Exercise 1: Write a program that declares two integers, then prints the results of all five arithmetic operators (+, -, *, /, %) applied to them, clearly labeling each line of output.

Exercise 2: Declare an integer variable temperature. Using relational and logical operators, print "Comfortable" if it is between 18 and 26 inclusive, and "Uncomfortable" otherwise. Do this with a single boolean expression combining >=, <=, and &&.

Exercise 3: Predict the output of the following on paper first, then verify by compiling it: int n = 4; cout << (n++ * 2) << " " << n;. Explain in one sentence why the first printed value uses the pre-increment value of n.

Summary

  • Operators combine one, two, or three operands into a resulting value; categories include arithmetic, relational, logical, assignment, increment/decrement, and bitwise.
  • Integer division truncates toward zero and modulus (%) only applies to integer types.
  • Relational and logical operators always produce a bool, printed as 1/0 by cout unless boolalpha is set.
  • Post-increment (x++) returns the old value; pre-increment (++x) returns the new value.
  • Logical && and || short-circuit — the right-hand operand may never be evaluated.
  • Operator precedence and associativity determine evaluation order in mixed expressions; use parentheses to avoid ambiguity and bugs.
  • Common bugs stem from confusing = with ==, forgetting integer division truncates, and mixing bitwise operators with comparisons without parentheses.