C++ Operators


C++ Operators Interview with follow-up questions

1. What are the different types of operators available in C++?

C++ provides a rich set of operators grouped by category:

1. Arithmetic operators +, -, *, /, % — standard math operations. Integer division truncates toward zero; % is the remainder (result sign follows the dividend in C++11+).

2. Relational (comparison) operators ==, !=, <, >, <=, >= — return bool.

C++20 adds the three-way comparison (spaceship) operator <=>, which returns a comparison category (std::strong_ordering, std::weak_ordering, or std::partial_ordering) and can auto-generate all six relational operators for a class.

3. Logical operators &&, ||, ! — operate on bool values with short-circuit evaluation. && stops at the first false; || stops at the first true.

4. Bitwise operators &, |, ^, ~, <<, >> — operate on individual bits of integer types. Commonly used in embedded programming, flags, and performance-sensitive code.

5. Assignment operators = (basic), and compound: +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=. Assignment returns an lvalue reference to the left operand, enabling chained assignment.

6. Increment and decrement operators ++ and --, in prefix (++i) and postfix (i++) forms. Prefix returns the updated value; postfix returns the old value (may create a temporary). Prefer prefix for iterators to avoid unnecessary copies.

7. Member access operators . (dot) for objects, -> (arrow) for pointers or smart pointers, [] for subscript access.

8. Pointer operators * (dereference), & (address-of). In modern C++ prefer smart pointers (unique_ptr, shared_ptr) over raw pointer manipulation.

9. Type operators

  • sizeof — size of a type or object in bytes (compile-time)
  • alignof — alignment requirement of a type (C++11)
  • typeid — runtime type information
  • Casts: static_cast, dynamic_cast, const_cast, reinterpret_cast

10. Other special operators

  • ? : (ternary/conditional)
  • , (comma — evaluates both operands, yields the right one; rarely needed)
  • :: (scope resolution)
  • new / delete — heap allocation/deallocation (prefer smart pointers)
  • noexcept operator — tests at compile time whether an expression can throw

11. User-defined operators (operator overloading) Almost all operators can be overloaded for custom types. The spaceship operator <=> (C++20) is especially powerful: define it once and the compiler generates <, >, <=, >=, ==, != automatically.

Common interview point: Know operator precedence — especially that & has lower precedence than ==, which causes bugs like if (flags & MASK == 0) being parsed as if (flags & (MASK == 0)). Always parenthesize bitwise expressions in conditions.

↑ Back to top

Follow-up 1

Can you explain the difference between unary and binary operators?

Unary operators operate on a single operand, while binary operators operate on two operands. Unary operators include increment (++), decrement (--), logical negation (!), and more. Binary operators include addition (+), subtraction (-), multiplication (*), division (/), and more.

Follow-up 2

How does the ternary operator work in C++?

The ternary operator in C++ is a conditional operator that takes three operands. It is written in the form: condition ? expression1 : expression2. If the condition is true, the operator evaluates to expression1; otherwise, it evaluates to expression2.

Follow-up 3

Can you give an example of using the assignment operator in C++?

Certainly! Here's an example of using the assignment operator in C++:

int x = 5;
x += 3; // equivalent to x = x + 3

2. How does the precedence of operators work in C++?

Operator precedence determines which operator binds more tightly in an expression. Higher-precedence operators are evaluated first. When two operators have the same precedence, associativity (left-to-right or right-to-left) breaks the tie.

Abbreviated precedence table (highest to lowest — selected levels):

