Mastering computational primitives in C++: arithmetic expressions, compound assignments, relational comparisons, logical short-circuiting, unary operations, prefix vs postfix increment semantics (lvalue vs rvalue mechanics), bitwise masking and shifting, ternary conditional operators, compile-time sizeof calculus, and operator precedence and associativity.
In C++, an expression is a sequence of operators and operands that specifies a computation and yields a result value. Arithmetic operators execute fundamental mathematical calculations:
+ (Addition), - (Subtraction), * (Multiplication)./ (Division): When both operands are integers, C++ performs integer division, truncating all fractional components toward zero (e.g., 7 / 2 yields 3, not 3.5).% (Modulo / Remainder): Returns the integer remainder of division (e.g., 7 % 2 yields 1). Both operands must strictly be integral types; attempting 7.5 % 2 triggers a compile-time error.Combines an arithmetic or bitwise operation with value assignment:
x += y ≡ x = x + y (evaluating x only once).
+=, -=, *=, /=, %=&=, |=, ^=, <<=, >>=a = b = c = 10; assigns 10 to c, then b, then a.
Dividing an integer by literal zero (x / 0) or modulo zero (x % 0) triggers Undefined Behavior (UB) in C++, typically terminating the program instantly with a CPU Hardware Exception (SIGFPE: Floating Point Exception). In floating-point arithmetic (IEEE 754), dividing by 0.0 results in inf (Infinity) or nan (Not a Number).
Relational and logical operators evaluate conditions to produce a bool result (true or false):
| Operator | Syntactic Name | Evaluation Rule | Example (int a=5, b=10) |
|---|---|---|---|
== |
Equal To | Returns true if both operands possess identical values |
(a == b) → false |
!= |
Not Equal To | Returns true if operand values differ |
(a != b) → true |
<, > |
Less than, Greater than | Strict magnitude comparison | (a < b) → true |
<=, >= |
Less/Greater than or equal | Non-strict magnitude comparison | (a <= 5) → true |
&& |
Logical AND | Returns true only if both operands evaluate to true |
(a > 0 && b > 0) → true |
|| |
Logical OR | Returns true if at least one operand evaluates to true |
(a > 10 || b > 5) → true |
! |
Logical NOT (Unary) | Inverts truth value (!true → false, !false → true) |
!(a == b) → true |
C++ guarantees strict left-to-right evaluation for logical operators, halting execution the instant the final outcome is determined:
A && B): If A evaluates to false, the overall expression must be false; expression B is never executed!A || B): If A evaluates to true, the overall expression must be true; expression B is never executed!
Life-saving Idiom (Null-Pointer Guard):
if (ptr != nullptr && ptr->isActive()) { /* Safe! ptr->isActive() is skipped if ptr is null */ }
(ptr != nullptr)0x0 (null), result is false.&&, false AND anything is false. CPU bypasses second operand entirely!ptr->val is avoided, preserving process runtime stability.
Unary operators operate on a single operand. The most widely used—and nuanced—are the increment (++) and decrement (--) operators:
++x)Increments the operand in memory first, and returns the newly incremented lvalue reference.
++++x; is valid in C++).x++)Makes a temporary copy of the current value, increments the original variable in memory, and returns the old un-incremented copy.
#include <iostream>
int main() {
int a = 10;
int b = 10;
// Prefix: increments 'a' to 11, then assigns 11 to res1
int res1 = ++a;
std::cout << "Prefix: res1 = " << res1 << ", a = " << a << "\n"; // 11, 11
// Postfix: copies old value 10 to res2, then increments 'b' to 11
int res2 = b++;
std::cout << "Postfix: res2 = " << res2 << ", b = " << b << "\n"; // 10, 11
return 0;
}
Bitwise operators perform Boolean algebraic operations directly on individual binary bits inside CPU registers, essential for embedded drivers, network protocols, and game physics:
| Operator | Name | Truth Rule | Binary Example (x=5 [0101], y=3 [0011]) |
|---|---|---|---|
& |
Bitwise AND | Bit is 1 only if both corresponding bits are 1 | 0101 & 0011 → 0001 (1) |
| |
Bitwise OR | Bit is 1 if at least one corresponding bit is 1 | 0101 | 0011 → 0111 (7) |
^ |
Bitwise XOR | Bit is 1 if corresponding bits are different | 0101 ^ 0011 → 0110 (6) |
~ |
Bitwise NOT | Unary: inverts every bit (1 → 0, 0 → 1) | ~00000101 → 11111010 (-6 in 2's comp) |
<< |
Left Shift | Shifts bits left by N positions (Equivalent to multiplying by 2N) | 00000101 << 1 → 00001010 (10) |
>> |
Right Shift | Shifts bits right by N positions (Equivalent to dividing by 2N) | 00000101 >> 1 → 00000010 (2) |
#include <iostream>
#include <bitset>
// Industry Bitmasking Flags (File Permissions)
constexpr unsigned char READ_PERMISSION = 0b00000001; // 1
constexpr unsigned char WRITE_PERMISSION = 0b00000010; // 2
constexpr unsigned char EXECUTE_PERMISSION = 0b00000100; // 4
int main() {
unsigned char userFlags = 0;
// 1. SET A BIT (OR operator |)
userFlags |= (READ_PERMISSION | WRITE_PERMISSION);
// 2. CHECK A BIT (AND operator &)
bool canWrite = (userFlags & WRITE_PERMISSION) != 0;
// 3. CLEAR A BIT (AND with NOT: & ~)
userFlags &= ~WRITE_PERMISSION;
// 4. TOGGLE A BIT (XOR operator ^)
userFlags ^= EXECUTE_PERMISSION;
std::cout << "Current Permissions Bitmask: "
<< std::bitset<8>(userFlags) << "\n"; // 00000101
return 0;
}
?:)C++'s only ternary operator, providing a compact, expression-based conditional:
condition ? expression_if_true : expression_if_false
const variables inline: const int max = (a > b) ? a : b;.sizeof Compile-Time OperatorReturns the size in bytes of the object representation of a type or expression:
sizeof(expr) evaluates at compile time without executing expr! (e.g., sizeof(i++) does not increment i).size_t count = sizeof(arr) / sizeof(arr[0]);When an expression contains multiple operators, Precedence dictates the binding order of operators, and Associativity dictates the direction of evaluation (Left-to-Right or Right-to-Left) when operators share equal precedence:
| Tier | Operators | Category Description | Associativity |
|---|---|---|---|
| Level 1 (Highest) | :: |
Scope Resolution | Left-to-Right |
| Level 2 | (), [], ., ->, x++, x-- |
Member access, subscripts, postfix increment | Left-to-Right |
| Level 3 | ++x, --x, +, -, !, ~, *ptr, &var, sizeof |
Unary operators, prefix increment, address-of | Right-to-Left |
| Level 4 | .*, ->* |
Pointer-to-member | Left-to-Right |
| Level 5 | *, /, % |
Multiplication, Division, Modulo | Left-to-Right |
| Level 6 | +, - |
Binary Addition, Subtraction | Left-to-Right |
| Level 7 | <<, >> |
Bitwise Shift (and Stream I/O) | Left-to-Right |
| Level 8 | <, <=, >, >= |
Relational comparisons | Left-to-Right |
| Level 9 | ==, != |
Equality and Inequality | Left-to-Right |
| Level 10 | & |
Bitwise AND | Left-to-Right |
| Level 11 | ^ |
Bitwise XOR | Left-to-Right |
| Level 12 | | |
Bitwise OR | Left-to-Right |
| Level 13 | && |
Logical AND | Left-to-Right |
| Level 14 | || |
Logical OR | Left-to-Right |
| Level 15 | ?: |
Conditional Ternary | Right-to-Left |
| Level 16 | =, +=, -=, *=, /=, &=, etc. |
Direct and compound assignments | Right-to-Left |
| Level 17 (Lowest) | , |
Comma Operator | Left-to-Right |
Never rely on colleagues or compiler edge cases to memorize complex precedence chains! Writing if ((flags & MASK) == TARGET) is clear and unambiguous. Writing if (flags & MASK == TARGET) causes a critical bug because == has higher precedence than &, evaluating to flags & (MASK == TARGET)!
++x) preferred over postfix increment (x++) in C++?
++x) mutates the variable and returns the modified lvalue reference directly, requiring no extra memory or temporary copy. Postfix increment (x++) must construct a temporary copy of the old value, mutate the original variable, and return the temporary copy by value. For complex user-defined types (like STL container iterators), postfix increment incurs an avoidable performance and memory overhead.
expr1 && expr2), if expr1 evaluates to false, C++ guarantees that expr2 will not be evaluated at all. When written as if (ptr != nullptr && ptr->value > 0), if ptr is null, the left condition fails and the right condition (which would dereference a null pointer and trigger a segmentation fault) is safely bypassed.
<< and >>) relate mathematically to arithmetic operations?
x << N) multiplies the value by 2N (assuming no overflow). Right-shifting an unsigned integer by N bits (x >> N) divides the value by 2N, discarding the remainder. In low-level systems programming, bit shifts execute in a single CPU clock cycle, providing maximum performance.
sizeof operator execute runtime side effects inside its operand?
sizeof is completely evaluated at compile time by the compiler inspecting the type yielded by the expression. Therefore, an expression with side effects such as sizeof(i++) does not actually execute i++ at runtime; the value of i remains completely unchanged.
flags & MASK == 0 fail to evaluate as expected without parentheses?
==, Level 9) has higher precedence than the bitwise AND operator (&, Level 10). Consequently, the compiler parses the expression as flags & (MASK == 0) instead of the intended (flags & MASK) == 0, yielding unexpected logic errors.
-7 % 3 and 7 % -3 according to modern C++ standards?
(a / b) * b + (a % b) == a. This guarantees that the sign of the remainder matches the sign of the numerator (left operand). Thus, -7 % 3 yields -1, while 7 % -3 yields 1.
?:) be assigned to an lvalue?
(a > b ? a : b) = 100;, which assigns 100 directly to whichever variable is greater.
^) enable swapping two integers without a temporary variable?
x ^ x = 0 and x ^ 0 = x. By executing: a = a ^ b; b = a ^ b; a = a ^ b;, the variables swap their values in place without allocating extra memory or registers.