C++ operators and expressions - floating holographic operator symbols in circuit pattern

C++ Operators & Expressions: Arithmetic, Logical, Bitwise & Precedence Guide

Back to C++ RoadmapC++ Programming Course • 65 Lessons

What Are Operators and Expressions?

An operator is a symbol that tells the compiler to perform a specific operation on one or more values (called operands). An expression is any combination of operators, values, and variables that evaluates to a single result. For example, 2 + 3 is an expression that uses the addition operator on two integer operands and evaluates to 5.

C++ has more operators than almost any other language — over 50 of them. In this lesson we cover the ones you will use daily. Later in the course, you will learn how to overload operators to work with your own custom types.

Arithmetic Operators

#include <iostream>

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

    std::cout << "a + b  = " << (a + b) << "\n";   // 22  (addition)
    std::cout << "a - b  = " << (a - b) << "\n";   // 12  (subtraction)
    std::cout << "a * b  = " << (a * b) << "\n";   // 85  (multiplication)
    std::cout << "a / b  = " << (a / b) << "\n";   // 3   (integer division!)
    std::cout << "a % b  = " << (a % b) << "\n";   // 2   (modulus/remainder)

    // Negation (unary minus)
    int c = -a;
    std::cout << "-a = " << c << "\n";  // -17

    // Floating-point arithmetic
    double x = 17.0, y = 5.0;
    std::cout << "x / y  = " << (x / y) << "\n";   // 3.4  (true division)

    // Mixed types: int + double promotes to double
    double result = a + 0.5;
    std::cout << "17 + 0.5 = " << result << "\n";   // 17.5

    return 0;
}

Integer Division and Modulus

This is one of the most common sources of bugs for beginners. When both operands of / are integers, C++ performs integer division — it truncates toward zero, discarding the fractional part:

#include <iostream>

int main() {
    // Integer division truncates toward zero
    std::cout << "7 / 2   = " << (7 / 2) << "\n";     // 3 (not 3.5!)
    std::cout << "-7 / 2  = " << (-7 / 2) << "\n";    // -3 (truncates toward zero)
    std::cout << "1 / 3   = " << (1 / 3) << "\n";     // 0 (not 0.333!)

    // To get true division, make at least one operand a double
    std::cout << "7.0 / 2 = " << (7.0 / 2) << "\n";   // 3.5
    std::cout << "7 / 2.0 = " << (7 / 2.0) << "\n";   // 3.5
    std::cout << "static_cast: " << (static_cast<double>(7) / 2) << "\n";  // 3.5

    // Modulus (remainder) — only works with integers
    std::cout << "17 % 5  = " << (17 % 5) << "\n";    // 2
    std::cout << "-17 % 5 = " << (-17 % 5) << "\n";   // -2 (sign follows dividend)

    // Common uses of modulus
    int num = 42;
    bool isEven = (num % 2 == 0);    // Check even/odd
    int lastDigit = num % 10;         // Extract last digit (2)
    bool divisible = (num % 7 == 0);  // Check divisibility

    std::cout << num << " is " << (isEven ? "even" : "odd") << "\n";
    std::cout << "Last digit: " << lastDigit << "\n";

    return 0;
}

Increment and Decrement (++ and –)

#include <iostream>

int main() {
    int a = 5;

    // Post-increment: returns the OLD value, THEN increments
    std::cout << "a++: " << a++ << "\n";  // Prints 5
    std::cout << "a is now: " << a << "\n"; // a is now 6

    // Pre-increment: increments FIRST, then returns the NEW value
    std::cout << "++a: " << ++a << "\n";  // Prints 7 (incremented first)

    // Same for decrement
    int b = 10;
    std::cout << "b--: " << b-- << "\n";  // Prints 10
    std::cout << "--b: " << --b << "\n";  // Prints 8

    // Best practice: prefer pre-increment (++i) in loops
    // For integers, both generate the same code
    // For iterators and complex types, ++i avoids creating a temporary copy
    for (int i = 0; i < 5; ++i) {
        std::cout << i << " ";
    }
    std::cout << "\n";

    return 0;
}

