Operators are the symbols and keywords that do the actual work inside a C++ expression. They add up cart totals, compare product prices, decide whether an item is in stock, read through pointers, and tell the compiler which scope to look inside. C++ has more operator families than most languages, so this lesson sketches the full map first, then later lessons drill into the families that matter most day-to-day.
This is the map, not the deep dive. The next two lessons go deep on arithmetic, assignment, comparison, and logical operators. Bitwise, ternary, and operator overloading each get their own treatment later. Each family gets one or two examples to make the symbol recognizable, plus the parts that are easy to misread (precedence, the comma operator, ::, *p++).
In C++, an operator is anything that takes one or more values (called operands) and produces a new value. Most operators are punctuation symbols like + or <<, but a few are keywords like sizeof, new, or typeid. Operators come in three arities:
| Arity | Operands | Examples |
|---|---|---|
| Unary | 1 | -x, !ok, *ptr, &value, sizeof(int) |
| Binary | 2 | a + b, price < 100, cart = items |
| Ternary | 3 | inStock ? "ship" : "wait" |
C++ has exactly one ternary operator, the conditional ?:. Everything else is unary or binary. A symbol like * can mean different things depending on context: as a binary operator it multiplies, as a unary operator it dereferences a pointer. The compiler decides which meaning applies based on where the symbol sits in the expression.
The families in C++, grouped by what they do:
| Family | Operators | What they do |
|---|---|---|
| Arithmetic | + - * / % | Add, subtract, multiply, divide, remainder |
| Assignment | = += -= *= /= %= &= |= ^= <<= >>= | Store a value, optionally combined with another op |
| Increment/Decrement | ++ -- (pre and post) | Add or subtract 1 in place |
| Comparison | == != < > <= >= | Test equality or ordering, produce a bool |
| Logical | && || ! | Combine boolean conditions, short-circuit |
| Bitwise | & | ^ ~ << >> | Operate on the individual bits of an integer |
| Member access | . -> .* ->* | Reach into a struct, class, or object through a pointer |
| Scope resolution | :: | Name something inside a namespace or class |
| Address-of / Dereference | & * | Take the address of a value, or read through a pointer |
| Sizeof / Alignof | sizeof alignof | Ask the compiler how many bytes a type occupies |
| Conditional (ternary) | ?: | Pick one of two values based on a condition |
| Comma | , | Evaluate two expressions left to right, return the right one |
| Type cast | static_cast dynamic_cast const_cast reinterpret_cast (T)x T(x) | Convert one type to another |
| Allocation | new new[] delete delete[] | Allocate and free heap memory |
| Identification | typeid | Look up runtime type information |
| Exception | throw noexcept | Raise and check for exceptions |
That's a long list, but it doesn't need to be memorised. What matters is recognising that, for example, :: is an operator, and that sizeof is too. Each family has its own lesson or section later in the course.
:::: already appeared in std::cout. It's the scope resolution operator, and it tells the compiler "look inside this scope for the name on the right." The left side is a namespace, a class name, or empty (meaning "the global scope").
std::vector says "the vector defined inside std," and store::totalItems says "the totalItems defined inside store." Without ::, the compiler doesn't know where to look and reports error: 'vector' was not declared in this scope.
:: shows up later in two other places: in front of a name to mean "the one at global scope" (e.g., ::main), and between a class name and a member (e.g., Cart::checkout). Both are the same operator, with a different left side.
. and ->Given a struct or class object, the dot operator . reaches into it for a member. Given only a pointer to the object, the arrow operator -> does the same thing but dereferences the pointer first.
item.name reads name directly from item. ptr->name is shorthand for (*ptr).name: first follow the pointer to the object, then read name. The two lines produce the same output because ptr points at item.
The symbols are what to recognize for now.
& and Dereference *Two more pointer-related operators show up everywhere, so they belong on the map even though their deep dive comes later.
&value takes the address of value. The result is a pointer pointing at it.*ptr dereferences ptr, meaning "read or write the value ptr points at."Same symbols, different contexts. As a binary operator, & is bitwise AND (a & b). As a unary operator in front of a variable name, it's address-of. As a binary operator, * is multiplication. As a unary operator in front of a pointer, it's dereference. The compiler decides which based on where the symbol sits.
sizeof Operatorsizeof is a compile-time operator that asks "how many bytes does this type or value occupy in memory?" It returns a value of type std::size_t, an unsigned integer big enough to hold any object size on the platform.
Output (on most 64-bit systems):
Two details worth noticing. First, sizeof works on a type (sizeof(int)) or on an expression (sizeof productId). When given a type, parentheses are required. When given an expression, they're optional. Second, the result is determined at compile time, so it costs nothing at runtime, and the expression's operand isn't actually evaluated. Writing sizeof(callExpensiveFunction()) does not call the function.
The exact sizes depend on the platform. On most modern desktop and server systems, int is 4 bytes and double is 8, but the standard only guarantees minimum sizes. For exact widths, use the fixed-width types from <cstdint> like int32_t or int64_t.
sizeof is a compile-time operator that produces a constant. The operand is never evaluated at runtime, so sizeof(arr) / sizeof(arr[0]) is a common idiom for counting array elements with zero runtime cost.
The comma , is one of C++'s more unusual operators. As a binary operator, it evaluates its left side, throws away the result, evaluates its right side, and returns the right side's value.
The expression (itemsAdded = itemsAdded + 1, itemsAdded * 10) runs the assignment first (bumping itemsAdded to 1), then evaluates itemsAdded * 10 (which is 10), and the whole comma expression equals 10. The most common place this appears is inside a for loop's update clause:
That's a legitimate use. Other appearances of the comma operator are usually accidents.
Don't confuse the comma operator with the comma separator. Commas in argument lists, initializer lists, and variable declarations are separators, not operators. They have different rules.
The comma operator has the lowest precedence of any operator in C++, which is why expressions like (1, 2, 3) work without the parentheses fighting the assignment.
?:The conditional operator condition ? a : b is C++'s only ternary operator. If condition evaluates to true, the whole expression takes the value a. Otherwise it takes the value b.
The ternary is handy for assigning one of two values in a single line. Treat it as "an if/else that produces a value."
The bitwise operators work on the individual bits of an integer rather than on the integer's numeric value. There are six of them.
| Operator | Name | Quick example |
|---|---|---|
& | Bitwise AND | 0b1100 & 0b1010 is 0b1000 (8) |
| | Bitwise OR | 0b1100 | 0b1010 is 0b1110 (14) |
^ | Bitwise XOR | 0b1100 ^ 0b1010 is 0b0110 (6) |
~ | Bitwise NOT | ~0b00000101 is 0b11111010 |
<< | Left shift | 1 << 3 is 8 (shifts bits left by 3) |
>> | Right shift | 16 >> 2 is 4 (shifts bits right by 2) |
Bitwise operators show up in low-level code: storing several boolean flags in one integer, manipulating colors as packed RGBA values, masking off parts of a network packet, and a handful of fast tricks. They're far less common in everyday e-commerce code than arithmetic or comparisons.
The same symbols << and >> are reused as the stream insertion and extraction operators for std::cout and std::cin. That's operator overloading, which the standard library uses to give streams an arrow-like syntax. The compiler picks the right meaning based on the operand types.
C++ has a family of named cast operators for converting between types. They show up here just as a reminder of what each is for.
| Cast | Use |
|---|---|
static_cast<T>(x) | Compile-time conversion between related types (most common) |
dynamic_cast<T>(x) | Safe downcast in a class hierarchy (runtime checked) |
const_cast<T>(x) | Add or remove const |
reinterpret_cast<T>(x) | Reinterpret the bit pattern as a different type (rare and dangerous) |
(T)x or T(x) | C-style cast. Avoid in modern C++. |
The static_cast<double> forces total to be treated as a double before division, which makes the result a double instead of integer division's truncated 18.
A few more operators round out the map. They aren't needed today, but knowing they exist matters.
noexcept(expr) evaluates to true if the compiler can guarantee that expr won't throw an exception, and false otherwise. The expression isn't actually evaluated, only inspected.std::size_t. alignof(double) is typically 8 on 64-bit systems. Useful for low-level memory layout work.dynamic_cast for safe downcasting and reflection-style code.That's everything. Once these names appear once, the chapters that go deeper fill in detail rather than introduce new concepts.
When an expression mixes several operators, the compiler has to decide what runs first. Precedence is the ranking. Associativity is the tie-breaker when two operators with the same precedence sit next to each other.
The full table from the C++ standard has 17 levels, from tightest binding to loosest:
| Rank | Operators | Description | Associativity |
|---|---|---|---|
| 1 | :: | Scope resolution | Left to right |
| 2 | a++ a-- T() T{} f() a[] . -> | Postfix, member access, function call, subscript | Left to right |
| 3 | ++a --a +a -a ! ~ *p &x sizeof new delete (T)x | Unary, prefix, casts | Right to left |
| 4 | .* ->* | Pointer-to-member | Left to right |
| 5 | * / % | Multiplicative | Left to right |
| 6 | + - | Additive | Left to right |
| 7 | << >> | Bitwise shift | Left to right |
| 8 | <=> | Three-way comparison (C++20) | Left to right |
| 9 | < <= > >= | Relational | Left to right |
| 10 | == != | Equality | Left to right |
| 11 | & | Bitwise AND | Left to right |
| 12 | ^ | Bitwise XOR | Left to right |
| 13 | | | Bitwise OR | Left to right |
| 14 | && | Logical AND | Left to right |
| 15 | || | Logical OR | Left to right |
| 16 | ?: throw = += -= ... | Conditional, throw, assignment | Right to left |
| 17 | , | Comma | Left to right |
This table doesn't need to be memorised. What does help is a feel for the broad order of operations:
:: and member access (. ->) bind tightest.-x, !ok, *ptr, &x, sizeof) come next.Most precedence bugs come from a handful of common surprises:
Multiplication beats addition. Same as ordinary math. 2 + 3 * 4 is 14, not 20.
Comparison beats bitwise. a & 1 == 0 does not check whether the low bit of a is zero. Because == binds tighter than &, the expression parses as a & (1 == 0), which is a & 0, which is always 0. To get the intended meaning, write (a & 1) == 0.
The wrong value is 0 (false) for every orderNumber. The fix is parentheses around the & expression.
***p++ parses unexpectedly.** Postfix ++ binds tighter than the unary *, so *p++ parses as *(p++). That means "dereference p, then post-increment p to point at the next element." It's a common idiom for reading through an array. To pre-increment then dereference, write *++p. To dereference then increment what's pointed at, write (*p)++.
This is a source of confusion for people coming from other languages. Read *p++ carefully every time it appears, and add parentheses if there's any chance of confusion. After working through pointers, this idiom is clearer.
Logical AND binds tighter than logical OR. a || b && c parses as a || (b && c), not (a || b) && c. This usually does what's intended, but add parentheses when in doubt.
The expression parses as inStock || (isPremium && hasCoupon). isPremium && hasCoupon is true, so the whole thing is true. For (inStock || isPremium) && hasCoupon, the parentheses are required.
Assignment is right-associative. a = b = c parses as a = (b = c), so both a and b end up with the value of c. This is rarely useful but occasionally surprising. The Arithmetic & Assignment lesson covers it.
The precedence table rarely sits open during C++ work. The easier rule: add parentheses whenever the order of operations isn't obvious. They cost nothing, they make intent explicit, and they save the next person reading the code from looking the table up.
That version reads cleanly without needing to remember whether && binds tighter than ||. The compiler doesn't care, but the next reader will.
To wrap the map up, a small program that touches several operator families: scope resolution, member access, arithmetic, comparison, logical, conditional, and assignment.
This short program uses :: (in std::cout), . (member access on item), * (multiplication), >= (comparison), ?: (the ternary), + (addition), = (assignment), and << (stream insertion). That's seven operator families on twelve lines of real code.
The flowchart shows the order operations run in: multiply first, compare next, pick a shipping value with the ternary, add to get the total, print everything. The compiler enforces this order using precedence and the explicit sequence of statements.
10 quizzes