Pointer Basics and Dereferencing
You can find the memory address of a variable using the & operator. A pointer is a variable built to hold onto that address. Later you can use it indirectly to work with the data stored at that memory location, even when the variable name is no longer in scope.
Pointers are also the foundation for the more advanced ideas in C++.
Declaring a pointer
You declare a pointer by putting an asterisk between the type and the name:
double balance = 500.0;double* balancePtr = &balance;The asterisk here is part of the type. The type double* means “this variable can hold an address of a variable that’s a double.”
Here, balancePtr is initialized with &balance, so it holds the address of balance. You can print the pointer and &balance to see the same memory address:
In such a case, the pointer balancePtr is said to point to balance.
Just like variables of other types, if you don’t initialize a pointer, it contains a garbage value.
Dereferencing a pointer
Once you have a pointer holding an address, you can retrieve the actual value stored at that address. This is called dereferencing a pointer.
It reuses the same asterisk symbol, but in a different role:
When *balancePtr appears outside of a declaration, like on lines 7 and 9, it means "the value at the address stored in balancePtr." Here, the pointer is dereferenced for different purposes:
- Line 7 reads the value to which the pointer points.
- Line 9 assigns a value (
550.0) at the address held by the pointer instead. It doesn’t create a new variable or changebalancePtritself.
Where to put the asterisk
The space around * in a declaration is optional, so all of these mean the same thing:
double* balancePtr; // Space after *double *balancePtr; // Space before *double * balancePtr; // Space before and after *Most style guides pick double* (asterisk next to the type) because it reads closer to “this is a pointer-to-double variable.”
The asterisk only attaches to the variable name that comes after it. Which means the following line declares b as an int, not a pointer:
int* a, b; // a is a pointer to int, b is just a plain intIf you want two pointers on one line, you need the asterisk on both: int *a, *b;. Most people just declare one variable per line to keep things unambiguous.
Changing a caller’s variable
A function that takes a pointer can change the caller’s variable directly:
On line 9, &myBalance passes the address in. balance inside the function (lines 3–5) is a pointer holding that address, and it is dereferenced on line 4 to update myBalance itself.
A pointer that changes what it points to
A pointer can be reassigned after it’s created, so the same pointer variable can hold different addresses at different points in the program.
Here, higher is not initialized at declaration and contains some garbage value. Then it is assigned januarySales‘s address, and later reassigned februarySales’s address.
Pointer size and the case for its type
A pointer’s own size doesn’t depend on what it points to. Instead, it depends on the system, typically 8 bytes on a 64-bit system and 4 bytes on a 32-bit one.
Here, balancePtr and quantityPtr are different types but they both have the same size.
At this point, you might wonder why double* balancePtr needs to specify double at all, since an address is just an address, regardless of the type of data stored there. But the type lets the compiler infer two things it has no other way to know:
- How many bytes to read, starting at that address. One byte for a
char, four bytes for afloat, and so on. - How to interpret those bytes. For an
intand afloat, each typically 4 bytes on a system, the same 4 bytes can represent different values, depending on which type you assume.
Checkpoint
What does this code print?
int x = 10;
int y = 20;
int* p = &x;
*p = 99;
p = &y;
std::cout << x << " " << *p;What’s the typical size of this pointer on a 64-bit system?
char* letterPtr;