Assignment Operators

#include <iostream>

int main() {
    int x = 100;

    x += 10;   // x = x + 10;   → 110
    x -= 20;   // x = x - 20;   → 90
    x *= 2;    // x = x * 2;    → 180
    x /= 3;    // x = x / 3;    → 60
    x %= 7;    // x = x % 7;    → 4

    std::cout << "x = " << x << "\n";

    // Bitwise assignment operators
    int flags = 0b1010;
    flags |= 0b0101;   // Set bits     → 0b1111
    flags &= 0b1100;   // Clear bits   → 0b1100
    flags ^= 0b0110;   // Toggle bits  → 0b1010
    flags <<= 1;       // Shift left   → 0b10100
    flags >>= 2;       // Shift right  → 0b101

    std::cout << "flags = " << flags << "\n";

    return 0;
}

Comparison (Relational) Operators

#include <iostream>
#include <string>

int main() {
    int a = 10, b = 20;

    std::cout << std::boolalpha;  // Print true/false instead of 1/0

    std::cout << "a == b: " << (a == b) << "\n";  // false
    std::cout << "a != b: " << (a != b) << "\n";  // true
    std::cout << "a < b:  " << (a < b) << "\n";   // true
    std::cout << "a > b:  " << (a > b) << "\n";   // false
    std::cout << "a <= b: " << (a <= b) << "\n";  // true
    std::cout << "a >= b: " << (a >= b) << "\n";  // false

    // String comparison works naturally in C++
    std::string s1 = "apple";
    std::string s2 = "banana";
    std::cout << "apple < banana: " << (s1 < s2) << "\n";   // true (lexicographic)
    std::cout << "apple == apple: " << (s1 == "apple") << "\n"; // true

    // C++20 three-way comparison (spaceship operator)
    // auto cmp = a <=> b;
    // if (cmp < 0) std::cout << "a < b\n";
    // if (cmp == 0) std::cout << "a == b\n";
    // if (cmp > 0) std::cout << "a > b\n";

    return 0;
}

Logical Operators

#include <iostream>

int main() {
    bool a = true, b = false;

    std::cout << std::boolalpha;

    // AND — both must be true
    std::cout << "true && true:  " << (true && true) << "\n";    // true
    std::cout << "true && false: " << (true && false) << "\n";   // false

    // OR — at least one must be true
    std::cout << "false || true: " << (false || true) << "\n";   // true
    std::cout << "false || false: " << (false || false) << "\n"; // false

    // NOT — inverts the value
    std::cout << "!true:  " << (!true) << "\n";    // false
    std::cout << "!false: " << (!false) << "\n";   // true

    // Practical example: input validation
    int age = 25;
    double salary = 50000;
    bool hasGoodCredit = true;

    bool qualifies = (age >= 18 && salary > 30000) || hasGoodCredit;
    std::cout << "Qualifies for loan: " << qualifies << "\n";

    // Range check
    int score = 85;
    bool inRange = (score >= 0 && score <= 100);
    std::cout << "Score valid: " << inRange << "\n";

    return 0;
}

Short-Circuit Evaluation

#include <iostream>

int main() {
    // AND (&&) short-circuits: if left is false, right is NEVER evaluated
    int x = 0;
    if (x != 0 && 10 / x > 2) {
        std::cout << "This is safe — division by zero never happens\n";
    }
    // Without short-circuit, 10/0 would crash!

    // OR (||) short-circuits: if left is true, right is NEVER evaluated
    bool found = true;
    if (found || expensiveSearch()) {  // expensiveSearch() is never called
        std::cout << "Found early!\n";
    }

    // Side effects and short-circuit — be careful!
    int count = 0;
    bool result = (true || ++count);  // count stays 0 (increment skipped!)
    std::cout << "count: " << count << "\n";  // 0, not 1

    return 0;
}

