Now that we know how scope works, we are ready to learn about
one of the main reasons why pointers were invented in the
first place. To demonstrate this, I created a simple program.
First we declare and initialize this x variable, and then
we pass it to the addThree function.
The only purpose of this function is to increment the
number from the parameter by 3.
I also printed out the value of x to the terminal in three places
throughout the code. Before continuing with the lesson,
try to guess what each one of these cout statements will print
out. Now, let's compile it to see what we'll get.
It seems that the function worked, since the second line
says 8, but what about this third line.
After we exit the function, the value of x is 5 again.
Why?
Let's see what's going on in memory line by line.
Main function will get executed first, so this means that the main scope is
created. Inside of the scope, we declared a variable x.
As we know, this will allocate a space in memory and store this number 5
inside of it. And, of course, this cout will then print out the number 5.
The addThree function will get executed next,
which means that the new scope for this function is spawned into existence.
Official term would be pushed onto the stack, but we will
talk about stack in the next module.
Now,
this is the important part, so pay close attention. In the previous
lesson, we said that the parameter variables are just local variables
that belong to the scope of the function, so a new place in memory is
allocated for this local variable x.
As you can see, now we have two variables with the same name,
same identifier, but they are not the same. They don't have the same place in
memory, and each of them exist in their own separate scope.
You can use the same name for more variables, as long as
you don't use it in the same scope.
So what happens when you pass a variable as a
parameter inside of a function call?
C++ will copy the value from the passed variable inside of the
parameter variable. In this case, this is the functional local x
variable, so the value of 5 gets copied from this place in memory
to this place in memory.
Since the value is passed from one variable to another,
we call this event passed by value. The value in this local variable x
will then increment by 3, and the number 8 will be printed out to the
terminal. And now this function scope is done,
which means that all of the declared variables from
the scope are released from memory.
This local variable x from the parameter does not exist anymore.
We are now back in the main function, and we print out the value of x,
which is 5, because this x variable from the main function scope was
never even touched, and now the output we saw makes sense. To make this
work as intended, we could have returned the value from the incremented
variable x from the function and then overwrite that value inside of the
main function x.
So this function should return int, and we assign its
return value to the x from the main scope.
When this function is finished,
the value from x, which is 8, will get copied from this place
in memory back to this place in memory.
The scope of this function will still be destroyed,
but the value is incremented by 3.
As you can see, this is really expressive.
What if we just wanted to simply increment this variable without
returning anything from the function. And, more importantly,
what if we didn't pass a simple integer?
What if this was an object that had 50 bytes of data and this
function had to be executed more than once.
This means that the object from this scope will have to be
copied over from this place in memory to this one, and then
when the function is finished,
the local object will have to copy the value back to the first place in
memory. And don't forget that you also have to allocate a new place in
memory for this temporary object every time the function gets called. This
memory allocation and copying of values around in memory will degrade the
performance of your programs, and that is one of the main reasons why we
needed to invent something like pointers. Instead of passing the value from
this variable, let's pass its memory address.
We can do this with the reference operator.
Since we are now passing the address,
this parameter variable needs to be able to store that address.
Integer variables don't do that,
but the pointers to integers do. So this will create a pointer
variable inside of the function scope, which will store the
address passed on from the main function.
And now, instead of incrementing this local variable by 3,
we need to dereference this pointer.
This means that this value 5 will be incremented by 3, the same place in
memory occupied by the variable x inside of the main scope.
So with the help of pointers, we access the variable from another scope, and
this scope still exists because main function is not done yet.
Once the addThree function is finished,
the function scope is deleted, and this pointer variable is removed
from memory. But we don't care, because the value from this variable we
passed in still has an incremented value.
If I compile this version of the program,
the result will be 5, 8, and 8, and this is exactly what we wanted.
At the first glance,
this program looks similar to the first version without pointers.
After all,
we still have to allocate memory for this pointer and copy this address
inside of it. But this program is a lot more efficient.
First of all,
we are not returning anything, so there is no copying of value
back to the variable x from the main function.
Second,
this local variable is now just a pointer, which means that we allocate only 4
or 8 bytes of data, depending on the computer's architecture.
If we pass around big complex objects,
you can see that it is easier and more efficient to store a
simple address instead of copying a big object and allocating a
lot of space to store it temporarily.
This idea of indirectly modifying data and memory from the scope of
a different function by storing an address of an argument as a
parameter is known as passing by reference.
So instead of passing by value,
which means copying the value from this variable to this one,
you are passing by reference,
which means that you are only storing an address, in other words,
a reference. You are referring to this variable in memory through the
pointer. Passing by reference is a far more efficient way to work with
objects that need to be passed to functions.
Of course, there are some cases where you really want to just pass by value,
because you need to use this value for some other purpose and you are not
interested in the variable that passed it in. However, passing by reference is
usually the preferred way, but as you can see,
it can seem a little bit frightening at first,
especially because you need to remember to pass an address and then also
dereference this pointer to work with the value at that address.
C programmers are very familiar with this,
but since we are working in C++,
you can be sure that there is a better way to do this,
and we will talk about it in the next lesson.