Beginner

Operators in C++

Master arithmetic, comparison, logical, bitwise, and assignment operators in C++ with runnable Docker examples covering precedence and operator overloading.

Operators are the verbs of C++. They combine values and variables into expressions, drive control flow, and form the backbone of every computation. Because C++ is a statically and strongly typed language, every operator has well-defined behavior for each operand type — and many can be overloaded to give your own classes natural syntax.

C++ inherits its operator set from C but extends it with stream operators (<<, >>), scope resolution (::), member access through pointers (->*), and the ability to redefine almost any operator for user-defined types. This tutorial focuses on the built-in operators every C++ programmer uses daily, then closes with a brief look at operator overloading — the feature that lets std::cout << x and a + b work for arbitrary types.

You’ll learn how arithmetic differs for integers and floating-point values, how short-circuit evaluation can prevent crashes, why bitwise operators matter even in high-level code, and how operator precedence determines what a complex expression actually means.

Arithmetic and Assignment Operators

C++ provides the familiar +, -, *, /, and %. Integer division truncates toward zero; modulo returns the remainder. Compound assignment operators (+=, -=, etc.) modify a variable in place, and ++/-- increment or decrement by one in either prefix or postfix form.

Create a file named operators_arithmetic.cpp:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>

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

    std::cout << "a + b  = " << (a + b) << '\n';
    std::cout << "a - b  = " << (a - b) << '\n';
    std::cout << "a * b  = " << (a * b) << '\n';
    std::cout << "a / b  = " << (a / b) << "  (integer division)\n";
    std::cout << "a % b  = " << (a % b) << '\n';

    double x = 17.0;
    double y = 5.0;
    std::cout << "x / y  = " << (x / y) << "  (floating-point)\n";

    int counter = 10;
    counter += 5;   // 15
    counter -= 2;   // 13
    counter *= 2;   // 26
    counter /= 4;   // 6
    std::cout << "counter after compound ops = " << counter << '\n';

    int n = 7;
    std::cout << "n++ evaluates to " << n++ << ", then n = " << n << '\n';
    std::cout << "++n evaluates to " << ++n << ", then n = " << n << '\n';

    return 0;
}

Comparison and Logical Operators

Comparison operators (==, !=, <, >, <=, >=) yield a bool (true or false, printed as 1 or 0 by default). Logical operators &&, ||, and ! combine boolean expressions and use short-circuit evaluation&& skips its right operand when the left is false, and || skips it when the left is true.

Create a file named operators_logical.cpp:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>

int main() {
    int age = 25;
    int score = 88;

    std::cout << "age == 25 : " << (age == 25) << '\n';
    std::cout << "age != 30 : " << (age != 30) << '\n';
    std::cout << "score >= 90: " << (score >= 90) << '\n';
    std::cout << "score < 90 : " << (score < 90) << '\n';

    bool eligible = (age >= 18) && (score >= 80);
    bool flagged  = (age < 18) || (score < 60);

    std::cout << "eligible (age>=18 && score>=80) = " << eligible << '\n';
    std::cout << "flagged  (age<18  || score<60)  = " << flagged  << '\n';
    std::cout << "!eligible = " << !eligible << '\n';

    // Short-circuit: the right side is never evaluated when the left
    // already determines the result, so divide-by-zero is avoided.
    int divisor = 0;
    if (divisor != 0 && (100 / divisor) > 1) {
        std::cout << "branch taken\n";
    } else {
        std::cout << "short-circuit prevented division by zero\n";
    }

    return 0;
}

Bitwise Operators and Precedence

C++ exposes the underlying bit representation of integers through & (AND), | (OR), ^ (XOR), ~ (NOT), and the shift operators << and >>. These are essential for flags, masks, low-level protocols, and performance-critical code. Operator precedence determines grouping when parentheses are absent — for example, * and / bind tighter than + and -, and arithmetic operators bind tighter than comparison operators.

Create a file named operators_bitwise.cpp:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
#include <bitset>

int main() {
    unsigned int a = 0b1100;  // 12
    unsigned int b = 0b1010;  // 10

    std::cout << "a       = " << std::bitset<4>(a) << " (" << a << ")\n";
    std::cout << "b       = " << std::bitset<4>(b) << " (" << b << ")\n";
    std::cout << "a & b   = " << std::bitset<4>(a & b) << " (" << (a & b) << ")\n";
    std::cout << "a | b   = " << std::bitset<4>(a | b) << " (" << (a | b) << ")\n";
    std::cout << "a ^ b   = " << std::bitset<4>(a ^ b) << " (" << (a ^ b) << ")\n";
    std::cout << "a << 2  = " << (a << 2) << "  (multiply by 4)\n";
    std::cout << "a >> 1  = " << (a >> 1) << "  (divide by 2)\n";

    // Precedence: * binds tighter than +, and + binds tighter than <
    int result = 2 + 3 * 4;        // 14, not 20
    bool check = 2 + 3 < 6;        // (2+3) < 6  ->  true
    std::cout << "2 + 3 * 4 = " << result << '\n';
    std::cout << "2 + 3 < 6 = " << check  << '\n';

    // Ternary conditional operator: condition ? then : else
    int n = 7;
    const char* parity = (n % 2 == 0) ? "even" : "odd";
    std::cout << n << " is " << parity << '\n';

    return 0;
}