bool expensiveSearch() {
    std::cout << "This should not print\n";
    return false;
}

Bitwise Operators

#include <iostream>
#include <bitset>

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

    std::cout << "a        = " << std::bitset<4>(a) << " (" << a << ")\n";
    std::cout << "b        = " << std::bitset<4>(b) << " (" << b << ")\n";

    // AND — both bits must be 1
    std::cout << "a & b    = " << std::bitset<4>(a & b) << " (" << (a & b) << ")\n";    // 1000 (8)

    // OR — at least one bit must be 1
    std::cout << "a | b    = " << std::bitset<4>(a | b) << " (" << (a | b) << ")\n";    // 1110 (14)

    // XOR — bits must differ
    std::cout << "a ^ b    = " << std::bitset<4>(a ^ b) << " (" << (a ^ b) << ")\n";    // 0110 (6)

    // NOT — flip all bits
    std::cout << "~a       = " << std::bitset<32>(~a) << "\n";

    // Shift left (multiply by 2^n)
    std::cout << "a << 1   = " << (a << 1) << "\n";   // 24 (12 * 2)
    std::cout << "a << 2   = " << (a << 2) << "\n";   // 48 (12 * 4)

    // Shift right (divide by 2^n)
    std::cout << "a >> 1   = " << (a >> 1) << "\n";   // 6 (12 / 2)

    // Practical: permission flags
    const unsigned int READ    = 0b001;
    const unsigned int WRITE   = 0b010;
    const unsigned int EXECUTE = 0b100;

    unsigned int perms = READ | WRITE;          // Set read + write
    bool canRead = (perms & READ) != 0;         // Check read permission
    perms |= EXECUTE;                           // Add execute
    perms &= ~WRITE;                            // Remove write

    std::cout << "Permissions: " << std::bitset<3>(perms) << "\n";  // 101

    return 0;
}

The Ternary Operator (?:)

#include <iostream>
#include <string>

int main() {
    int age = 20;

    // Syntax: condition ? value_if_true : value_if_false
    std::string status = (age >= 18) ? "adult" : "minor";
    std::cout << status << "\n";  // "adult"

    // Use for simple conditional assignment
    int a = 10, b = 20;
    int max = (a > b) ? a : b;
    std::cout << "Max: " << max << "\n";  // 20

    // Nested ternary (avoid — hard to read)
    int score = 85;
    std::string grade = (score >= 90) ? "A"
                      : (score >= 80) ? "B"
                      : (score >= 70) ? "C"
                      : "F";
    std::cout << "Grade: " << grade << "\n";  // B

    // Ternary in output
    bool isOnline = true;
    std::cout << "User is " << (isOnline ? "online" : "offline") << "\n";

    return 0;
}

The Comma Operator

#include <iostream>

int main() {
    // The comma operator evaluates left-to-right and returns the rightmost value
    int a = (1, 2, 3);  // a = 3
    std::cout << "a = " << a << "\n";

    // Most common use: multiple variables in a for loop
    for (int i = 0, j = 10; i < j; ++i, --j) {
        std::cout << "i=" << i << " j=" << j << "\n";
    }

    return 0;
}

sizeof and typeid Operators

#include <iostream>
#include <typeinfo>

int main() {
    // sizeof — returns size in bytes (compile-time)
    std::cout << "int: " << sizeof(int) << " bytes\n";
    std::cout << "double: " << sizeof(double) << " bytes\n";

    int arr[10];
    std::cout << "Array: " << sizeof(arr) << " bytes (" 
              << sizeof(arr)/sizeof(arr[0]) << " elements)\n";

    // typeid — returns type information (runtime)
    auto x = 42;
    auto y = 3.14;
    std::cout << "x is: " << typeid(x).name() << "\n";  // i (int)
    std::cout << "y is: " << typeid(y).name() << "\n";  // d (double)

    return 0;
}

