Wrox Press C++ Tutorial
It's very important to understand how arguments are passed to a function, as it will affect how you write functions and how they will ultimately operate. There are also a number of pitfalls to be avoided, so we'll look at the mechanism quite closely.
The arguments specified when a function is called should usually correspond in type and sequence to the parameters appearing in the definition of the function. As we saw in the last example, if the type of an argument specified in a function call doesn't correspond with the type of parameter in the function definition, then (where possible) it will be converted to the required type. If this proves not to be possible, you will get an error message from the compiler. However, even if the conversion is possible and the code compiles, it could well result in the loss of data (for example from type long to short) and should therefore be avoided.
One mechanism, used generally in C++ to pass parameters to functions, applies when the parameters are specified in the function definition, or prototype, as ordinary variables (not references). This is called the pass-by-value method of transferring data to a function.
With this mechanism, the variables or constants that you specify as arguments are not passed to a function at all. Instead, copies of the arguments are created and these copies are used as the values to be transferred. We can show this in a diagram using the example of our function power():

Each time you call the function power(), the compiler arranges for copies of the arguments that you specify to be stored in a temporary location in memory. During execution of the functions, all references to the function parameters will be mapped to these temporary copies of the arguments.
Purely to help your understanding of the diagram, we have used pseudo-names for the copies generated in the illustration. In reality, they do not exist in this form.
One consequence of the pass-by-value mechanism is that a function can't directly modify the arguments passed. We can demonstrate this by deliberately trying to do so in an example:
// Ex4_02.cpp
// A futile attempt to modify caller arguments
#include <iostream>
using namespace std;
int incr10(int num); // Function prototype
int main(void)
{
int num = 3;
cout << endl
<< "incr10(num) = " << incr10(num)
<< endl
<< "num = " << num;
cout << endl;
return 0;
}
// Function to increment a variable by 10
int incr10(int num) // Using the same name might help...
{
num += 10; // Increment the caller argument - hopefully
return num; // Return the incremented value
}
Of course, this program is doomed to failure. If you run it, you will get this output:

This confirms that the original value of num remains untouched. The incrementing occurred on the copy of num that was generated, and was eventually discarded on exiting from the function.
Clearly, the pass-by-value mechanism provides you with a high degree of protection from having your caller arguments mauled by a rogue function, but it is conceivable that we might actually want to arrange to modify caller arguments. Of course, there is a way to do this. Didn't you just know that pointers would turn out to be incredibly useful?
When you use a pointer as an argument, the pass-by-value mechanism still operates as before. However, a pointer is an address of another variable, and if you take a copy of this address, the copy still points to the same variable. This is how specifying a pointer as a parameter enables your function to get at a caller argument.
We can change the last example to use a pointer to demonstrate the effect:
// Ex4_03.cpp
// A successful attempt to modify caller arguments
#include <iostream>
using namespace std;
int incr10(int* num); // Function prototype
int main(void)
{
int num = 3;
int* pnum = # // Pointer to num
cout << endl
<< "Address passed = " << pnum;
cout << endl
<< "incr10(pnum) = " << incr10(pnum);
cout << endl
<< "num = " << num;
cout << endl;
return 0;
}
// Function to increment a variable by 10
int incr10(int* num) // Function with pointer argument
{
cout << endl
<< "Address received = " << num;
*num += 10; // Increment the caller argument - confidently
return *num; // Return the incremented value
}
In this example, the principal alterations from the previous version relate to passing a pointer, pnum, in place of the original variable, num. The prototype for the function now has the parameter type specified as a pointer to int, and the main() function has the pointer pnum declared and initialized with the address of num. The function main(), and the function incr10(), output the address sent and the address received respectively, to verify that the same address is indeed being used in both places.
If you run this program, you'll get output similar to this:

The address values produced by your computer may well be different from those shown above, but the two values should be identical to each other.
The output shows that this time the variable num has been incremented and has a value that's now identical to that returned by the function.
In the rewritten version of the function incr10(), both the statement incrementing the value passed to the function, and the return statement, now need to de-reference the pointer in order to use the value stored.
You can also pass an array to a function, but in this case the array is not copied, even though a pass-by-value method of passing arguments still applies. The array name is converted to a pointer, and a copy of the pointer to the beginning of the array is passed to the function. This is quite advantageous, as copying large arrays could be very time consuming.
We can illustrate the ins and outs of this by writing a function to compute the average of a number of values that are passed to a function in an array.
// Ex4_04.cpp
// Passing an array to a function
#include <iostream>
using namespace std;
//Function prototype
double average(double array[], int count);
int main(void)
{
double values[] = { 1.0, 2.0, 3.0, 4.0,
5.0, 6.0, 7.0, 8.0, 9.0, 10.0 };
cout << endl
<< "Average = "
<< average(values, (sizeof values)/(sizeof values[0]));
cout << endl;
return 0;
}
// Function to compute an average
double average(double array[], int count)
{
double sum = 0.0; // Accumulate total in here
for(int i = 0; i<count; i++)
sum += array[i]; // Sum array elements
return sum/count; // Return average
}
The function average() is designed to work with an array of any length. As you can see from the prototype, it accepts two arguments: the array and a count of the number of elements. Since we want it to work with arrays of arbitrary length, the array parameter appears without a specified dimension.
The function is called in main() in this statement,
cout << endl
<< "Average = "
<< average(values, (sizeof values)/(sizeof values[0]));
with the first argument as the array name, values, and the second argument as an expression which evaluates to the number of elements in the array.
You'll recall this expression, using the operator sizeof, from when we looked at arrays in Chapter 3.
Within the body of the function, the computation is expressed in the way you would expect. There's no significant difference between this and the way we would write the same computation if we implemented it directly in main().
If you run the example, it will produce the following output:

