C++

Operator Overloading & References

Two smaller but genuinely useful additions: operator overloading lets a custom class define what +, ==, or << mean for its own type, so objects can be used with the same natural syntax as an int. References are a second way to refer to an existing variable — related to pointers, but with real, useful restrictions that make a lot of code both safer and easier to read.

Quick reference
What it is
Operator overloading: defining what a built-in operator does for a custom type. A reference: an alias for an existing variable, declared with & after the type.
Mental model
Operator overloading is teaching + a new trick for your specific type. A reference is a second name taped onto the same box a variable already lives in — not a new box, and it can never be re-taped onto a different one.
Key numbers
A reference must be initialized at declaration and can never be reassigned to refer to something else — unlike a pointer, which can be reassigned or left null.
Reach for it when
Operator overloading: a type genuinely has a natural mathematical or comparison meaning (a vector, a complex number, a duration). References: passing a large object to a function without copying it, or without ever needing to check for null.

Operator overloading, in a worked example

class Vector2D {
public:
    Vector2D(float x, float y) : x(x), y(y) {}

    Vector2D operator+(const Vector2D &other) {   // defines what + means for two Vector2D objects
        return Vector2D(x + other.x, y + other.y);
    }

    float x, y;
};

Vector2D a(1, 2);
Vector2D b(3, 4);
Vector2D c = a + b;   // calls operator+ — c.x is 4, c.y is 6

Without overloading, adding two vectors would need an explicit function call like add(a, b) — overloading lets the natural + syntax work directly, which reads closer to the actual math it represents.

Overloading << for printing

The most common operator to overload is <<, letting std::cout print a custom type directly:

std::ostream& operator<<(std::ostream &out, const Vector2D &v) {
    out << "(" << v.x << ", " << v.y << ")";
    return out;
}

std::cout << c << std::endl;   // (4, 6) — printed directly, no manual field access

References

int x = 5;
int &ref = x;   // ref is a REFERENCE to x — another name for the exact same variable

ref = 10;        // this actually changes x
std::cout << x << std::endl;   // 10

ref isn't a copy of x and isn't a separate variable holding x's address — it is x, under a second name. This is exactly why passing a reference to a function achieves the same "modify the caller's variable" effect as passing a pointer in C's Pointers, without needing & at the call site or * to access the value:

void increment(int &x) {   // reference parameter
    x = x + 1;               // no * needed — x IS the caller's variable
}

int n = 5;
increment(n);        // no & needed at the call site either
std::cout << n << std::endl;   // 6

References vs. pointers

PointerReference
Can be nullYesNo — always refers to something real
Can be reassignedYes, to point elsewhereNo — bound permanently at declaration
Needs explicit syntax to useYes — *p to dereferenceNo — used exactly like the original variable
Must be initialized immediatelyNoYes — a reference cannot be declared and assigned later

In practice: reach for a reference whenever you know the thing being referred to genuinely exists and will never need to change to something else — a function parameter is the classic case. Reach for a pointer specifically when you need the flexibility a reference deliberately gives up: the ability to be null, or to be reassigned.

Real-world applications

Gotchas

Field notes