C++ Operators & Expressions: Arithmetic, Logical, Bitwise & Precedence Guide
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.