This confirms that everything works as we anticipated.
However, we haven't exhausted all the possibilities here. As we determined at the outset, the array name is passed as a pointer-in fact, as a copy of a pointer, so within the function we needn't necessarily deal with the data as an array at all. We could modify the function in the example to work with pointer notation throughout, in spite of the fact that we are using an array.
// Ex4_05.cpp
// Handling an array in a function as a pointer
#include <iostream>
using namespace std;
double average(double* array, int count); //Function prototype
int main(void)
{
double values[] = { 1.0, 2.0, 3.0, 4.0, 5.0,
6.0, 7.0, 8.0, 9.0, 10.0 };
cout << endl
<< "Average = "
<< average(values, (sizeof values)/(sizeof values[0]));
cout << endl;
return 0;
}
// Function to compute an average
double average(double* array, int count)
{
double sum = 0.0; // Accumulate total in here
for(int i = 0; i<count; i++)
sum += *array++; // Sum array elements
return sum/count; // Return average
}
As you can see, the program needed very few changes to make it work with the array as a pointer. The prototype and the function header have been changed, although neither change is absolutely necessary. If you change both back to the original version with the first parameter specified as a double array, and leave the function body written in terms of a pointer, it will work just as well. The most interesting aspect of this version is the for loop statement:
sum += *array++; // Sum array elements
Here we apparently break the rule about not being able to modify an address specified as an array name, because we are incrementing the address stored in array. In fact, we aren't breaking the rule at all. Remember that the pass-by-value mechanism makes a copy of the original array address and passes that, so we are just modifying the copy here - the original array address will be quite unaffected. As a result, whenever we pass a one-dimensional array to a function, we are free to treat the value passed as a pointer in every sense, and change the address in any way we wish.
Naturally, this version produces exactly the same output as the original.
Passing a multi-dimensional array to a function is quite straightforward. The following line declares a two dimensional array, beans:
double beans[2][4];
You could then write the prototype of a hypothetical function, yield(), like this:
double yield(double beans[2][4]);
You may be wondering how the compiler can know that this statement is defining an array of the dimensions shown as an argument, and not a single array element. The answer is simple - you can't write a single array element as a parameter in a function definition or prototype, although you can pass one as an argument when you call a function. For a parameter accepting a single element of an array as an argument, the parameter would just have a variable name. The array context wouldn't apply.
When you are defining a multi-dimensional array as a parameter, you can also omit the first dimension value. Of course, the function will need some way of knowing the extent of the first dimension. For example, you could write this:
double yield(double beans[][4], int index);
Here, the second parameter would provide the necessary information about the first dimension. The function can operate with a two-dimensional array with any value for the first dimension, but with the second dimension fixed at 4.
We define such a function in the following example:
// Ex4_06.cpp
// Passing a two-dimensional array to a function
#include <iostream>
using namespace std;
double yield(double array[][4], int n);
int main(void)
{
double beans[3][4] = { { 1.0, 2.0, 3.0, 4.0 },
{ 5.0, 6.0, 7.0, 8.0 },
{ 9.0, 10.0, 11.0, 12.0 } };
cout << endl
<< "Yield = "
<< yield(beans, sizeof beans/sizeof beans[0]);
cout << endl;
return 0;
}
// Function to compute total yield
double yield(double beans[][4], int count)
{
double sum = 0.0;
for(int i=0; i<count; i++) // Loop through number of rows
for(int j=0; j<4; j++) // Loop through elements in a row
sum += beans[i][j];
return sum;
}
We have used different names for the parameters in the function header from those in the prototype, just to remind you that this is possible - but in this case, it doesn't really improve the program at all. The first parameter is defined as an array of an arbitrary number of rows, each row having four elements. We actually call the function using the array beans with three rows. The second argument is specified by dividing the total size of the array in bytes by the size of the first row. This will evaluate to the number of rows in the array.
The computation in the function is simply a nested for loop with the inner loop summing elements of a single row and the outer loop repeating this for each row. For what it's worth, the program will display this result:

