References in C++: Syntax, Aliasing, and Runnable Examples

Build a reliable mental model of C++ references through state traces, parameter examples, const binding, pointer comparisons, and common error repairs.

KnowledgeGate Team

Exam prep & CS education

Updated 23 Aug 20266 min read

You have seen & in C++, but does it mean an address, a reference declaration, or pass by reference? All three, depending on where the symbol sits. A reference is a second name for an object that already exists: int& score = marks; does not copy marks, it renames it, and a write through either name lands in the same integer. Get that one idea straight and reference output questions stop being guesswork.

References in C++ are aliases, not copied variables

The declaration T& name = object; creates an lvalue reference. The reference is another name, or alias, for an existing object. It must be initialised when declared, has no normal empty state, and cannot later be reseated to another object.

Do not assume that every reference occupies a separate memory location. That detail depends on the implementation and is not part of the aliasing model.

The symbol & has two beginner-facing jobs. In int& score = marks, it is part of the reference type. In &marks, it is the address-of operator. Read the position first: a & attached to a type declares a reference, while a & in front of a name inside an expression takes that name's address. Pointers reuse the symbol the same way, and the Coding & DSA learning path works through them in order.

Keep one invariant in mind: reading either name reads the same object, and assigning through either name changes that one object.

C++ reference syntax with one complete state trace

Run this program:

#include <iostream>

int main() {
    int marks = 72;
    int& score = marks;

    std::cout << std::boolalpha << (&marks == &score) << '\n';
    score += 8;
    std::cout << marks << ' ' << score << '\n';
    marks *= 2;
    std::cout << marks << ' ' << score << '\n';
}

At first, both names read 72. The comparison &marks == &score is true because both names identify the same integer. Next, score += 8 changes the shared object from 72 to 80. Then marks *= 2 changes that same object from 80 to 160.

The exact output is:

true
80 80
160 160

score never received a copy of 72. It remained an alias through both updates.

Alias diagram: marks and score name one integer cell as its value goes 72, then 80, then 160.

Pass by reference changes the caller's object

A reference parameter makes the function's parameter an alias for the caller's object:

#include <iostream>

void swapValues(int& left, int& right) {
    int temp = left;
    left = right;
    right = temp;
}

int main() {
    int a = 12;
    int b = 35;
    swapValues(a, b);
    std::cout << a << ' ' << b << '\n';
}

Before the call, a = 12 and b = 35. During the call, left aliases a, while right aliases b. temp becomes 12; left = right makes a = 35; and right = temp makes b = 12. The program therefore prints 35 12.

Compare three signatures. void change(int value) works on a copy. void change(int& value) can modify the caller. void inspect(const int& value) avoids a copy and forbids mutation through value. For a type as small as int, that avoided copy saves nothing measurable, because an int already fits in a machine word. Pick the form that states whether the function may change the caller's object; the copying question only starts to pay for itself on larger types.

Const references can observe objects and bind to temporaries

Consider int total = 25; const int& view = total; total = 31;. Printing view now produces 31. The const qualifier blocks writes through view, but it does not freeze total against changes made through its original name.

A const lvalue reference can also bind to a temporary. const int& answer = 6 * 7; is valid and prints 42. With this local direct binding, the temporary's lifetime is extended to the lifetime of answer. By contrast, int& bad = 6 * 7; is a compile-time error because a non-const lvalue reference cannot bind to that temporary.

Use const T& when a function should read an existing object without modifying it, especially for larger objects. Use T& when caller-visible mutation is intended.

Lvalue references and rvalue references serve different jobs

The earlier int& declarations are lvalue references. They normally bind to named, persistent objects. A T&& declaration is an rvalue reference, commonly used with temporary objects and as a foundation for move semantics.

#include <iostream>
#include <string>

int main() {
    std::string&& label = std::string("KG") + " AI";
    std::cout << label << '\n';
}

This program prints KG AI. The directly bound temporary lives for the local lifetime of label.

One detail catches people out: although label has type std::string&&, the named expression label is an lvalue when you use it again, so passing label onward selects an lvalue overload. std::move(label) casts it back to an rvalue, which is how its buffer gets taken over rather than copied.

References versus pointers: assignment is the decisive trap

Both references and pointers can provide indirect access, but they behave differently:

Question

Reference

Pointer

Declaration

int& ref = x

int* ptr = &x

Access the integer

ref

*ptr

Normal empty state

None for a valid reference

nullptr

Can it be reseated?

No

Yes

Now start with int x = 10; int y = 20; int& ref = x; ref = y;. The last statement copies the value of y into x. It does not make ref alias y. Afterwards, x = 20, y = 20, and &ref == &x remains true.

Continue from that state with int* ptr = &x; ptr = &y; *ptr = 30;. Pointer assignment changes which object ptr points to, and dereferencing it changes y. The final state is x = 20, y = 30, and ptr == &y is true.

Reference-versus-pointer diagram: ref stays bound to x while a pointer is reseated from x to y.

Common C++ reference errors and how coding tests expose them

Three failures are worth recognising immediately:

  • int& unbound; fails because a reference needs an initialiser.

  • int& bad = 10; fails because a non-const lvalue reference cannot bind to that temporary. const int& good = 10; is valid.

  • Returning a reference to int local = 5; leaves a dangling reference after the function returns. Using it is undefined behaviour, so return the int by value instead.

Now solve three short state checks.

  1. int a = 4, b = 9; int& r = a; r += b; b = r - 3; first makes a and r equal to 13, then makes b = 10. The final state is a = 13, b = 10, r = 13.

  2. int x = 5; const int& cr = x; x *= 3; changes the object to 15, so cr reads 15.

  3. void scale(int& n, int factor) { n *= factor; }, called with n = 6 and factor = 4, leaves the caller's n = 24.

Typical coding tests ask you to predict aliasing output, identify an invalid binding, choose between value and reference parameters, or spot a dangling return. C has no reference type at all, so there the same swap has to be written with pointers, traced line by line in Functions in C: Call by Value vs Pointers. Once the thing being passed is a whole container rather than one int, const T& is the parameter form that stops a copy on every call, which is worth carrying into Stacks and Queues: LIFO vs FIFO. For a broader sequence of structured problems, Coding for Placements is the next practice option.

C++ references: the short version and next step

Keep these five rules:

  1. Initialise every reference when you declare it.

  2. Remember that a reference is an alias, not a copied variable.

  3. Read ref = other as value assignment, not reseating.

  4. Prefer const T& for read-only access to larger objects.

  5. Never return a reference to a local variable.

For one final check, write void addBonus(int& score, int bonus). Start with score = 40 and bonus = 7; the caller's score must become 47. Before running it, explain which object the parameter score aliases.

If you want the rest of the language in the same order, with the same predict-the-output practice, the C++ Programming Course carries the sequence on from references.