Chapter 7 of 23

Pointers and References

Understand pointers, address and dereference operators, pointer arithmetic, function pointers, references, and the key differences between them — with a swap worked example.

Meritshot10 min read
C++PointersReferencesMemoryPointer Arithmetic
All C++ Chapters

What Is a Pointer?

A pointer is a variable that stores the memory address of another variable. Every variable you declare occupies some location in RAM; a pointer lets you work with that location directly. This gives C++ its power and its reputation for both performance and danger.

Understanding pointers is non-negotiable for system programming, competitive programming, and cracking FAANG interviews. Questions on pointers, linked lists, and dynamic memory appear in virtually every C++ technical round at companies like Google India, Amazon, Microsoft, and Adobe.


The Address Operator & and Dereference Operator *

#include <iostream>
using namespace std;

int main() {
    int salary = 1500000;   // a normal int variable

    int* ptr = &salary;     // ptr holds the ADDRESS of salary

    cout << "Value of salary:   " << salary  << "\n"; // 1500000
    cout << "Address of salary: " << &salary << "\n"; // e.g. 0x7ffee4b3c
    cout << "ptr holds:         " << ptr     << "\n"; // same address
    cout << "Value via ptr:     " << *ptr    << "\n"; // 1500000 (dereference)

    *ptr = 1800000;          // modify salary through the pointer
    cout << "Updated salary:    " << salary  << "\n"; // 1800000

    return 0;
}
  • &variableaddress-of operator: gives the memory address of variable.
  • *ptrdereference operator: gives the value stored at the address in ptr.

Declaring Pointers

int*    iPtr;    // pointer to int
double* dPtr;    // pointer to double
char*   cPtr;    // pointer to char

The * is part of the variable declaration, not the type. It is good practice to initialise pointers immediately.


The Null Pointer

A null pointer points to nothing. Dereferencing it is undefined behaviour and will crash your program on most systems. Always check before dereferencing a pointer that could be null.

int* ptr = nullptr;   // C++11 nullptr (preferred)
// int* ptr = NULL;   // older C-style, still valid but avoid

if (ptr != nullptr) {
    cout << *ptr;     // safe
}

Use nullptr (not 0 or NULL) in modern C++.


Pointer Arithmetic

Because array elements are contiguous in memory, you can move through them by adding integers to a pointer. Adding 1 to a pointer advances it by sizeof(element type) bytes — not by 1 byte.

#include <iostream>
using namespace std;

int main() {
    int marks[4] = {55, 70, 85, 90};
    int* ptr = marks;  // points to marks[0]

    cout << *ptr       << "\n";   // 55
    cout << *(ptr + 1) << "\n";   // 70
    cout << *(ptr + 2) << "\n";   // 85

    ptr++;                        // advance to marks[1]
    cout << *ptr       << "\n";   // 70

    return 0;
}

The pointer difference (ptr2 - ptr1) gives the number of elements between them, not bytes.


Pointers to Arrays

When an array name is used in an expression, it decays to a pointer to its first element. Pointer arithmetic and array indexing are therefore interchangeable:

int arr[3] = {10, 20, 30};
int* p = arr;

// These are all equivalent:
cout << arr[1]    << "\n";  // 20
cout << *(arr+1)  << "\n";  // 20
cout << p[1]      << "\n";  // 20
cout << *(p+1)    << "\n";  // 20

Passing Arrays via Pointers

#include <iostream>
using namespace std;

void doubleAll(int* arr, int size) {
    for (int i = 0; i < size; i++) {
        arr[i] *= 2;
    }
}

int main() {
    int scores[5] = {10, 20, 30, 40, 50};
    doubleAll(scores, 5);
    for (int i = 0; i < 5; i++) {
        cout << scores[i] << " ";  // 20 40 60 80 100
    }
    return 0;
}

Pointers to Functions

A function pointer stores the address of a function and lets you call it indirectly — the foundation of callbacks and strategy patterns.

#include <iostream>
using namespace std;

int add(int a, int b) { return a + b; }
int mul(int a, int b) { return a * b; }

int main() {
    // Declare a pointer to a function taking two ints, returning int
    int (*op)(int, int);

    op = add;
    cout << op(3, 4) << "\n";  // 7

    op = mul;
    cout << op(3, 4) << "\n";  // 12

    return 0;
}

Function pointers look intimidating at first — reading the declaration inside-out helps: int (*op)(int, int) means "op is a pointer to a function that takes two ints and returns an int."


const with Pointers

The placement of const matters — it controls whether the pointer itself or the pointed-to value can change.

int val = 42;
int other = 99;

const int* ptr1 = &val;    // pointer to CONST int
// *ptr1 = 10;             // ERROR — cannot change the value
ptr1 = &other;             // OK   — can change where it points

int* const ptr2 = &val;    // CONST pointer to int
*ptr2 = 10;                // OK   — can change the value
// ptr2 = &other;          // ERROR — cannot change where it points

const int* const ptr3 = &val; // const pointer to const int
// Neither *ptr3 nor ptr3 can change

A helpful mnemonic: read the declaration right-to-left. "pointer to const int" vs "const pointer to int".


References

A reference is an alias — another name for an existing variable. Unlike a pointer, a reference cannot be null, cannot be reseated (made to refer to a different variable), and does not need explicit dereferencing.

#include <iostream>
using namespace std;

int main() {
    int salary = 1200000;
    int& ref = salary;    // ref is another name for salary

    ref = 1500000;        // modifies salary directly
    cout << salary << "\n"; // 1500000

    return 0;
}

References are most commonly used as function parameters to avoid copying large objects and to allow functions to modify their arguments.

void applyBonus(int& salary, int bonus) {
    salary += bonus;
}

int main() {
    int ctc = 1000000;
    applyBonus(ctc, 200000);
    cout << ctc << "\n";   // 1200000
    return 0;
}

const References

Passing by const reference is the idiomatic way to accept large objects cheaply without allowing modification:

void printName(const string& name) {
    cout << name << "\n";
    // name = "other";  // ERROR — const reference
}

Pointers vs References

FeaturePointerReference
Syntaxint* p = &x;int& r = x;
Can be nullYes (nullptr)No (must bind to a valid object)
Can be reseatedYes (p = &y;)No (always refers to initial object)
Needs dereferencingYes (*p)No (use directly)
Can point/refer to arraysYesNo (refers to a single object)
ArithmeticYes (p++)No
Use in dynamic memoryYes (new/delete)No
ReadabilityLowerHigher
Preferred for optional valuesYesNo
Preferred for function paramsSometimesUsually

Worked Example: Swapping Two Numbers

Swapping is a classic demonstration of why pass-by-reference matters.

Approach 1 — Using Pointers

#include <iostream>
using namespace std;

void swapPointers(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main() {
    int x = 500000, y = 750000;
    cout << "Before: x = " << x << ", y = " << y << "\n";
    swapPointers(&x, &y);
    cout << "After:  x = " << x << ", y = " << y << "\n";
    return 0;
}

Output:

Before: x = 500000, y = 750000
After:  x = 750000, y = 500000

Approach 2 — Using References

#include <iostream>
using namespace std;

void swapReferences(int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;
}

int main() {
    int x = 500000, y = 750000;
    cout << "Before: x = " << x << ", y = " << y << "\n";
    swapReferences(x, y);   // no & at the call site — cleaner!
    cout << "After:  x = " << x << ", y = " << y << "\n";
    return 0;
}

The reference version is cleaner at the call site. In practice, use std::swap from <algorithm> which is already optimised — but knowing how to implement it yourself is a standard interview expectation.


Common Pitfalls

  • Dereferencing a null or uninitialised pointer is undefined behaviour. Always initialise pointers to nullptr if they have no target yet.
  • Dangling pointers: A pointer that outlives the variable it points to becomes a dangling pointer. Accessing it is undefined behaviour.
    int* dangling;
    {
        int local = 42;
        dangling = &local;
    } // local is destroyed here
    // *dangling is now undefined behaviour
    
  • Forgetting & when calling a pointer-parameter function: swapPointers(x, y) will not compile — you must write swapPointers(&x, &y).
  • Confusing const int* and int* const: The position of const has dramatically different meanings. Write small test snippets until the rule is second nature.
  • References must be initialised: int& r; is a compile error. A reference must always be bound at declaration.
  • Pointer arithmetic on non-array pointers: Moving a pointer that does not point into an array (except to one-past-the-end) is undefined behaviour.

Practice Exercises

  1. Write a function findMax(int* arr, int size) that returns the maximum element of an array using pointer arithmetic (no square bracket indexing).
  2. Write a function reverseArray(int* arr, int size) that reverses the array in place using two pointers.
  3. Declare a const int* and an int* const. Write a short program showing which operations are allowed on each.
  4. Write a function countOccurrences(const string& str, char ch) that returns how many times ch appears in str, using a reference parameter.
  5. Implement a function pointer table: given an operator character '+', '-', '*', '/', look up the corresponding function and execute it on two integers.
  6. Write a program that creates two int variables representing two candidates' vote counts, swaps them using a pointer-based function, then swaps them back using a reference-based function, printing results after each swap.

Summary

  • A pointer stores the memory address of a variable; use & to get an address and * to dereference.
  • nullptr is the correct way to express a null pointer in modern C++; always check before dereferencing.
  • Pointer arithmetic moves through contiguous memory — advancing by 1 moves sizeof(type) bytes.
  • Arrays decay to pointers to their first element; subscript notation and pointer arithmetic are interchangeable.
  • Function pointers store the address of a function, enabling callbacks and runtime dispatch.
  • const int* — the value cannot change through the pointer; int* const — the pointer itself cannot be reseated.
  • A reference is an alias for an existing variable: it cannot be null, cannot be reseated, and needs no * syntax.
  • Prefer pass-by-reference over pass-by-pointer for function parameters when null is not a valid input.
  • The swap example shows how both approaches work, with references producing cleaner call-site syntax.