Using a pointer in a function rather than a multi-dimensional array as an argument doesn't really apply particularly well. When the array is passed, it passes an address value which points to an array of four elements (a row) 4. This doesn't lend itself to an easy pointer operation within the function. We would need to modify the statement in the nested for loop to the following,
sum += *(*(beans+i)+j);
so the computation is almost certainly clearer in array notation.
Specifying a parameter to a function as a reference changes the method of passing data for that parameter. The method used is not pass-by-value, where an argument is copied before being passed, but pass-by-reference where the parameter acts as an alias for the argument passed. This eliminates any copying and allows the function to access the caller argument directly. It also means that the de-referencing, which is required when passing and using a pointer to a value, is also unnecessary.
Let's go back to a revised version of a very simple example, Ex4_03.cpp, to see how it would work using reference parameters:
// Ex4_07.cpp
// Using a reference to modify caller arguments
#include <iostream>
using namespace std;
int incr10(int& num); // Function prototype
int main(void)
{
int num = 3;
int value = 6;
cout << endl
<< "incr10(num) = " << incr10(num);
cout << endl
<< "num = " << num;
cout << endl
<< "incr10(value) = " << incr10(value);
cout << endl
<< "value = " << value;
cout << endl;
return 0;
}
// Function to increment a variable by 10
int incr10(int& num) // Function with reference argument
{
cout << endl
<< "Value received = " << num;
num += 10; // Increment the caller argument - confidently
return num; // Return the incremented value
}
You should find the way this works quite remarkable. This is essentially the same as Ex4_03.cpp, except that the function uses a reference as a parameter. The prototype has been changed to reflect this. When the function is called, the argument is specified just as though it is a pass-by-value operation, that is, it's used in the same way as the earlier version. The argument value isn't passed to the function. Here, the function parameter is initialized with the address of the argument, so whenever the parameter num is used in the function, it accesses the caller argument directly.
Just to reassure you that there's nothing fishy about the use of the identifier num in main() as well as in the function, the function is called a second time with the variable value as the argument. At first sight this may give you the impression that it contradicts what we said was a basic property of a reference - that once declared and initialized, it couldn't be reassigned to another variable. The reason it isn't contradictory is that a reference as a function parameter is created and initialized when the function is called, and destroyed when the function ends, so we get a completely new reference each time we use the function.
Within the function, the value received from the calling program is displayed on the screen. Although the statement is essentially the same as the one used to output the address stored in a pointer, because num is now a reference, we obtain the data value rather than the address.
This clearly demonstrates the difference between a reference and a pointer. A reference is an alias for another variable, and therefore can be used as an alternative way of referring to it. It is equivalent to using the original variable name.
The output from this example is as follows:

This shows that the function incr10() is directly modifying the variable passed as a caller argument.
You will find that if you try to use a numeric value, such as 20, as an argument to incr10(), the compiler will output an error message. This is because the compiler recognizes that a reference parameter can be modified within a function, and the last thing you want is to have your constants changing value now and again. This would introduce a kind of excitement into your programs that you could probably do without.
This security is all very well, but if the function didn't modify the value, we wouldn't want the compiler to create all these error messages every time we pass a reference argument that was a constant. Surely there ought to be some way to accommodate this? As Ollie would have said, 'There most certainly is, Stanley!'
We can use the const modifier with a parameter to a function to tell the compiler that we don't intend to modify it in any way. This will cause the compiler to check that your code indeed does not modify the argument, and there will be no error messages when you use a constant argument.
We can modify the previous program to show how the const modifier changes the situation.
// Ex4_08.cpp
// Using a reference to modify caller arguments
#include <iostream>
using namespace std;
int incr10(const int& num); // Function prototype
int main(void)
{
// Declared const to test for temporary creation
const int num = 3;
int value = 6;
cout << endl
<< "incr10(num) = " << incr10(num);
cout << endl
<< "num = " << num;
cout << endl
<< "incr10(value) = " << incr10(value);
cout << endl
<< "value = " << value;
cout << endl;
return 0;
}
// Function to increment a variable by 10
// Function with const reference argument
int incr10(const int& num)
{
cout << endl
<< "Value received = " << num;
// num += 10; // this statement would now be illegal
return num+10; // Return the incremented value
}
We declare the variable num in main() as const to show that when the parameter to the function incr10() is declared as const, we no longer get a compiler message when passing a const object.
It has also been necessary to comment out the statement which increments num in the function incr10(). If you uncomment this line, you'll find the program will no longer compile, because the compiler won't allow num to appear on the left-hand side of an assignment. When you specified num as const in the function header and prototype, you promised not to modify it, and so the compiler checks that you kept your word.
Everything works as before, except that the variables in main() are no longer changed in the function, so the program produces the following output:

Now, by using reference arguments, we have the best of both worlds. On one hand, we can write a function that can access caller arguments directly, and avoid the copying that is implicit in the pass-by-value mechanism. On the other hand, where we don't intend to modify an argument, we can get all the protection against accidental modification we need by using a const modifier with a reference.