Operator Overloading

One of C++’s defining features is the ability to define what operators mean for your own types. This is why std::string supports + for concatenation and std::cout supports << for output — they aren’t built-in keywords, they’re overloaded operators in the standard library.

Create a file named operators_overload.cpp:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>

struct Vec2 {
    double x, y;

    Vec2 operator+(const Vec2& other) const {
        return Vec2{x + other.x, y + other.y};
    }

    bool operator==(const Vec2& other) const {
        return x == other.x && y == other.y;
    }
};

std::ostream& operator<<(std::ostream& os, const Vec2& v) {
    return os << '(' << v.x << ", " << v.y << ')';
}

int main() {
    Vec2 a{1.0, 2.0};
    Vec2 b{3.0, 4.0};
    Vec2 c = a + b;

    std::cout << "a     = " << a << '\n';
    std::cout << "b     = " << b << '\n';
    std::cout << "a + b = " << c << '\n';
    std::cout << "a == b? " << (a == b) << '\n';
    std::cout << "c == Vec2{4,6}? " << (c == Vec2{4.0, 6.0}) << '\n';

    return 0;
}

Running with Docker

The official gcc:14 image ships with g++, so no local compiler is required.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# Pull the official GCC image
docker pull gcc:14

# Compile and run each example
docker run --rm -v $(pwd):/app -w /app gcc:14 \
    sh -c 'g++ -std=c++20 -o arithmetic operators_arithmetic.cpp && ./arithmetic'

docker run --rm -v $(pwd):/app -w /app gcc:14 \
    sh -c 'g++ -std=c++20 -o logical operators_logical.cpp && ./logical'

docker run --rm -v $(pwd):/app -w /app gcc:14 \
    sh -c 'g++ -std=c++20 -o bitwise operators_bitwise.cpp && ./bitwise'

docker run --rm -v $(pwd):/app -w /app gcc:14 \
    sh -c 'g++ -std=c++20 -o overload operators_overload.cpp && ./overload'

Expected Output

operators_arithmetic.cpp:

a + b  = 22
a - b  = 12
a * b  = 85
a / b  = 3  (integer division)
a % b  = 2
x / y  = 3.4  (floating-point)
counter after compound ops = 6
n++ evaluates to 7, then n = 8
++n evaluates to 9, then n = 9

operators_logical.cpp:

age == 25 : 1
age != 30 : 1
score >= 90: 0
score < 90 : 1
eligible (age>=18 && score>=80) = 1
flagged  (age<18  || score<60)  = 0
!eligible = 0
short-circuit prevented division by zero

operators_bitwise.cpp:

a       = 1100 (12)
b       = 1010 (10)
a & b   = 1000 (8)
a | b   = 1110 (14)
a ^ b   = 0110 (6)
a << 2  = 48  (multiply by 4)
a >> 1  = 6  (divide by 2)
2 + 3 * 4 = 14
2 + 3 < 6 = 1
7 is odd

operators_overload.cpp:

a     = (1, 2)
b     = (3, 4)
a + b = (4, 6)
a == b? 0
c == Vec2{4,6}? 1

Key Concepts

  • Integer vs floating-point division: / truncates for integer operands but performs true division when at least one operand is a floating-point type. Mix types deliberately.
  • Short-circuit evaluation: && and || only evaluate their right operand when needed, making guard expressions like p != nullptr && p->ready() safe.
  • Prefix vs postfix ++/--: Prefix returns the new value; postfix returns the old value. Prefer prefix for non-primitive types where the copy has cost.
  • Bitwise operators work on bit patterns, not booleans — use &&/|| for logic and &/| for bits. They are not interchangeable.
  • Precedence and associativity determine grouping without parentheses. When in doubt, parenthesize for clarity rather than memorizing the full table.
  • The ternary ?: is an expression (it has a value), unlike if, which is a statement. Use it for short value selections.
  • Operator overloading lets user-defined types use familiar syntax. Overload member operators for asymmetric ones like += and free functions for symmetric ones like << to streams.
  • << and >> are not just shifts — when applied to streams they are overloaded for I/O, a classic demonstration of how operator overloading shapes idiomatic C++.

Running Today

All examples can be run using Docker:

docker pull gcc:14
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining