There is one more way to pass variables to functions, and that is by address.
Passing an argument by address involves passing the address of the argument variable rather than the argument variable itself. Because the argument is an address, the function parameter must be a pointer. The function can then dereference the pointer to access or change the value being pointed to.
Here is an example of a function that takes a parameter passed by address:
10 | cout << "nValue = " << nValue << endl; |
12 | cout << "nValue = " << nValue << endl; |
The above snippet prints:
nValue = 5
nValue = 6
As you can see, the function foo() changed the value of nValue through the pointer parameter pValue.
Pass by address is typically used with dynamically allocated variables and arrays. For example, the following function will print all the values in an array:
1 | void PrintArray( int *pnArray, int nLength) |
3 | for ( int iii=0; iii < nLength; iii++) |
4 | cout << pnArray[iii] << endl; |
Here is an example program that calls this function:
3 | int anArray[6] = { 6, 5, 4, 3, 2, 1 }; |
4 | PrintArray(anArray, 6); |
This program prints the following:
6
5
4
3
2
1
Note that the length of the array must be passed in as a parameter, because arrays don’t keep track of how long they are. Otherwise the PrintArray() function would not know how many elements to print.
It is always a good idea to ensure parameters passed by address are not null pointers before dereferencing them. Dereferencing a null pointer will typically cause the program to crash. Here is our PrintArray() function with a null pointer check:
1 | void PrintArray( int *pnArray, int nLength) |
7 | for ( int iii=0; iii < nLength; iii++) |
8 | cout << pnArray[iii] << endl; |
Advantages of passing by address:
- It allows us to have the function change the value of the argument, which is sometimes useful
- Because a copy of the argument is not made, it is fast, even when used with large structs or classes.
- We can return multiple values from a function.
Disadvantages of passing by address:
- Because literals and expressions do not have addresses, pointer arguments must be normal variables.
- All values must be checked to see whether they are null. Trying to dereference a null value will result in a crash. It is easy to forget to do this.
- Because dereferencing a pointer is slower than accessing a value directly, accessing arguments passed by address is slower than accessing arguments passed by value.
As you can see, pass by address and pass by reference have almost identical advantages and disadvantages. Because pass by reference is generally safer than pass by address, pass by reference should be preferred in most cases.
Passing by reference, address, and value is actually not so different
Now that you understand the basic differences between passing by reference, address, and value, let’s complicate things by simplifying them. :)
In the lesson on passing arguments by reference, we briefly mentioned that references are typically implemented by the compiler as pointers. Because of this, the only real difference between pointers and references is that references have a cleaner but more restrictive syntax. This makes references easier and safer to use, but also less flexible. This also means that pass by reference and pass by address are essentially identical in terms of efficiency.
Here’s the one that may surprise you. When you pass an address to a function, that
address is actually passed by value! Because the address is passed by value, if you change the value of that address within the function, you are actually changing a temporary copy. Consequently, the original pointer address will not be changed!
Here’s a sample program that illustrates this.
08 | void SetToSix( int *pTempPtr); |
33 | void SetToSix( int *pTempPtr) |
Because pTempPtr receives a copy of the address of pPtr, even though we change pTempPtr, this does not change the value that pPtr points to. Consequently, this program prints
565
Even though the address itself is passed by value, you can still dereference that address to permanently change the value at that address! This is what differentiates pass by address (and reference) from pass by value.
The next logical question is, “What if we want to be able to change the address of an argument from within the function?”. Turns out, this is surprisingly easy. You just use pass the pointer itself by reference (effectively passing the address by reference). You already learned that values passed by reference reflect any changes made in the function back to the original arguments. So in this case, we’re telling the compiler that any changes made to the address of pTempPtr should be reflected back to pPtr! The syntax for doing a reference to a pointer is a little strange (and easy to get backwards):
int *&pPtr
. However, if you do get it backwards, the compiler will give you an error.
The following program illustrates using a reference to a pointer.
03 | void SetToSix( int *&pTempPtr) |
Note that you’ll also have to update the function prototype above main to account for the new prototype of SetToSix():
3 | void SetToSix( int *&pTempPtr); |
When we run the program again with this version of the function, we get:
566
Which shows that calling SetToSix() did indeed change the address of pPtr!
So strangely enough, the conclusion here is that references are pointers, and pointer addresses are passed by value. The value of pass by address (and reference) comes
solely from the fact that we can dereference addresses to change the original arguments, which we can not do with a normal value parameter.
No comments:
Post a Comment