C++ Cheatsheet

References and Pointers

Use this C++ reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

References

A reference is an alias — another name for an existing object. It cannot be null, cannot be reseated, and must be initialized.

int x = 10;
int& ref = x;      // ref is an alias for x
ref = 20;          // x is now 20
int* p = &ref;     // p points to x (ref and x are the same object)

// const reference — read-only alias (extends lifetime of temporaries)
const int& cr = x;
const int& ct = 42;  // binds to temporary; lifetime extended to scope of ct

// lvalue reference  — binds to named objects
// const lvalue ref — binds to anything (lvalue or rvalue)
void f(const std::string& s);   // most common: pass large objects cheaply

// rvalue reference (C++11) — binds to temporaries (enables move semantics)
int&& rr = 42;
std::string&& sr = std::string("temp");

Pointers — Fundamentals

int x = 5;
int* p = &x;      // p holds the address of x
int y = *p;       // dereference: y == 5
*p = 99;          // x is now 99

// Null pointer
int* np = nullptr;      // C++11 null pointer constant (prefer over NULL or 0)
if (np != nullptr) { /* safe to dereference */ }
if (np) { }            // equivalent check

// Pointer arithmetic (valid only on arrays)
int arr[5] = {10, 20, 30, 40, 50};
int* pa = arr;         // arr decays to &arr[0]
*(pa + 2) == 30;       // true
pa++;                  // pa now points to arr[1]
ptrdiff_t diff = pa - arr;  // 1 (distance in elements)

Pointer Declarations — Reading * and const

Read right-to-left, treating * as "pointer to":

int* p;              // p is a pointer to int
const int* cp;       // cp is a pointer to const int (can't modify *cp)
int* const pc = &x; // pc is a const pointer to int (can't change pc itself)
const int* const cpc = &x; // const pointer to const int

// Mnemonic: const left of * → data is const; const right of * → pointer is const

int n = 0;
const int* ptr1 = &n;   // *ptr1 is read-only; ptr1 can point elsewhere
int* const ptr2 = &n;   // ptr2 is fixed; *ptr2 is writable

Pointers to Pointers

int x = 5;
int* p = &x;
int** pp = &p;         // pointer to pointer to int

**pp == 5;             // double dereference
*pp = nullptr;         // sets p to nullptr through pp

Arrays and Pointers

int arr[4] = {1, 2, 3, 4};
int* p = arr;            // arr decays to &arr[0]; sizeof(p) != sizeof(arr)

p[2] == *(p + 2);        // true — both are 3; subscript IS pointer arithmetic

// Pointer to whole array (preserves size info)
int (*pa)[4] = &arr;     // pa is pointer to array of 4 ints
(*pa)[0] == 1;

// Do NOT return a pointer/reference to a local array
int* bad() {
    int local[4] = {1, 2, 3, 4};
    return local;   // UB: local destroyed on return
}

References vs. Pointers

FeatureReferencePointer
Can be nullNoYes (nullptr)
Must be initializedYesNo (but dangerous)
Can be reseatedNoYes
Syntax to accessdirect (r.m)dereference ((*p).m or p->m)
ArithmeticNoYes
Use in optional / nullableNoYes
Use as function paramPreferWhen null is meaningful or re-seating needed

Member Access via Pointer

struct Point { int x, y; };
Point pt{3, 4};
Point* pp = &pt;

pp->x == 3;          // arrow: equivalent to (*pp).x
(*pp).y == 4;

void* — Type-Erased Pointer

void* vp = &x;               // can point to any object
int* ip = static_cast<int*>(vp);   // must cast to use
// Cannot dereference or do arithmetic on void*

Function Pointers (see also Functions)

int (*fp)(int, int);          // pointer to function returning int, taking 2 ints
fp = add;
fp(2, 3);                     // 5

// Member function pointer
struct Foo { int bar(int x); };
int (Foo::*mfp)(int) = &Foo::bar;
Foo obj;
(obj.*mfp)(5);                // call through member function pointer

Move Semantics and Rvalue References

#include <utility>

// std::move — cast to rvalue reference (signals "I no longer need this")
std::string a = "hello";
std::string b = std::move(a);  // b takes a's buffer; a is valid but unspecified

// Typical use: avoid copying in constructors and assignments
class MyVec {
    std::vector<int> data_;
public:
    MyVec(std::vector<int> data) : data_(std::move(data)) {}  // move from param
};

// Perfect forwarding — forward as lvalue or rvalue as received
template<typename T>
void wrapper(T&& arg) {
    target(std::forward<T>(arg));   // forwards rvalue as rvalue, lvalue as lvalue
}

std::move vs std::forward

std::movestd::forward<T>
Always casts to rvalue?YesNo — preserves value category
Use inmove constructors, move assignments, passing to take ownershipforwarding/wrapper templates

Raw Memory and new / delete

// Dynamic allocation (prefer smart pointers)
int* p = new int(42);        // allocate + initialize
delete p;                    // free; p is dangling after this
p = nullptr;                 // good habit

int* arr = new int[10]{};    // array, zero-initialized
delete[] arr;                // must use delete[] for arrays

// Placement new — construct in pre-allocated buffer
alignas(int) char buf[sizeof(int)];
int* pp = new (buf) int(7);  // no allocation; constructs in buf
using T = int;
pp->~T();                    // manual destructor call (keywords like int need an alias)
// or: std::destroy_at(pp);  // C++17, <memory>

std::span — Non-Owning View of Contiguous Data (C++20)

#include <span>

void process(std::span<int> data) {
    for (int& x : data) x *= 2;
    data[0];                          // subscript
    data.size();                      // element count
    data.subspan(1, 3);               // subview
    data.first(3); data.last(2);
}

std::vector<int> v = {1, 2, 3, 4};
process(v);                          // implicit conversion
int arr[4] = {1, 2, 3, 4};
process(arr);                        // works for raw arrays too

// Fixed-size span
std::span<int, 4> fixed = arr;

Common Pointer Bugs

// Dangling pointer — points to freed/out-of-scope memory
int* dangle() {
    int x = 5;
    return &x;   // UB: x destroyed here
}

// Double free
int* p = new int(1);
delete p;
delete p;   // UB

// Memory leak — allocated but never deleted
void leak() {
    int* p = new int[100];
    // forgot delete[] p;
}

// Use-after-move
std::string s = "hello";
auto t = std::move(s);
s.size();   // technically valid (unspecified state) but logically wrong

// Buffer overrun
int arr[5];
arr[5] = 0;   // UB

Prefer smart pointers (unique_ptr, shared_ptr) over raw new/delete. Use std::span instead of raw pointer + length pairs.