Pointers underlie arrays, polymorphism, data structures and every C API, and you must read them fluently even if you rarely own memory with them.
A pointer holds the address of an object. int* p = &x; stores the address of x; *p dereferences it to reach x. For a pointer to a struct, p->member is shorthand for (*p).member.
Unlike references, pointers can be null (nullptr, never NULL or 0 in modern code), can be reassigned to point elsewhere, and need explicit dereferencing. Dereferencing a null or dangling pointer is undefined behaviour, so check for nullptr wherever null is a valid state.
Pointers and arrays are closely tied. An array name decays to a pointer to its first element, and pointer arithmetic moves by whole elements: p + 2 points two ints further, and p[i] means *(p + i). Arithmetic is only valid within one array (plus one past the end); going beyond is undefined.
const applies as usual, read right to left: const T* can't modify the pointee, T* const can't be repointed.
You can allocate with new and must then delete (or new[] with delete[]). Doing this by hand leaks memory on early returns and exceptions and invites double deletes. In modern C++ raw pointers don't own: use containers and smart pointers for ownership, and use raw pointers (or references) only to observe objects someone else owns.
So when should a function take T* instead of T&? When "no object" is a valid argument. Otherwise a reference states the intent better. Raw pointers remain essential for linked structures that point to optional neighbours, for C APIs, and for low-level code.
Example
#include <iostream>
struct Node { int value; Node* next; }; // can be null, can be repointed
int main() {
int x = 10;
int* p = &x; // p holds the address of x
*p = 20; // write through the pointer
std::cout << x << ' ' << (p != nullptr) << '\n'; // 20 1
int arr[] = {1, 2, 3, 4};
int* it = arr; // array decays to a pointer to its first element
it += 2; // pointer arithmetic: now at arr[2]
std::cout << *it << ' ' << it[1] << '\n'; // 3 4
const int* read_only = &x; // can't modify *read_only, can repoint
int* const fixed = &x; // can modify *fixed, can't repoint
*fixed = 30;
std::cout << *read_only << '\n'; // 30
Node c{3, nullptr}, b{2, &c}, a{1, &b};
for (Node* n = &a; n != nullptr; n = n->next) // n->value is (*n).value
std::cout << n->value << ' '; // 1 2 3
std::cout << '\n';
}Remember
&xtakes an address,*pdereferences,p->maccesses a member- Use
nullptr; dereferencing null or a dangling pointer is undefined behaviour - Raw pointers observe; containers and smart pointers own
Watch out for
- Using
new/deleteby hand in application code, leaking on early return or exception.