Operator Precedence Table

Operators with higher precedence bind tighter. When in doubt, use parentheses to make your intent explicit:

#include <iostream>

int main() {
    // Precedence matters!
    int result1 = 2 + 3 * 4;      // 14 (not 20!) — * before +
    int result2 = (2 + 3) * 4;    // 20 — parentheses override

    std::cout << "2 + 3 * 4 = " << result1 << "\n";
    std::cout << "(2 + 3) * 4 = " << result2 << "\n";

    // Common precedence surprises
    int x = 5;
    // if (x & 1 == 0)    // WRONG! == has higher precedence than &
    // Actually parsed as: x & (1 == 0) → x & 0 → 0
    if ((x & 1) == 0) {   // CORRECT — parentheses force correct order
        std::cout << "Even\n";
    } else {
        std::cout << "Odd\n";
    }

    return 0;
}

// Precedence (high to low, simplified):
// 1.  () [] -> .              (postfix)
// 2.  ++ -- ! ~ + - * &      (unary/prefix)
// 3.  * / %                   (multiplicative)
// 4.  + -                     (additive)
// 5.  << >>                  (shift)
// 6.  < <= > >=              (relational)
// 7.  == !=                   (equality)
// 8.  &                       (bitwise AND)
// 9.  ^                       (bitwise XOR)
// 10. |                       (bitwise OR)
// 11. &&                      (logical AND)
// 12. ||                      (logical OR)
// 13. ?:                      (ternary)
// 14. = += -= *= /= etc       (assignment)
// 15. ,                       (comma)

Common Pitfalls

#include <iostream>

int main() {
    // Pitfall 1: = vs == in conditions
    int x = 5;
    // if (x = 10)    // ASSIGNMENT, not comparison! x becomes 10, condition is true
    if (x == 10) {    // CORRECT comparison
        std::cout << "Equal\n";
    }

    // Pitfall 2: Integer division when you want floating-point
    double avg = 7 / 2;           // 3.0 (integer division, then converts to double)
    double correct = 7.0 / 2;     // 3.5

    // Pitfall 3: Unsigned subtraction wrapping
    unsigned int a = 5, b = 10;
    unsigned int diff = a - b;     // Wraps to 4294967291, NOT -5!

    // Pitfall 4: Chained comparison (doesn't work like math)
    int score = 85;
    // if (70 < score < 90)     // WRONG! Parsed as: (70 < score) < 90 → 1 < 90 → true (always!)
    if (score > 70 && score < 90) {  // CORRECT
        std::cout << "B grade\n";
    }

    return 0;
}

Practice Exercises

// Exercise 1: Write a program that reads an integer and prints:
// - Whether it is even or odd (use %)
// - Whether it is positive, negative, or zero
// - Whether it is divisible by 3, 5, or both

// Exercise 2: Swap two variables using XOR (no temp variable)
// Hint: a ^= b; b ^= a; a ^= b;

// Exercise 3: Given a year, determine if it is a leap year
// Rules: divisible by 4, BUT not by 100, UNLESS also by 400
// Use logical operators in a single expression

// Exercise 4: Write a permission checker using bitwise operators
// Define READ=1, WRITE=2, EXECUTE=4
// Given a permission value, check and print which permissions are set

// Exercise 5: Implement abs() using the ternary operator
// auto myAbs = [](int x) { return x >= 0 ? x : -x; };
// Test with positive, negative, and zero values

Summary

You now know every major operator category in C++: arithmetic (+ - * / %), comparison (== != < > <= >=), logical (&& || !), bitwise (& | ^ ~ << >>), assignment (= += -= *= etc.), increment/decrement (++ --), ternary (?:), and comma. You understand operator precedence, short-circuit evaluation, and the common pitfalls that trip up beginners.

In the next lesson, we cover control flow — if/else, switch, and loops — to make your programs make decisions and repeat actions.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *