Pointers — The Core of C++
& and * operators, how to declare and use a pointer, how arrays and pointers relate, pointer arithmetic, nullptr, and why pointers are the single most important idea separating C++ from languages like Python or JavaScript.
What Is a Memory Address?
Every variable in your program lives somewhere in your computer's memory (RAM). That "somewhere" is a numeric address — think of your computer's memory as one enormous street of numbered mailboxes, and every variable you declare gets assigned to one of those mailboxes.
In Python or JavaScript, this address is completely hidden from you. In C++, you can see it, hold onto it, and pass it around — and that ability is the foundation of everything in this lesson.
// address_of.cpp #include <iostream> using namespace std; int main() { int age = 25; cout << "Value of age: " << age << endl; cout << "Address of age: " << &age << endl; return 0; }
Running this prints something like Address of age: 0x7ffee2a3b4ac — a hexadecimal number representing where age lives in memory. That exact number will be different every time you run the program, and that's expected.
The Address-Of Operator &
Placing & directly before a variable name gives you that variable's address instead of its value. You just saw this with &age above. This is the first of the two operators you must master to understand pointers.
&age out loud as "the address of age." It doesn't change age at all — it just tells you where age lives in memory.Pointer Declaration & The Dereference Operator *
A pointer is a variable whose job is to store a memory address — usually the address of another variable. You declare one by writing the type it points to, an asterisk *, and a name.
// pointer_basics.cpp #include <iostream> using namespace std; int main() { int age = 25; int* agePtr = &age; // agePtr now holds the address of age cout << "Value of age: " << age << endl; cout << "Address stored in agePtr: " << agePtr << endl; cout << "Value pointed to by agePtr: " << *agePtr << endl; *agePtr = 30; // change age THROUGH the pointer cout << "New value of age: " << age << endl; return 0; }
Notice that * is used in two different ways here — in the declaration int* agePtr, it means "this variable is a pointer to an int." In an expression like *agePtr, it means "dereference — give me the value stored at the address this pointer holds." Same symbol, different meaning depending on context.
*agePtr = 30;, age itself becomes 30 — because agePtr and age refer to the exact same memory location. This is fundamentally different from copying a value.The 3-Step Mental Model
Every time you work with a pointer, you're really doing three distinct steps. Internalize this pattern and pointers stop feeling mysterious.
int* p; — p is a variable that will hold the memory address of an int. Right now it doesn't point anywhere safe yet.p = &age; — now p stores the address of age. p and age are connected.*p — reads or writes the value living at that address. Reading gives you age's value; assigning to *p changes age itself.Pointers and Arrays
Back in Lesson 6 you learned that arrays "decay" into pointers when used in most expressions. Here's what that actually means: an array's name, on its own, evaluates to the address of its first element. That's why you can assign an array directly to a pointer.
// array_decay.cpp #include <iostream> using namespace std; int main() { int nums[4] = {10, 20, 30, 40}; int* p = nums; // array decays to a pointer at its first element for (int i = 0; i < 4; i++) { cout << "nums[" << i << "] = " << *(p + i) << endl; } p++; // moves forward by sizeof(int), not by 1 byte cout << "After p++, *p = " << *p << endl; return 0; }
The expression *(p + i) is exactly equivalent to nums[i] — in fact, that's literally how the compiler implements array indexing under the hood. Square-bracket indexing is really just pointer arithmetic wearing a friendlier syntax.
Pointer Arithmetic
When you add 1 to a pointer, C++ doesn't move it forward by 1 byte — it moves it forward by the size of the type it points to. An int* advances by sizeof(int) bytes (commonly 4), a double* advances by sizeof(double) bytes (commonly 8), and so on. This is exactly why p++ in the example above correctly landed on nums[1] instead of some random byte in the middle of an int.
p + i automatically calculates the correct byte offset to land exactly on element i — you never have to compute byte offsets yourself.Null Pointers (nullptr)
A pointer that isn't pointing at anything valid yet should be set to nullptr — a special value meaning "this pointer intentionally points to nothing." Checking for nullptr before dereferencing a pointer is a habit that prevents an entire category of crashes.
// nullptr_check.cpp #include <iostream> using namespace std; int main() { int* ptr = nullptr; // ptr points to nothing (yet) if (ptr == nullptr) { cout << "ptr is not pointing to anything" << endl; } int value = 99; ptr = &value; // now it's safe to dereference if (ptr != nullptr) { cout << "*ptr = " << *ptr << endl; } return 0; }
*ptr while ptr is nullptr is one of the most common C++ crashes, called a null pointer dereference. Always initialize pointers to nullptr when you don't yet have a valid address for them, and check before dereferencing if there's any doubt.Why Pointers Matter
It's fair to ask: why does C++ make you deal with all this, when Python and JavaScript hide it completely? Here's the payoff.
// pass_by_pointer.cpp #include <iostream> using namespace std; void doubleValue(int* n) { *n = *n * 2; } int main() { int x = 21; doubleValue(&x); cout << "x is now: " << x << endl; return 0; }
Lesson Summary
Let's recap everything you learned in this lesson:
& gives you that address.* dereferences it to reach the value.*(p + i) equals arr[i].nullptr, and never dereference one that might be null.int* p = &age;, what does p actually store?Now it's your turn to write real code. Complete the challenge below in your own C++ environment.
Write a C++ program called
swap.cpp containing a function void swapValues(int* a, int* b) that swaps the values of two integers using pointers only — no return value, no third array. Call it from main() on two variables and print their values before and after the swap.
Rules: Use a temporary variable inside the function to hold one value during the swap. Pass the addresses of your two variables using
& when calling the function. Confirm the swap actually worked by printing both variables after the call.
💡 Show hints if you're stuck
- Inside the function:
int temp = *a; *a = *b; *b = temp; - Call it like
swapValues(&x, &y);from main() - Because you passed addresses, the changes inside the function affect the original x and y
- Print x and y both before and after the call to see the difference