AlgoMaster Logo

Operators

High Priority18 min readUpdated June 6, 2026
Listen to this chapter
Unlock Audio

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.

How to Read This Lesson

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++).

What Counts as an Operator

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:

ArityOperandsExamples
Unary1-x, !ok, *ptr, &value, sizeof(int)
Binary2a + b, price < 100, cart = items
Ternary3inStock ? "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 Operator Map

The families in C++, grouped by what they do:

FamilyOperatorsWhat 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 / Alignofsizeof alignofAsk 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 caststatic_cast dynamic_cast const_cast reinterpret_cast (T)x T(x)Convert one type to another
Allocationnew new[] delete delete[]Allocate and free heap memory
IdentificationtypeidLook up runtime type information
Exceptionthrow noexceptRaise 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.

Scope Resolution: ::

:: 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.

Member Access: . 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.

Address-of & 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.

The sizeof Operator

sizeof 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 Operator

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 Ternary (Conditional) Operator ?:

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."

Bitwise Operators

The bitwise operators work on the individual bits of an integer rather than on the integer's numeric value. There are six of them.

OperatorNameQuick example
&Bitwise AND0b1100 & 0b1010 is 0b1000 (8)
|Bitwise OR0b1100 | 0b1010 is 0b1110 (14)
^Bitwise XOR0b1100 ^ 0b1010 is 0b0110 (6)
~Bitwise NOT~0b00000101 is 0b11111010
<<Left shift1 << 3 is 8 (shifts bits left by 3)
>>Right shift16 >> 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.

Type Cast Operators

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.

CastUse
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.

Other Operators Worth Knowing the Names Of

A few more operators round out the map. They aren't needed today, but knowing they exist matters.

  • `noexcept` is both a specifier (on a function) and an operator. As an operator, 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.
  • `alignof` is a compile-time operator that returns the alignment requirement of a type, again as a std::size_t. alignof(double) is typically 8 on 64-bit systems. Useful for low-level memory layout work.
  • `typeid` returns runtime type information for an expression. It's used with dynamic_cast for safe downcasting and reflection-style code.
  • `new` and `delete` allocate and free objects on the heap. They're operators, not functions, even though they look like they ought to be functions.
  • `throw` raises an exception.

That's everything. Once these names appear once, the chapters that go deeper fill in detail rather than introduce new concepts.

Precedence and Associativity

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:

RankOperatorsDescriptionAssociativity
1::Scope resolutionLeft to right
2a++ a-- T() T{} f() a[] . ->Postfix, member access, function call, subscriptLeft to right
3++a --a +a -a ! ~ *p &x sizeof new delete (T)xUnary, prefix, castsRight to left
4.* ->*Pointer-to-memberLeft to right
5* / %MultiplicativeLeft to right
6+ -AdditiveLeft to right
7<< >>Bitwise shiftLeft to right
8<=>Three-way comparison (C++20)Left to right
9< <= > >=RelationalLeft to right
10== !=EqualityLeft to right
11&Bitwise ANDLeft to right
12^Bitwise XORLeft to right
13|Bitwise ORLeft to right
14&&Logical ANDLeft to right
15||Logical ORLeft to right
16?: throw = += -= ...Conditional, throw, assignmentRight to left
17,CommaLeft to right

This table doesn't need to be memorised. What does help is a feel for the broad order of operations:

  1. :: and member access (. ->) bind tightest.
  2. Unary operators (-x, !ok, *ptr, &x, sizeof) come next.
  3. Then arithmetic, then shifts, then comparisons, then bitwise, then logical.
  4. Assignment and the ternary are near the bottom.
  5. The comma operator is at the very bottom.

The Surprises

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.

When in Doubt, Add Parentheses

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.

A Tiny End-to-End Example

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.

Quiz

Operators Quiz

10 quizzes