Skip to content

Operators — Arithmetic, Relational, Logical, Bitwise, and Precedence

DodaTech Updated 2026-06-28 10 min read

In this tutorial, you will learn about Operators. We cover key concepts, practical examples, and best practices to help you master this topic.

C++ provides a rich set of operators that map directly to CPU instructions, including arithmetic, relational, logical, bitwise, and assignment, with well-defined precedence and associativity rules.

What You'll Learn

You will master C++ operators grouped by category, understand operator precedence and associativity to write correct expressions, learn bitwise operations for low-level programming, use compound assignment operators for concise code, and avoid common pitfalls like integer division truncation and short-circuit evaluation surprises.

Why It Matters

Operators are the building blocks of every computation. Misunderstanding operator precedence causes subtle bugs that compile without warning. Bitwise operators are essential for graphics, cryptography, networking, and Embedded Systems. Logical operator short-circuiting is a powerful tool for conditionally evaluating expressions.

Learning Path

graph LR
    A["05: Constants & Modifiers"] --> B["06: Operators"]
    B --> C["07: Control Flow"]
    C --> D["08: Loops"]
    style A fill:#4a90d9,stroke:#2c5f8a,color:#fff
    style B fill:#4a90d9,stroke:#2c5f8a,color:#fff
    style C fill:#4a90d9,stroke:#2c5f8a,color:#fff
    style D fill:#4a90d9,stroke:#2c5f8a,color:#fff

Arithmetic Operators

#include <iostream>

int main() {
    int a = 10, b = 3;
    
    std::cout << "a + b = " << (a + b) << "\n";   // 13
    std::cout << "a - b = " << (a - b) << "\n";   // 7
    std::cout << "a * b = " << (a * b) << "\n";   // 30
    std::cout << "a / b = " << (a / b) << "\n";   // 3 (integer division)
    std::cout << "a % b = " << (a % b) << "\n";   // 1 (modulo)
    
    double x = 10.0, y = 3.0;
    std::cout << "x / y = " << (x / y) << "\n";   // 3.33333
    
    // Unary operators
    int c = 5;
    std::cout << +c << " " << -c << "\n";         // 5 -5
    
    // Increment/decrement
    int i = 1;
    std::cout << i++ << " ";  // prints 1, then i=2 (post-increment)
    std::cout << ++i << "\n"; // i=3, then prints 3 (pre-increment)
}

Integer division truncates toward zero in C++11 and later. 10 / 3 yields 3, not 3.333. If you need a fractional result, ensure at least one operand is a floating-point type.

Relational Operators

#include <iostream>

int main() {
    int a = 5, b = 10;
    
    std::cout << std::boolalpha;  // print true/false instead of 1/0
    std::cout << (a == b) << "\n";  // false
    std::cout << (a != b) << "\n";  // true
    std::cout << (a < b)  << "\n";  // true
    std::cout << (a > b)  << "\n";  // false
    std::cout << (a <= b) << "\n";  // true
    std::cout << (a >= b) << "\n";  // false
    
    // Chaining comparison (mathematical notation)
    bool in_range = (1 < a) && (a < 10);  // true
    std::cout << in_range << "\n";
}

Logical Operators

#include <iostream>

int main() {
    bool a = true, b = false;
    
    std::cout << std::boolalpha;
    std::cout << (a && b) << "\n";  // false (logical AND)
    std::cout << (a || b) << "\n";  // true  (logical OR)
    std::cout << (!a)     << "\n";  // false (logical NOT)
    
    // Short-circuit evaluation
    int x = 0;
    bool result = (x != 0) && (10 / x > 2);  // false, does not evaluate 10/x
    std::cout << result << "\n";  // false
    
    // The left side of || short-circuits if true
    bool shortcut = true || (x++ > 0);
    std::cout << x << "\n";  // 0, x++ never executed
}

Short-circuit evaluation means && stops evaluating as soon as the result is determined (first false), and || stops as soon as it sees true. This is essential for guard conditions like ptr && ptr->isValid().

Bitwise Operators

#include <iostream>
#include <bitset>

