When a function is called, its arguments are substituted for the formal parameters in the function definition, or to state it less formally, the arguments are “plugged in” for the formal parameters. There are different mechanisms used for this substitution process. The mechanism we used in Chapter 4, and thus far in this chapter, is known as the call-by-value mechanism. The second main mechanism for substituting arguments is known as the call-by-reference mechanism.
The call-by-value mechanism that we used until now is not sufficient for certain subtasks. For example, one common subtask is to obtain one or more input values from the user. Look back at the program in Display 5.2. Its tasks are divided into four subtasks: initialize the screen, obtain the Fahrenheit temperature, compute the corresponding Celsius temperature, and output the results. Three of these four subtasks are implemented as the functions initializeScreen, celsius,
and showResults
. However, the subtask of obtaining the input is implemented as the following four lines of code (rather than as a function call):
cout << "I will convert a Fahrenheit temperature"
<< " to Celsius.\n"
<< "Enter a temperature in Fahrenheit: ";
cin >> fTemperature;
The subtask of obtaining the input should be accomplished by a function call. To do this with a function call, we will use a call-by-reference parameter.
A function for obtaining input should set the values of one or more variables to values typed in at the keyboard, so the function call should have one or more variables as arguments and should change the values of these argument variables. With the call-by-value formal parameters that we have used until now, an argument in a function call can be a variable, but the function takes only the value of the variable and does not change the variable in any way. With a call-by-value formal parameter only the value of the argument is substituted for the formal parameter. For an input function, we want the variable (not the value of the variable) to be substituted for the formal parameter. The call-by-reference mechanism works in just this way. With a call-by-reference formal parameter (also called simply a reference parameter), the corresponding argument in a function call must be a variable and this argument variable is substituted for the formal parameter. It is as if the argument variable were literally copied into the body of the function definition in place of the formal parameter. After the argument is substituted in, the code in the function body is executed and this code can change the value of the argument variable.
A call-by-reference parameter must be marked in some way so that the compiler will know it from a call-by-value parameter. The way that you indicate a call-by-reference parameter is to attach the ampersand sign, &, to the end of the type name in the formal parameter list in both the function declaration and the header of the function definition. For example, the following function definition has one formal parameter, fVariable
, and that formal parameter is a call-by-reference parameter:
void getInput (double & fVariable)
{ using namespace std;
cout << "I will convert a Fahrenheit temperature"
<< " to Celsius.\n"
<< "Enter a temperature in Fahrenheit: ";
cin >> fVariable;
}
In a program that contains this function definition, the following function call sets the variable fTemperature
equal to a value read from the keyboard:
getInput(fTemperature);
Using this function definition, we could easily rewrite the program shown in Display 5.2 so that the subtask of reading the input is accomplished by this function call. However, rather than rewrite an old program, let’s look at a completely new program.
Display 5.4 demonstrates call-by-reference parameters. The program doesn’t do very much. It just reads in two numbers and writes the same numbers out, but in the reverse order. The parameters in the functions getNumbers
and swapValues
are call-by-reference parameters. The input is performed by the function call
getNumbers(firstNum, secondNum);
1 //Program to demonstrate call-by-reference parameters.
2 #include <iostream>
3 void getNumbers(int& input1, int& input2);
4 //Reads two integers from the keyboard.
5 void swapValues(int& variable1, int& variable2);
6 //Interchanges the values of variable1 and variable2.
7 void showResults(int output1, int output2);
8 //Shows the values of variable1 and variable2, in that order.
9 int main( )
10 {
11 int firstNum = 0, secondNum = 0;
12
13 getNumbers(firstNum, secondNum);
14 swapValues(firstNum, secondNum);
15 showResults(firstNum, secondNum);
16 return 0;
17 }
18 //Uses iostream:
19 void getNumbers (int& input1, int& input2)
20 {
21 using namespace std;
22 cout << "Enter two integers: ";
23 cin >> input1
24 >> input2;
25 }
26 void swapValues(int& variable1, int& variable2)
27 {
28 int temp;
29 temp = variable1;
30 variable1 = variable2;
31 variable2 = temp;
32 }
33 //Uses iostream:
34 void showResults(int output1, int output2)
35 {
36 using namespace std;
37 cout << "In reverse order the numbers are: "
38 << output1 << " " << output2 << endl;
39 }
Sample Dialogue
Enter two integers: 5 10
In reverse order the numbers are: 10 5
The values of the variables firstNum
and secondNum
are set by this function call. After that, the following function call reverses the values in the two variables firstNum
and secondNum
:
swapValues(firstNum, secondNum);
In the next few subsections we describe the call-by-reference mechanism in more detail and also explain the particular functions used in Display 5.4.
In most situations, the call-by-reference mechanism works as if the name of the variable given as the function argument were literally substituted for the call-by-reference formal parameter. However, the process is a bit more subtle than that. In some situations, this subtlety is important, so we need to examine more details of this call-by-reference substitution process.
Recall that program variables are implemented as memory locations. The compiler assigns one memory location to each variable. For example, when the program in Display 5.4 is compiled, the variable firstNum
might be assigned location 1010, and the variable secondNum
might be assigned 1012. For purposes of this example, consider these variables to be stored at these memory locations. In other words, after executing the line
int firstNum = 0, secondNum = 0;
the value 0 will be stored at memory locations 1010 and 1012. The arrows in the diagram below point to the memory locations referenced by the variables.
Next, consider the following function declaration from Display 5.4:
void getNumbers(int& input1, int& input2);
The call-by-reference formal parameters input1
and input2
are place holders for the actual arguments used in a function call.
Now consider a function call like the following from the same display:
getNumbers(firstNum, secondNum);
When the function call is executed, the function is not given values stored in firstNum
and secondNum
. Instead, it is given the memory locations associated with each name. In this example, the locations are
1010
1012
which are the locations assigned to the argument variables firstNum
and secondNum
, in that order. It is these memory locations that are associated with the formal parameters. The first memory location is associated with the first formal parameter, the second memory location is associated with the second formal parameter, and so forth. In our example input1
is the first parameter, so it gets the same memory location as firstNum
. The second parameter is input2
and it gets the same memory location as secondNum
. Diagrammatically, the correspondence is
When the function statements are executed, whatever the function body says to do to a formal parameter is actually done to the variable in the memory location associated with that formal parameter. In this case, the instructions in the body of the function getNumbers
say that a value should be stored in the formal parameter input1
using a cin
statement, and so that value is stored in the variable in memory location 1010 (which happens to be where the variable firstNum
is stored). Similarly, the instructions in the body of the function getNumbers
say that a value should then be stored in the formal parameter input2
using a cin
statement, and so that value is stored in the variable in memory location 1012 (which happens to be where the variable secondNum
is stored). Thus, whatever the function instructs the computer to do to input1
and input2
is actually done to the variables firstNum
and secondNum
. For example, if the user enters 5
and 10
as in Display 5.4, then the result is
When the function getNumbers
exits, the variables input1
and input2
go out of scope and are lost. This means we can no longer retrieve the data values at 1010 and 1012 through the variables input1
and input2
. However, the data still exists in memory location 1010 and 1012 and is accessible through the variables firstNum
and secondNum
within the scope of the main
function. These details of how the call-by-reference mechanism works in this function call to getNumbers
are described in Display 5.5.
Behavior of Call-by-Reference Arguments
Anatomy of a Function Call from Display 5.4
Using Call-by-Reference Arguments
0 Assume the variables firstNum
and secondNum
have been assigned the following memory address by the compiler:
firstNum → 1010
secondNum → 1012
(We do not know what addresses are assigned and the results will not depend on the actual addresses, but this will make the process very concrete and thus perhaps easier to follow.)
1 In the program in Display 5.4, the following function call begins executing:
getNumbers(firstNum, secondNum);
2 The function is told to use the memory location of the variable firstNum in place of the formal parameter input1 and the memory location of the secondNum in place of the formal parameter input2. The effect is the same as if the function definition were rewritten to the following (which is not legal C++ code, but does have a clear meaning to us):
void getNumbers( int& <the variable at memory location 1010>,
int& <the variable at memory location 1012>)
{
using namespace std;
cout << "Enter two integers: ";
cin >> <the variable at memory location 1010>
>> <the variable at memory location 1012>;
}
Anatomy of the Function Call in Display 5.4 (concluded)
Since the variables in locations 1010 and 1012 are firstNum and secondNum, the effect is thus the same as if the function definition were rewritten to the following:
void getNumbers(int& firstNum, int& secondNum)
{
using namespace std;
cout << "Enter two integers: ";
cin >> firstNum
>> secondNum;
}
3 The body of the function is executed. The effect is the same as if the following were executed:
{
using namespace std;
cout << "Enter two integers: ";
cin >> firstNum
>> secondNum;
}
4 When the cin
statement is executed, the values of the variables firstNum
and secondNum
are set to the values typed in at the keyboard. (If the dialogue is as shown in Display 5.4, then the value of firstNum
is set to 5 and the value of secondNum
is set to 10.)
5 When the function call ends, the variables firstNum
and secondNum
retain the values that they were given by the cin
statement in the function body. (If the dialogue is as shown in Display 5.4, then the value of firstNum
is 5 and the value of secondNum
is 10 at the end of the function call.)
It may seem that there is an extra level of detail, or at least an extra level of verbiage. If firstNum
is the variable with memory location 1010, why do we insist on saying “the variable at memory location 1010” instead of simply saying “firstNum
”? This extra level of detail is needed if the arguments and formal parameters contain some confusing coincidence of names. For example, the function getNumbers
has formal parameters named input1
and input2
. Suppose you want to change the program in Display 5.4 so that it uses the function getNumbers
with arguments that are also named input1
and input2
, and suppose that you want to do something less than obvious. Suppose you want the first number typed in to be stored in a variable named input2
, and the second number typed in to be stored in the variable named input1
—perhaps because the second number will be processed first, or because it is the more important number. Now, let’s suppose that the variables input1
and input2
, which are declared in the main
part of your program, have been assigned memory locations 1014 and 1016. The function call could be as follows:
In this case if you say “input1
,” we do not know whether you mean the variable named input1
that is declared in the main
part of your program or the formal parameter input1
. However, if the variable input1
declared in the main
part of your program is assigned memory location 1014, the phrase “the variable at memory location 1014” is unambiguous. Let’s go over the details of the substitution mechanisms in this case.
In this call the argument corresponding to the formal parameter input1
is the variable input2
, and the argument corresponding to the formal parameter input2
is the variable input1
. This can be confusing to us, but it produces no problem at all for the computer, since the computer never does actually “substitute input2
for input1
” or “substitute input1
for input2
.” The computer simply deals with memory locations. The computer substitutes “the variable at memory location 1016” for the formal parameter input1
, and “the variable at memory location 1014” for the formal parameter input2
.
The function swapValues
defined in Display 5.4 interchanges the values stored in two variables. The description of the function is given by the following function declaration and accompanying comment:
void swapValues(int& variable1, int& variable2);
//Interchanges the values of variable1 and variable2.
To see how the function is supposed to work, assume that the variable firstNum
has the value 5
and the variable secondNum
has the value 10
and consider the function call:
swapValues(firstNum, secondNum);
After this function call, the value of firstNum
will be 10 and the value of secondNum
will be 5
.
As shown in Display 5.4, the definition of the function swapValues
uses a local variable called temp.
This local variable is needed. You might be tempted to think the function definition could be simplified to the following:
To see that this alternative definition cannot work, consider what would happen with this definition and the function call
swapValues(firstNum, secondNum);
The variables firstNum
and secondNum
are substituted for the formal parameters variable1
and variable2
so that, with this incorrect function definition, the function call is equivalent to the following:
firstNum = secondNum;
secondNum = firstNum;
This code does not produce the desired result. The value of firstNum
is set equal to the value of secondNum
, just as it should be. But then, the value of secondNum
is set equal to the changed value of firstNum
, which is now the original value of secondNum
. Thus the value of secondNum
is not changed at all. (If this is unclear, go through the steps with specific values for the variables firstNum
and secondNum
.) What the function needs to do is to save the original value of firstNum
so that value is not lost. This is what the local variable temp
in the correct function definition is used for. That correct definition is the one in Display 5.4. When that correct version is used and the function is called with the arguments firstNum
and secondNum
, the function call is equivalent to the following code, which works correctly:
temp = firstNum;
firstNum = secondNum;
secondNum = temp;
Whether a formal parameter is a call-by-value parameter or a call-by-reference parameter is determined by whether there is an ampersand attached to its type specification. If the ampersand is present, then the formal parameter is a call-by-reference parameter. If there is no ampersand associated with the formal parameter, then it is a call-by-value parameter.
It is perfectly legitimate to mix call-by-value and call-by-reference formal parameters in the same function. For example, the first and last of the formal parameters in the following function declaration are call-by-reference formal parameters and the middle one is a call-by-value parameter:
void goodStuff(int& par1, int par2, double& par3);
Call-by-reference parameters are not restricted to void
functions. You can also use them in functions that return a value. Thus, a function with a call-by-reference parameter could both change the value of a variable given as an argument and return a value.
Display 5.6 illustrates the differences between how the compiler treats call-by-value and call-by-reference formal parameters. The parameters par1Value
and par2Ref
are both assigned a value inside the body of the function definition. But since they are different kinds of parameters, the effect is different in the two cases.
Comparing Argument Mechanisms
1 //Illustrates the difference between a call-by-value
2 //parameter and a call-by-reference parameter.
3 #include <iostream>
4 void doStuff(int par1Value, int& par2Ref);
5 //par1Value is a call-by-value formal parameter and
6 //par2Ref is a call-by-reference formal parameter.
7 int main( )
8 {
9 using namespace std;
10 int n1, n2;
11
12 n1 = 1;
13 n2 = 2;
14 doStuff(n1, n2);
15 cout << "n1 after function call = " << n1 << endl;
16 cout << "n2 after function call = " << n2 << endl;
17 return 0;
18 }
19 void doStuff(int par1Value, int& par2Ref)
20 {
21 using namespace std;
22 par1Value = 111;
23 cout << "par1Value in function call = "
24 << par1Value << endl;
25 par2Ref = 222;
26 cout << "par2Ref in function call = "
27 << par2Ref << endl;
28 }
Sample Dialogue
par1Value in function call = 111 par2Ref in function call = 222 n1 after function call = 1 n2 after function call = 222
par1Value
is a call-by-value parameter, so it is a local variable. When the function is called as follows
doStuff(n1, n2);
the local variable par1Value is initialized to the value of n1. That is, the local variable par1Value is initialized to 1 and the variable n1 is then ignored by the function. As you can see from the sample dialogue, the formal parameter par1Value (which is a local variable) is set to 111 in the function body and this value is output to the screen. However, the value of the argument n1 is not changed. As shown in the sample dialogue, n1 has retained its value of 1.
On the other hand, par2Ref
is a call-by-reference parameter. When the function is called, the variable argument n2
(not just its value) is substituted for the formal parameter par2Ref
. So that when the following code is executed:
par2Ref = 222;
it is the same as if the following were executed:
n2 = 222;
Thus, the value of the variable n2
is changed when the function body is executed, so as the dialogue shows, the value of n2
is changed from 2
to 222
by the function call.
If you keep in mind the lesson of Display 5.6, it is easy to decide which parameter mechanism to use. If you want a function to change the value of a variable, then the corresponding formal parameter must be a call-by-reference formal parameter and must be marked with the ampersand sign, &
. In all other cases, you can use a call-by-value formal parameter.
What is the output of the following program?
#include <iostream>
void figureMeOut(int& x, int y, int& z);
int main()
{
using namespace std;
int a, b, c;
a = 10;
b = 20;
c = 30;
figureMeOut(a, b, c);
cout << a << " " << b << " " << c;
return 0;
}
void figureMeOut(int& x, int y, int& z)
{
using namespace std;
cout << x << " " << y << " " << z << endl;
x = 1;
y = 2;
z = 3;
cout << x << " " << y << " " << z << endl;
}
What would be the output of the program in Display 5.4 if you omit the ampersands, &
, from the first parameter in the function declaration and function heading of swapValues
? The ampersand is not removed from the second parameter.
What would be the output of the program in Display 5.6 if you change the function declaration for the function doStuff
to the following and you change the function header to match, so that the formal parameter par2Ref
is changed to a call-by-value parameter:
void doStuff(int par1Value, int par2Ref);
Write a void
function definition for a function called zeroBoth
that has two reference parameters, both of which are variables of type int
, and sets the values of both variables to 0
.
Write a void
function definition for a function called addTax
. The function addTax
has two formal parameters: taxRate
, which is the amount of sales tax expressed as a percentage, and cost
, which is the cost of an item before tax. The function changes the value of cost
so that it includes sales tax.
Can a function that returns a value have a call-by-reference parameter? May a function have both call-by-value and call-by-reference parameters?