Pointers
In this section, we will cover the importance of pointers!
In this section, we are finally covering the topic of pointers! Pointers are everywhere, in some ways, everything boils down to being a pointer. Pointers are often thought of as one of the most challenging topics to cover in C++ but, in the right hands, they are not so complicated.
Pointers are a fundamental concept in programming, especially in languages like C and C++. They allow you to directly interact with memory locations. Here’s a simple explanation:
Memory Address: Every piece of data in a computer’s memory has a unique address. Think of it like a house number on a street. We may need to access that specific address at some point!
Vocab
Here are 2 important definitions for this section:
- Pointer: A pointer is a special type of variable that stores the memory address of another variable. It’s like writing down the address of a house on a piece of paper.
- Dereferencing: When you want to access the actual value at the memory address stored in a pointer, you “dereference” the pointer. This is like going to the house whose address you wrote down and seeing what’s inside.
Where would I see a pointer?
Pointers are just memory addresses!
- Arrays are just pointers which are just memory addresses.
- Strings are just an array of characters which are pointers which are just memory addresses.
- References are just pointers in disguise which are memory addresses as well.
So essentially:
- arrays == pointers
- strings == array of characters == arrays == pointer
- references == pointer
- everything == pointer
Before, we get into pointers, let’s review some things:
What even is a variable? And what does it hold?
4 important aspects of any variable:
- Type
- Name (identifier)
- Value
- Location (memory address)
int x; // where type: int, name: x, value: garbage, locaton: &x
Any fundamental type in C++ holds a garbage value until it’s initialized. This is not true for non-fundamental types such as string which are empty upon declaration.
Location is always some integer value! (ie, the memory address of the variable) If we need it we can access it via &
AKA the “address-of” operator
An example of a memory address might look something like 0x744c516a000
In a program, each variable holds a unique memory address!
Syntax
Here’s some syntax that you’ll see in the rest of the section:
- Pointers: Pointers are declared using an asterisk (*). They directly hold the memory address of another variable.
- References: References are declared using an ampersand (&). They are essentially aliases for existing variables.
Here’s an example:
int n = 7; // seems normal
int *ptr = &n; // a pointer to n's memory address
ptr
is the location of the address of n
and is of type int*
where *
represents a pointer.
In this example:
n
is 7&n
is n’s memory addressptr
points to the address ofn
(ptr and &n are equivalent)&ptr
is a different memory address*ptr
dereferences the pointer and is equal to the value ofn
(7).
Pointers are powerful because they allow you to manipulate data at a lower level, which can be very useful in certain situations. However, they can also be a bit tricky, so it’s important to be careful when using them to avoid errors in your code.
C Strings
Now let’s look at “strings”. In C, there is no string type. Instead, we have either char[]
or char*
. And believe it or not, in C and C++, arrays don’t exist either.
All of these c strings (strings of characters) are equivalent:
char msg[] = "hello";
char msg[] = { 'h', 'e', 'l', 'l', 'o', 0 };
char msg[] = { 'h', 'e', 'l', 'l', 'o', '\0' };
To check the length of a string, one could use the function:
int strlen(const char *str) {
int len = 0;
for(; *str; ++str)
++len;
return len;
}
When the function is called via:
strlen(msg);
The parameter str
in the function strlen()
gets a copy of the argument: pointer/memory address of msg
.
In the function itself, when we use *str
we are dereferencing it or getting its actual value at the address being pointed to.
By incrementing str
with ++str
we are increasing the value of the pointer/memory address str
by the size of on char. Meaning we move forward to the next item in the referenced string.
Why the hell would I want to use pointers
At this point, you might be wondering: When do you need to use pointers
- Whenever you are using arrays. (You have no choice)
- Rarely! (You should use
std::vector
instead of arrays most time) - Whenever you are using references. (They are just pointers)
So where, if anywhere, do pointers show up in the STL?
- Not too frequently, but you may find them useful occasionally!
Difference between references and pointers:
In C++, both references and pointers are used to work with memory addresses, but they have some important differences:
Nullability
Pointers: Pointers can be assigned a special value nullptr
to indicate that they are not pointing to any valid memory location.
NOTE: References must always be initialized and cannot be changed to refer to a different object after initialization.
Here’s the code example:
int* ptr = nullptr; // Valid
int& ref; // Error: References must be initialized
Dereferencing
Pointers: To access the value pointed to by a pointer, you use the dereference operator (*) like *ptr
.
References: References do not require dereferencing. They are used directly, similar to regular variables.
cout << *ptr << endl; // Access value pointed to by ptr
cout << ref << endl; // Access value through ref
Reassignment
Pointers: Pointers can be reassigned to point to different memory locations. References: References cannot be reassigned after initialization. They always refer to the same object.
int num2 = 10;
ptr = &num2; // ptr now points to num2
int& ref2 = num2;
// ref2 = # // Error: Cannot reassign a reference
Pointer Arithmetic
Pointers: You can perform arithmetic operations on pointers, like adding or subtracting an integer to move between memory locations. References: References don’t have a notion of memory address arithmetic.
int arr[5] = {1, 2, 3, 4, 5};
int* ptr_arr = arr; // Points to the first element of arr
// References don't have this capability
Function Parameters
Pointers: They can be passed as arguments to functions, allowing the function to directly modify the original variable. References: They can also be used as function parameters, offering similar benefits to pointers, but with a more intuitive syntax.
void modifyWithPointer(int* ptr) {
*ptr = 100;
}
void modifyWithReference(int& ref) {
ref = 100;
}
Both references and pointers have their use cases. References are often preferred for their safety and cleaner syntax when you want to work with existing variables. Pointers, on the other hand, offer more flexibility in certain situations, such as dynamic memory allocation or when you need to reassign where the variable points.
We will be using lots of pointers and references in the courses that follow! If you are interested in learning more, I encourage you to check out the more advanced courses.