Pointers are variables that hold memory addresses of data as opposed to the data itself. In other words, the pointer variable points to the location of the data.
The syntax to declare a pointer variable is dataType *identifier
.
The *
can be anywhere between the dataType
and identifier
.
int* p;
int * p;
int *p;
In order to assign a value to a pointer, you need to use the address of operator: &
.
The address of operator is a unary operator that returns the address of its operand.
For example, if you assign 10
to the integer variable num
and output num
prefixed with the address of operator, you will see the memory address storing num
.
int num = 10;
std::cout << #
// output:
0x7ffeefbff538
0x7ffeefbff538
is the memory address holding the value of num
. The hexadecimal value represending the memory location will be different for you because it won't be storing the data in your machine at the same location as mine.
You can use a pointer variable to store the memory address:
int num = 10;
int *numPtr = #
The pointer data type has to match the data type of the variable it's pointing to.
double num = 10.75;
double *numPtr = #
char ch = 'A';
char *chPtr = &ch;
If you output the value of numPtr
you would get the same memory address as outputting &num
. In order to retrieve the value 10
you need to dereference the pointer variable. The dereferencing operator is the *
(asterisk) like the one used to create a pointer.
int num = 10;
int *numPtr = #
std::cout << *numPtr << std::endl; // 10
Here is a comparison of outputs:
int num = 10;
int *numPtr = #
std::cout << "&num: " << &num << std::endl;
std::cout << "numPtr: " << numPtr << std::endl;
std::cout << "&numPtr: " << &numPtr << std::endl;
std::cout << "*numPtr: " << *numPtr << std::endl;
// output:
&num: 0x7ffeefbff538
numPtr: 0x7ffeefbff538
&numPtr: 0x7ffeefbff530
*numPtr: 10
You can see that the value of numPtr
is the memory address of num, printing the memory address of numPtr
shows the memory address of the pointer variable, and dereferencing numPtr
gives the value stored in num
.
You can use pointers with classes and structs, too.
struct DogType
{
string name;
char breed;
int age;
};
DogType dog;
DogType *dogPtr = &dog;
dog.name = "Penny";
std::cout << dog.name << std::endl; // Penny
(*dogPtr).name = "Pixel";
std::cout << dog.name << std::endl; // Pixel;
You can see that in order to use the pointer to set a new value in the memory address the pointer is pointing to, you have to put parentheses around the derefercing of dogPtr
. This is because, in C++, the dot operator .
has a higher precedence than the dereferencing operator *
. By using the parentheses, the dereferencing operator will evaluate first.
Thankfully there is an easier way to write this, using the *member access operator arrow ->
// (*dogPtr).name = "Pixel";
// becomes
dogPtr->name = "Pixel";
So, why bother with any of this when you can just directly access/modify the variable that's being pointed to?
By using pointers, you can create dynamic variables, which are variables created during program execution. Dynamic variables are unnamed and must be accessed indirectly by pointers.
The keyword new
is used to create dynamic variables by allocationg memory in the heap.
You can use new
to allocate memory for a single variable or for an array of variables.
int *p = new int;
int size = 10;
int *pArray = new int[size];
pArray[0] = 1;
The code below asks the user to press 1 to create a new dog. Each time a dog is created with the new
operator, the memory address is stored in dogPtr
.
int input = -1;
DogType *dogPtr;
while(input != 2)
{
std::cout << "Press 1 to create a new dog: ";
std::cin >> input;
if (input == 1)
{
dogPtr = new DogType;
dogPtr->name = getDogName();
std::cout << "I'm a new dog and my name is " << dogPtr->name << endl;
std::cout << "dogPtr is located at " << dogPtr << endl;
}
}
return 0;
}
// output:
Press 1 to create a new dog: 1
I'm a new dog and my name is Elaine
dogPtr is located at 0x1006040e0
Press 1 to create a new dog: 1
I'm a new dog and my name is Murray
dogPtr is located at 0x1004045a0
Press 1 to create a new dog: 1
I'm a new dog and my name is Guybrush
dogPtr is located at 0x1004045c0
As you can see, each time a new dog is created it's stored in a new memory address. An important thing to note is when you use new
to allocate memory you need to use delete
to deallocate that memory when you're done with it. Otherwise, when you point the pointer variable elsewhere, that initial memory space will still be taken and you'll have no way to access it. This is known as a memory leak.
If you delete an object in memory, but a pointer still points to that memory address, you end up with a dangling pointer, which could cause corrupted data or program termination.
nullptr
C++ doesn't initialize variables; therefore, pointer variables must be initialized to prevent them from accidentally pointing to some place in memory you didn't intend.
int *p = 0;
// is equivalent to
int *p = NULL;
The C++11 standard introduced nullptr
:
int *p = nullptr;
Pointer Arithmetic
You can do some arithmetic on pointers for operations, but it doesn't work the same way as it does on data types such as int
and double
.
If you increase an int value by 1, it will increase by 1.
int num = 1;
num++;
std::cout << num << std::endl;
// output
2
When you increase a pointer by 1, it increases by the size of the data type it's pointing to.
If you have an array the pointer will point to the memory address of the first index. Since this example is an array of integers and an int is 4 bytes, when you increase the pointer by 1 it will increase the memory address by the size of an int to point to the next index in the array.
int *p = new int[10];
cout << p << endl;
p++;
cout << p << endl;
// output
0x1030bd1c0
0x1030bd1c4
You can see by the last digit of the output that the memory address increased by 4.
Pointer arithmetic can be dangerous because you could end up accessing the memory where other variables are stored and altering their values.
Further Reading / References
- C++ Programming: Program Design Including Data Structures 8th ed. by D.S. Malik
- Pointers - Microsoft Docs
- The Stack and the Heap
Top comments (2)
Hey!
Good job on this article :)
There is a point I would like to discuss:
The main reason is because variables are passed by copy to functions in C++.
This code prints "2 2", not "2 4". This is why we need pointers. In fact, we need addresses. References work perfectly in such a situation:
By the way, with modern C++ (== C++11 and above), you should avoid calling new and delete by yourself and prefer using smart pointers:
Output:
There are the best way to avoid memory leaks :)
Thanks for the info, Pierre! :)