Level Operators Associativity
1 (highest) :: (scope resolution) left-to-right
2 (), [], ., ->, post-++/-- left-to-right
3 Pre-++/--, unary -/+, !, ~, * (deref), & (addr-of), sizeof, new, delete, casts right-to-left
5 *, /, % left-to-right
6 +, - left-to-right
7 <<, >> (shift) left-to-right
9 <, <=, >, >=, <=> left-to-right
10 ==, != left-to-right
11 & (bitwise AND) left-to-right
12 ^ (bitwise XOR) left-to-right
13 `\ ` (bitwise OR)
14 && (logical AND) left-to-right
15 `\ \
16 ? :, =, +=, -=, … right-to-left
17 (lowest) , (comma) left-to-right

Classic example:

int result = 2 + 3 * 4;   // 14, not 20 — * binds tighter than +

Common gotchas interviewers test:

// Bitwise AND has LOWER precedence than ==
if (flags & MASK == 0)  // parsed as: flags & (MASK == 0) — almost certainly wrong
if ((flags & MASK) == 0)  // correct

// Shift has lower precedence than +
std::cout << 1 + 2 << '\n';  // prints 3, then newline — parsed as (cout << (1+2)) << '\n'

// Ternary is right-associative
int x = a ? b : c ? d : e;  // parsed as: a ? b : (c ? d : e)

Best practice: When in doubt, use parentheses to make intent explicit. Do not rely on precedence rules for expressions mixing bitwise, arithmetic, and comparison operators — parenthesize them every time. This eliminates bugs and improves readability.

C++20 note: The spaceship operator <=> sits between shift and the relational operators (<, >) in the precedence table.

↑ Back to top

Follow-up 1

What happens when operators of the same precedence appear in an expression?

When operators of the same precedence appear in an expression, the associativity of the operators determines the order in which they are evaluated. Most operators in C++ have left-to-right associativity, which means they are evaluated from left to right. For example, in the expression 2 + 3 - 4, the addition and subtraction operators have the same precedence, but since they have left-to-right associativity, the addition is performed first, resulting in 5 - 4, which equals 1.

Follow-up 2

Can you give an example of an expression where operator precedence plays a crucial role?

Sure! Consider the expression 2 + 3 * 4. If the operators were evaluated from left to right without considering precedence, the result would be 10. However, due to the higher precedence of the multiplication operator, the expression is evaluated as 2 + (3 * 4), resulting in 2 + 12, which equals 14.

Follow-up 3

How can parentheses be used to change the precedence of operators in an expression?

Parentheses can be used to explicitly specify the order in which operators are evaluated in an expression, overriding the default precedence and associativity. For example, in the expression (2 + 3) * 4, the addition inside the parentheses is performed first, resulting in 5 * 4, which equals 20.

3. What is the purpose of the 'sizeof' operator in C++?

The sizeof operator returns the size in bytes of a type or object as a compile-time constant of type std::size_t. It is not a function — it is evaluated entirely at compile time and produces no runtime overhead.

Syntax:

sizeof(type)       // size of a type
sizeof expression  // size of the type of an expression (parentheses optional)

Examples:

#include 
int main() {
    std::cout << sizeof(int)    << '\n';  // typically 4
    std::cout << sizeof(double) << '\n';  // typically 8
    std::cout << sizeof(char)   << '\n';  // always 1 (by definition)

    int arr[10];
    std::cout << sizeof(arr)              << '\n';  // 40 (4 * 10)
    std::cout << sizeof(arr) / sizeof(arr[0]) << '\n';  // 10 — element count

    int* p = arr;
    std::cout << sizeof(p) << '\n';  // 8 on 64-bit — size of POINTER, not array!
}

Key points interviewers probe:

  • sizeof(char) is always 1 by the standard. All other sizes are implementation-defined (though int is almost universally 4 bytes on modern platforms).
  • sizeof on an array gives the total byte size of the array. sizeof on a pointer to the same array gives only the pointer size — a classic source of bugs when arrays decay to pointers.
  • sizeof does not evaluate its operand — there are no side effects: sizeof(i++) does not increment i.
  • sizeof returns std::size_t, an unsigned type. Mixing it with signed integers in arithmetic can cause subtle bugs.
  • For class types, sizeof includes padding bytes inserted by the compiler for alignment. Use offsetof (from ``) to inspect field layout.

Modern alternatives:

  • std::array stores its size as a template parameter — use arr.size() instead of sizeof(arr)/sizeof(arr[0]).
  • std::span (C++20) carries a size alongside a pointer, eliminating the need for sizeof arithmetic when passing arrays to functions.
  • alignof(T) (C++11) returns the alignment requirement of a type, complementing sizeof.
#include 
std::array arr{};
std::cout << arr.size() << '\n';  // 10 — always correct, no sizeof trickery needed
↑ Back to top

Follow-up 1

Can you give an example of using the 'sizeof' operator?

Sure! Here's an example:

#include 

int main() {
    int arr[5];
    std::cout << "Size of arr: " << sizeof(arr) << " bytes" << std::endl;
    return 0;
}

Output:

Size of arr: 20 bytes

Follow-up 2

Can 'sizeof' be used on user-defined types?

Yes, 'sizeof' can be used on user-defined types. It returns the size of the object or type in bytes.

Follow-up 3

What is the return type of the 'sizeof' operator?

The return type of the 'sizeof' operator is 'size_t', which is an unsigned integer type defined in the header.

4. What are bitwise operators in C++?

Bitwise operators work directly on the binary representation of integer values, operating bit by bit. They are fundamental in systems programming, embedded development, flag manipulation, and performance-sensitive code.

The six bitwise operators:

Operator Name Description
& AND Bit is 1 only if both corresponding bits are 1
`\ ` OR
^ XOR Bit is 1 if the corresponding bits differ
~ NOT Flips all bits (one's complement)
<< Left shift Shifts bits left; fills vacated bits with 0
>> Right shift Shifts bits right; fills with 0 (unsigned) or sign bit (signed, implementation-defined pre-C++20)

Examples:

unsigned int a = 0b1010;   // 10
unsigned int b = 0b1100;   // 12

a & b;   // 0b1000 = 8  (AND)
a | b;   // 0b1110 = 14 (OR)
a ^ b;   // 0b0110 = 6  (XOR)
~a;      // 0b...11110101 (NOT — depends on type width)
a << 1;  // 0b10100 = 20 (multiply by 2)
a >> 1;  // 0b0101  = 5  (divide by 2, truncated)

Common practical patterns:

// Set a bit
flags |= (1u << bitPos);

// Clear a bit
flags &= ~(1u << bitPos);

// Toggle a bit
flags ^= (1u << bitPos);

// Test a bit
bool isSet = (flags >> bitPos) & 1u;

// Check if a value is a power of two
bool isPow2 = n > 0 && (n & (n - 1)) == 0;

Important gotchas:

  • Bitwise operators on signed integers can invoke undefined behaviour (e.g., left-shifting into the sign bit). Always prefer unsigned types for bit manipulation.
  • ~ on a small unsigned type (e.g., uint8_t) produces an int due to integer promotion — mask the result: uint8_t result = ~x & 0xFF;.
  • C++20 defines arithmetic right shift for signed types as implementation-defined but standardises two's-complement representation for all signed integer types, removing previous ambiguity.
  • << and >> have lower precedence than + but higher than == — parenthesize when combining with comparisons: (x >> 2) == 3, not x >> 2 == 3.

C++20 addition — `` header:

C++20 introduces `` with portable bit-manipulation utilities:

#include 
std::popcount(0b10110u);       // 3 — count set bits
std::countl_zero(0b00100u);    // count leading zeros
std::has_single_bit(16u);      // true — power of two check
std::rotl(x, 3);               // rotate left by 3 bits

Prefer these standard functions over hand-rolled bit tricks in new code — they are clearer, portable, and compiler-optimised.

↑ Back to top

Follow-up 1

Can you explain the difference between bitwise AND and bitwise OR operators?

The bitwise AND (&) operator performs a bitwise AND operation between the corresponding bits of two integers. It returns a new integer with bits set to 1 only if both corresponding bits are 1. On the other hand, the bitwise OR (|) operator performs a bitwise OR operation between the corresponding bits of two integers. It returns a new integer with bits set to 1 if at least one of the corresponding bits is 1.

Here's an example to illustrate the difference:

int a = 5; // Binary: 0101
int b = 3; // Binary: 0011

int resultAnd = a & b; // Binary: 0001 (1 in decimal)
int resultOr = a | b; // Binary: 0111 (7 in decimal)

Follow-up 2

How does the bitwise NOT operator work?

The bitwise NOT (~) operator performs a bitwise NOT operation on an integer. It flips all the bits of the integer, i.e., it changes 0 to 1 and 1 to 0. This operator is a unary operator, meaning it operates on a single operand.

Here's an example to illustrate how the bitwise NOT operator works:

int a = 5; // Binary: 0101

int result = ~a; // Binary: 1010 (-6 in decimal)

Follow-up 3

Can you give an example of using bitwise shift operators?

Bitwise shift operators in C++ are used to shift the bits of an integer to the left or right by a specified number of positions. There are two types of bitwise shift operators: left shift (<<) and right shift (>>).

Here's an example to illustrate the usage of bitwise shift operators:

int a = 5; // Binary: 0101

int resultLeftShift = a &lt;&lt; 2; // Binary: 010100 (20 in decimal)
int resultRightShift = a &gt;&gt; 1; // Binary: 0010 (2 in decimal)

5. What are the special operators in C++?

"Special operators" in C++ refers to operators that don't fit neatly into arithmetic, relational, logical, or bitwise categories. The most commonly discussed in interviews:

1. Conditional (ternary) operator ? : The only ternary operator in C++. A compact if-else expression:

int max = (a &gt; b) ? a : b;

Both branches must have compatible types. Prefer it for simple expressions; avoid nesting it deeply.

2. Scope resolution operator :: Accesses names in a specific namespace or class, or disambiguates the global scope:

std::cout      // name 'cout' in namespace 'std'
MyClass::func  // static member of MyClass
::globalVar    // explicit global scope

3. sizeof and alignof Compile-time unary operators returning the byte size or alignment of a type/expression. Not function calls — they produce compile-time constants.

4. new and delete Allocate and deallocate heap memory:

int* p = new int{42};
delete p;

int* arr = new int[10]{};
delete[] arr;

In modern C++, prefer std::make_unique() and std::make_shared() over raw new. Direct use of new/delete is a code smell unless writing a custom allocator.

5. Type cast operators C++ provides four named casts replacing C-style (type):

  • static_cast — safe, checked at compile time (numeric conversions, up/downcasting in known hierarchies)
  • dynamic_cast — runtime-checked downcast in polymorphic hierarchies; returns nullptr (pointers) or throws std::bad_cast (references) on failure
  • const_cast — add or remove const/volatile only
  • reinterpret_cast — low-level bit reinterpretation; dangerous, platform-dependent

6. Pointer-to-member operators .* and -&gt;* Access a member specified by a pointer-to-member:

struct Foo { int x; };
int Foo::* mp = &amp;Foo::x;
Foo obj{5};
int val = obj.*mp;         // 5

Rarely used directly; common in callbacks and reflection frameworks.

7. noexcept operator (C++11) Tests at compile time whether an expression is declared not to throw:

static_assert(noexcept(std::swap(a, b)));

8. Three-way comparison &lt;=&gt; (C++20) The "spaceship operator" returns a comparison category and auto-generates all six relational operators when defaulted:

auto operator&lt;=&gt;(const MyClass&amp;) const = default;

9. co_await, co_yield, co_return (C++20) Coroutine operators that suspend and resume coroutine execution — a significant addition enabling async and generator patterns without threads.

Common interview follow-up: "When would you use dynamic_cast?" — Only when you have a pointer/reference to a base class and genuinely need to check at runtime which derived type it is. Overuse signals a design problem; prefer virtual dispatch instead.

↑ Back to top

Follow-up 1

What is the purpose of the scope resolution operator?

The scope resolution operator (::) is used to access global variables, functions, or static members of a class. It allows you to specify the scope in which a particular identifier is defined.

For example, if you have a global variable named 'x' and a local variable with the same name inside a function, you can use the scope resolution operator to access the global variable as follows:

int x = 10;

void foo() {
    int x = 20;
    cout &lt;&lt; ::x; // Accessing the global variable
}

Follow-up 2

How does the member selection operator work?

The member selection operator (.) is used to access members of an object or a structure. It is used when you have an instance of a class or a structure and you want to access its member variables or member functions.

For example, if you have a class named 'Person' with a member variable 'name', you can access it using the member selection operator as follows:

class Person {
public:
    string name;
};

Person p;
p.name = "John"; // Accessing the member variable

Follow-up 3

Can you give an example of using the new and delete operators?

Yes, the new and delete operators are used for dynamic memory allocation and deallocation in C++.

The new operator is used to allocate memory for an object or an array, and it returns a pointer to the allocated memory. Here's an example:

int* p = new int; // Allocating memory for an integer
*p = 10;

delete p; // Deallocating memory

The delete operator is used to deallocate memory that was previously allocated with the new operator. It frees the memory and makes it available for reuse.

Note: It's important to always deallocate memory that was allocated with the new operator to avoid memory leaks.

Live mock interview

Mock interview: C++ Operators

Intermediate ~5 min Your own free AI key

Your voice and your AI key never touch our servers; the key stays in this browser and is sent only to Google. Only your round scores are saved to track progress.