int main() {
    unsigned int a = 0b1100;  // 12 in binary
    unsigned int b = 0b1010;  // 10 in binary
    
    std::cout << std::bitset<4>(a & b)  << "\n";  // 1000 (bitwise AND)
    std::cout << std::bitset<4>(a | b)  << "\n";  // 1110 (bitwise OR)
    std::cout << std::bitset<4>(a ^ b)  << "\n";  // 0110 (bitwise XOR)
    std::cout << std::bitset<4>(~a)     << "\n";  // ...0011 (bitwise NOT)
    
    std::cout << std::bitset<8>(a << 2) << "\n";  // 00110000 (left shift)
    std::cout << std::bitset<8>(a >> 2) << "\n";  // 00000011 (right shift)
    
    // Practical: checking and setting flags
    const unsigned int READ_FLAG   = 1 << 0;  // 001
    const unsigned int WRITE_FLAG  = 1 << 1;  // 010
    const unsigned int EXEC_FLAG   = 1 << 2;  // 100
    
    unsigned int permissions = READ_FLAG | WRITE_FLAG;  // 011
    bool can_read  = permissions & READ_FLAG;   // true
    bool can_write = permissions & WRITE_FLAG;  // true
    bool can_exec  = permissions & EXEC_FLAG;   // false
    
    std::cout << can_read << " " << can_write << " " << can_exec << "\n";
    
    // Toggle a flag with XOR
    permissions ^= WRITE_FLAG;  // remove write
    std::cout << permissions << "\n";
}

Compound Assignment

int x = 10;
x += 5;    // x = x + 5  (15)
x -= 3;    // x = x - 3  (12)
x *= 2;    // x = x * 2  (24)
x /= 4;    // x = x / 4  (6)
x %= 3;    // x = x % 3  (0)
x &= 0xFF; // x = x & 0xFF
x |= 0x0F; // x = x | 0x0F
x ^= 0xAA; // x = x ^ 0xAA
x <<= 2;   // x = x << 2
x >>= 1;   // x = x >> 1

Operator Precedence

Precedence determines which operators are evaluated first when multiple operators appear in an expression. Higher precedence operators bind tighter.

| Precedence | Operators | Associativity | |-----------|-----------|---------------| | 1 (highest) | :: | Left-to-right | | 2 | () [] -> . ++ -- | Left-to-right | | 3 | ++ -- + - ! ~ * & (unary) | Right-to-left | | 4 | .* ->* | Left-to-right | | 5 | * / % | Left-to-right | | 6 | + - | Left-to-right | | 7 | << >> | Left-to-right | | 8 | < <= > >= | Left-to-right | | 9 | == != | Left-to-right | | 10 | & (bitwise) | Left-to-right | | 11 | ^ | Left-to-right | | 12 | | | Left-to-right | | 13 | && | Left-to-right | | 14 | || | Left-to-right | | 15 | ?: (ternary) | Right-to-left | | 16 | = += -= ... | Right-to-left | | 17 (lowest) | , | Left-to-right |

int x = 5 + 3 * 2;     // 5 + (3 * 2) = 11, not (5 + 3) * 2
int y = (5 + 3) * 2;   // 16 (parentheses override precedence)

int a = 1, b = 2, c = 3;
int z = a = b = c;     // right-to-left: a = (b = c), all become 3

Guideline: Use parentheses to make precedence explicit, even when you know the rules. It improves readability and prevents mistakes during maintenance.

The Ternary Operator

int score = 85;
std::string grade = (score >= 60) ? "Pass" : "Fail";
std::cout << grade << "\n";

// Nested ternary (use sparingly)
int x = 10;
std::string result = (x > 0) ? "positive" : (x < 0) ? "negative" : "zero";

The Comma Operator

int a = 1, b = 2;
int c = (a += 1, b += 2, a + b);  // evaluates each, returns last
std::cout << a << " " << b << " " << c << "\n";  // 2 4 6

The comma operator evaluates each operand left-to-right and returns the value of the rightmost operand. It is rarely needed but useful in for loop increment expressions.

Common Mistakes

