C++ Pointers & References

Raw Pointers

Address-of and dereference
int x = 42;
int* ptr = &x;     // ptr holds the address of x

std::cout << ptr;   // 0x7ffd1234abcd (address)
std::cout << *ptr;  // 42 (dereferenced value)

*ptr = 100;
std::cout << x;     // 100 — x was modified through ptr

&x gets the address. *ptr dereferences to get the value. Pointers can be null, reassigned, and used for pointer arithmetic.

Null Pointers

nullptr replaces NULL in modern C++
int* ptr = nullptr;  // modern C++ — type-safe null

if (ptr != nullptr) {
    std::cout << *ptr;  // safe — only dereference if not null
}

// Common pattern: check before use
void process(int* data) {
    if (!data) return;  // guard clause
    std::cout << *data << std::endl;
}

Dereferencing a null pointer is undefined behavior. Always check before dereferencing.

References

References vs pointers
int x = 42;

// Reference — must be initialized, cannot be null, cannot be reseated
int& ref = x;
ref = 100;
std::cout << x;  // 100

// Pointer — can be null, can be reseated
int* ptr = &x;
int y = 99;
ptr = &y;  // now points to y

// Function parameters
void by_value(int n) { n = 0; }    // copies, original unchanged
void by_ref(int& n) { n = 0; }    // modifies original
void by_ptr(int* n) { *n = 0; }   // modifies through pointer
void by_cref(const int& n) { }     // read-only reference (no copy)

Pass by const& for read-only access to large objects (avoids copying). Pass by & when the function needs to modify the argument. Pass by value for small types (int, char, bool).

Pointer Arithmetic

Array traversal with pointers
int arr[] = {10, 20, 30, 40, 50};
int* p = arr;  // arrays decay to pointers

std::cout << *p;       // 10
std::cout << *(p + 2); // 30
p++;
std::cout << *p;       // 20

// Iterate with pointers
for (int* it = arr; it != arr + 5; ++it) {
    std::cout << *it << " ";
}

p + n advances by n * sizeof(type) bytes. This is how C-style arrays work under the hood.

Dynamic Memory

new/delete — manual heap allocation
// Single object
int* p = new int(42);
delete p;

// Array
int* arr = new int[100];
delete[] arr;  // delete[] for arrays, not delete

// The problem: forgetting delete = memory leak
// The solution: smart pointers (see memory management)

Raw new/delete should be avoided in modern C++. Use smart pointers or containers instead.

void Pointers

Generic pointer type
void* generic = &x;

// Must cast to use
int* typed = static_cast<int*>(generic);
std::cout << *typed;

// Common in C APIs
void callback(void* user_data) {
    auto* config = static_cast<Config*>(user_data);
    // use config...
}

void* cannot be dereferenced directly. Cast it to the correct type first. Prefer templates in C++ — they provide type safety.