Operators — Arithmetic, Relational, Logical, Bitwise, and Precedence
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
- What is the value of
10 / 4and10.0 / 4in C++? - Write an expression that checks if a number is even using only bitwise operators.
- What does
(true || x++)evaluate to? Doesxincrement? - What is the order of evaluation in
a + b * c / d - e? - 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
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