Mistake 1: Assignment instead of Comparison

if (x = 5) { ... }  // assigns 5 to x, always true (since 5 is non-zero)

Compile with -Wparentheses to catch this. GCC's -Wall includes it.

Mistake 2: Integer Division

double fraction = 1/3;  // 0.0, not 0.333

Use 1.0/3 or static_cast<double>(1)/3.

Mistake 3: Bitwise vs Logical Operators

if (x & y) { ... }  // bitwise AND, not logical AND

Use && for logical AND unless you specifically need bitwise operations.

Mistake 4: Confusing Precedence of << with Arithmetic

std::cout << 5 + 3;     // prints 8 (addition first)
std::cout << (5 << 3);  // prints 40 (need parentheses)

The << operator for output has different precedence than the shift <<.

Mistake 5: Modulo with Negative Numbers

In C++11 and later, -5 % 3 yields -2 (the sign follows the dividend). Earlier standards could give different results.

Mistake 6: Short-Circuit Side Effects

if (ptr != nullptr && ptr->value() > 0) { ... }  // safe: short-circuit protects ptr

Practice Questions

  1. What is the value of 10 / 4 and 10.0 / 4 in C++?
  2. Write an expression that checks if a number is even using only bitwise operators.
  3. What does (true || x++) evaluate to? Does x increment?
  4. What is the order of evaluation in a + b * c / d - e?
  5. Write a function that uses bitwise operations to count the number of 1 bits in an integer.

Challenge

Implement a getBit, setBit, clearBit, and toggleBit function using bitwise operators, then write a program that manipulates the 3rd bit of an integer and prints the result in binary.

FAQ

What is the difference between `++i` and `i++`?

++i increments i and returns the new value. i++ returns the original value and then increments. For integers, both produce the same final state, but ++i is preferred because it avoids creating a temporary copy.

Can I use `&&` and `||` with non-bool types?

Yes, C++ treats zero as false and non-zero as true. 5 && 3 evaluates to true (1). The result of && and || is bool in C++.

Why does `-1 >> 1` not always produce `0`?

Right shift of a signed negative value is implementation-defined. GCC and Clang perform arithmetic right shift (sign-extending), so -1 >> 1 remains -1. Use unsigned types when you want logical right shifts.

What is the `sizeof` operator?

sizeof returns the size of a type or expression in bytes. It is evaluated at compile time (except for variable-length arrays in C, which C++ does not have).

When should I use the comma operator?

Almost never. It is occasionally useful in for loop increment expressions like for (int i=0, j=10; i<j; ++i, --j).

Does C++ have a power operator?

No. Use std::pow from <cmath> for floating-point exponentiation, or write your own constexpr function for integers.

Mini Project

Write a bit manipulation library:

#include <iostream>
#include <bitset>

unsigned int getBit(unsigned int value, unsigned int bit) {
    return (value >> bit) & 1;
}

unsigned int setBit(unsigned int value, unsigned int bit) {
    return value | (1 << bit);
}

unsigned int clearBit(unsigned int value, unsigned int bit) {
    return value & ~(1 << bit);
}

unsigned int toggleBit(unsigned int value, unsigned int bit) {
    return value ^ (1 << bit);
}

int main() {
    unsigned int flags = 0;
    flags = setBit(flags, 0);
    flags = setBit(flags, 2);
    flags = setBit(flags, 5);
    
    std::cout << std::bitset<8>(flags) << " = " << flags << "\n";
    std::cout << "Bit 2: " << getBit(flags, 2) << "\n";
    std::cout << "Bit 1: " << getBit(flags, 1) << "\n";
    
    flags = toggleBit(flags, 2);
    std::cout << std::bitset<8>(flags) << "\n";
    
    flags = clearBit(flags, 0);
    std::cout << std::bitset<8>(flags) << "\n";
}

Expected output:

00100101 = 37
Bit 2: 1
Bit 1: 0
00100001
00100000

What's Next

Operators let you compute values and combine conditions. The next lesson covers control flow: if/else, switch, the ternary operator, and the C++17 if constexpr for compile-time branching.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro