Why?
Up until now, we've been limited to returning one value or no values from our functions. In math, a function always resulted in only one value, so being able to return nothing was an 'improvement' already. But programmers -- especially in the object-oriented arena -- are all about simulating real-world processes. And tasks in the real world often have more than one result. For instance, when you pay for something with cash, you get back an amount of money, but it is physically represented by several denominations of coins and/or bills. For a function to represent this kind of transaction, it would have to take in the cost amount and the tendered amount and then produce the number of each possible denomination of coin and bill (some of which might be 0). With simply the return mechanism for sending our function's output back to the caller, we'd be limited to using side-effects for this kind of thing. Side effects aren't a good thing to rely on since they are often platform dependent and the world seems to like to change platforms quite often (it used to be that console program interfaces ruled the world; now most user interfaces are graphical in nature).
Metaphorically, the return mechanism is more like a game of catch: the caller throws the ball (passes its inputs) to the function and then awaits the function to throw the ball (return its outputs) back. The so-called reference mechanism, however, is more like having a business card for a company/service/person so that you don't have to actually carry the whole building around with you or constantly go to the person's office to do things. You can simply call them up or email them or [snail] mail them because you can use those types of references that are listed on the card.
The way programming languages support this type of multiple result function is by allowing the function to 'refer to' one or more of the caller's memory locations. If the function can directly refer to a caller's memory, it can simply write a new value there so that the caller will remember it upon the function's return. This is different from the return mechanism in that there the caller is actively waiting for a value to be returned so that it may use it in certain ways.
C++ implements the reference mechanism of effecting results, like many languages do, by tweaking the argument list. At first glance, this can be disturbing: the argument list is where our inputs are...now you're mixing in the outputs! *yeuck* Never fear! We are going to clearly label any argument to be used for effecting an output so as to distinguish it easily from those arguments being used purely for input. The C++ notation for this is a single ampersand (&) placed after the formal argument's data type (i.e. between its data type and its identifier): output_argument_type & output_argument_name. Now you can tell the 'output arguments' from the 'input arguments' by the mere presence or absence (respectively) of an ampersand (&)!
For example, let's say you wanted to take the code that reads the two coordinates for a single 2D point from the user and place it in a function. This function's prototype might look like this:
// Reads coordinates for a 2D point from the user and stores // them in the caller's 'x' and 'y' memory locations. Standard // notation is expected, but not specifically enforced. void read_coordinate(double & x, double & y);
This function would require two arguments to be specified by the caller. Neither is to be used by the function as input, but rather for effecting output. The & placed after the types (and therefore before the names) of the arguments says that they are actually for effecting output -- not for input. In terms of what happens during a call, this causes x and y to not take up their own local memory space, but rather to simply refer to the caller's memory space -- as directed.
Vocabulary/
Terminology
This means of effecting output via an argument is, as noted, a significant change to our argument list, so we need a bit of terminology to deal with it more clearly...of course. Arguments which are purely for input are said to be using the value mechanism for argument passing whereas these new arguments which can effect results are said to be using the reference mechanism of argument passing. Both of these have been twiddled over the years by lazy or persnickety humans to give us a plethora of versions:
Value Mechanism | Reference Mechanism |
---|---|
value mechanism | reference mechanism |
pass-by-value mechanism | pass-by-reference mechanism |
by-value mechanism | by-reference mechanism |
by-value argument | by-reference argument |
value argument | reference argument |
As a typically lazy programmer, I'll tend to use the last version as it is the most succinct.
(A Fairly)
Complete
Example
Let's expand on the read_coordinate function mentioned above by looking at it in a more complete context. Here's a skeleton program showing the prototype and definition of read_coordinate as well as some details of some portion of the program which would call it. (Note how we've used the .... to denote portions of the caller's code which we don't really care about.)
-------------------------------------------------------------------- // Reads coordinates for a 2D point from the user and stores // them in the caller's 'x' and 'y' memory locations. Standard // notation is expected, but not specifically enforced. void read_coordinate(double & x, double & y); -------------------------------------------------------------------- .... a_caller(....) { double x, y; .... read_coordinate(x, y); .... } -------------------------------------------------------------------- // Reads coordinates for a 2D point from the user and stores // them in the caller's 'x' and 'y' memory locations. Standard // notation is expected, but not specifically enforced. void read_coordinate(double & x, double & y) { char t; cout << "Enter your XY coordinate: "; cin >> t >> x >> t >> y >> t; return; } --------------------------------------------------------------------
Note how the return statement is empty even though the function has two outputs. This is because the return statement matches with the return value (which is void here) and not strictly with the number of outputs.
Also note that the names of formal and actual arguments still do NOT have to match, even though we are really passing the actual argument's memory space to the formal argument:
-------------------------------------------------------------------- // Reads coordinates for a 2D point from the user and stores // them in the caller's 'x' and 'y' memory locations. Standard // notation is expected, but not specifically enforced. void read_coordinate(double & x, double & y); -------------------------------------------------------------------- .... a_caller(....) { .... double point1_x, point1_y, point2_x, point2_y; .... read_coordinate(point1_x, point1_y); read_coordinate(point2_x, point2_y); cout << "Travelling from (" << point1_x << ", " << point1_y << ") to (" point2_x << ", " << point2_y << ")." << endl; .... } -------------------------------------------------------------------- // Reads coordinates for a 2D point from the user and stores // them in the caller's 'x' and 'y' memory locations. Standard // notation is expected, but not specifically enforced. void read_coordinate(double & x, double & y) { char t; cout << "Enter your XY coordinate: "; cin >> t >> x >> t >> y >> t; return; } --------------------------------------------------------------------
See how we called the function twice to do our bidding -- even with different actual arguments! (The exact way this works is given below in a detailed description of how function the call mechanism works.)
Function
Design
Tip
But, did you notice how the read_coordinate function gives the same [cheesy] prompt for both the starting point and the ending point?
One way to fix this would be by altering the calling function to give the user a clearer (more clear?) prompt before it calls the read_coordinate function to do the primary work:
-------------------------------------------------------------------- // Reads coordinates for a 2D point from the user and stores // them in the caller's 'x' and 'y' memory locations. Standard // notation is expected, but not specifically enforced. void read_coordinate(double & x, double & y); -------------------------------------------------------------------- .... a_caller(....) { .... double point1_x, point1_y, point2_x, point2_y; .... cout << "First I need your starting point:" << endl; read_coordinate(point1_x, point1_y); cout << "Now I need your destination point:" << endl; read_coordinate(point2_x, point2_y); cout << "Travelling from (" << point1_x << ", " << point1_y << ") to (" point2_x << ", " << point2_y << ")." << endl; .... } -------------------------------------------------------------------- // Reads coordinates for a 2D point from the user and stores // them in the caller's 'x' and 'y' memory locations. Standard // notation is expected, but not specifically enforced. void read_coordinate(double & x, double & y) { char t; cout << "Enter your XY coordinate: "; cin >> t >> x >> t >> y >> t; return; } --------------------------------------------------------------------
Now the user will see that the first XY coordinate is for a starting point and the second one is for a destination point -- and we didn't even change the function itself!
However, we can do even better!
If you notice that the prompts are ALL simply strings... then we can generalize our function as much as possible by doing one of two things: parameterize the string prompts or simply allow the caller to manage all of the prompting (which could still benefit from the use of strings).
Here are a few alternative ways to take those prompts and parameterize them/their use:
-------------------------------------------------------------------- // Reads coordinates for a 2D point from the user and stores // them in the caller's 'x' and 'y' memory locations. Standard // notation is expected, but not specifically enforced. void read_coordinate(double & x, double & y, bool prompt); -------------------------------------------------------------------- .... a_caller(....) { .... double point1_x, point1_y, point2_x, point2_y; .... cout << "First I need your starting point:" << endl; read_coordinate(point1_x, point1_y, true); cout << "Now I need your destination point:" << endl; read_coordinate(point2_x, point2_y, false); cout << "Travelling from (" << point1_x << ", " << point1_y << ") to (" point2_x << ", " << point2_y << ")." << endl; .... } -------------------------------------------------------------------- void read_coordinate(double & x, double & y, bool prompt) { char t; if (prompt) { cout << "Enter your XY coordinate: "; } cin >> t >> x >> t >> y >> t; return; } --------------------------------------------------------------------Notice how the caller can select whether they want the generalized prompt inside the function to be printed or not by passing true or false (respectively). A sample of watching this run might look like so:
First I need your starting point: Enter your XY coordinate: (42, 2) Now I need your destination point: (44,4)(Hmm...maybe some spacing is in order for the second one...*shrug* As they say, "This is left as an exercise to the interested reader." *grin*)
-------------------------------------------------------------------- // Reads coordinates for a 2D point from the user and stores // them in the caller's 'x' and 'y' memory locations. Standard // notation is expected, but not specifically enforced. const string CoordPromptDefault = "Enter your XY coordinate: "; void read_coordinate(double & x, double & y, string prompt); -------------------------------------------------------------------- .... a_caller(....) { .... double point1_x, point1_y, point2_x, point2_y; .... cout << "First I need your starting point:" << endl; read_coordinate(point1_x, point1_y, CoordPromptDefault); cout << "Now I need your destination point:" << endl; read_coordinate(point2_x, point2_y, ""); cout << "Travelling from (" << point1_x << ", " << point1_y << ") to (" point2_x << ", " << point2_y << ")." << endl; .... } -------------------------------------------------------------------- void read_coordinate(double & x, double & y, string prompt) { char t; cout << prompt; cin >> t >> x >> t >> y >> t; return; } --------------------------------------------------------------------This time, the caller can control whether the generalized prompt is displayed by passing it (note that it was named and moved to a global constant string for the caller's convenience) or passing an empty string (""). (Recall that displaying an empty string in fact displays nothing.)
-------------------------------------------------------------------- const string CoordPromptDefault = "Enter your XY coordinate: "; void read_coordinate(double & x, double & y, string prompt); -------------------------------------------------------------------- .... a_caller(....) { .... double point1_x, point1_y, point2_x, point2_y; .... read_coordinate(point1_x, point1_y, "First I need your starting point:\n" + CoordPromptDefault); read_coordinate(point2_x, point2_y, "Now I need your destination point:\n"); cout << "Travelling from (" << point1_x << ", " << point1_y << ") to (" point2_x << ", " << point2_y << ")." << endl; .... } -------------------------------------------------------------------- void read_coordinate(double & x, double & y, string prompt) { char t; cout << prompt; cin >> t >> x >> t >> y >> t; return; } --------------------------------------------------------------------Notice how this version of read_coordinate is identical to the one above, but the caller has entirely taken control of prompt content and yet made us do all the work.
Any of the above ways can be easily applied to similar situations, of course.
Letting the Caller Manage the Prompts |
(And How strings Can Help Them) |
Here are a few other ways to allow the caller to manage the prompts for us (my aren't we the lazy programmers?):
-------------------------------------------------------------------- // Reads coordinates for a 2D point from the user and stores // them in the caller's 'x' and 'y' memory locations. Standard // notation is expected, but not specifically enforced. void read_coordinate(double & x, double & y); -------------------------------------------------------------------- .... a_caller(....) { .... double point1_x, point1_y, point2_x, point2_y; .... cout << "First I need your starting point:" << endl << "Enter your XY coordinate: "; read_coordinate(point1_x, point1_y); cout << "Now I need your destination point:" << endl << "Enter your XY coordinate: "; read_coordinate(point2_x, point2_y); cout << "Travelling from (" << point1_x << ", " << point1_y << ") to (" point2_x << ", " << point2_y << ")." << endl; .... } -------------------------------------------------------------------- void read_coordinate(double & x, double & y) { char t; cin >> t >> x >> t >> y >> t; return; } --------------------------------------------------------------------At least the function doesn't have to worry about it anymore!
-------------------------------------------------------------------- // Reads coordinates for a 2D point from the user and stores // them in the caller's 'x' and 'y' memory locations. Standard // notation is expected, but not specifically enforced. void read_coordinate(double & x, double & y); -------------------------------------------------------------------- .... a_caller(....) { .... const string general_instr = "Enter your XY coordinate: "; const string fore_prompt = " I need your ", aft_prompt = " point:\n"; double point1_x, point1_y, point2_x, point2_y; .... cout << "First" + fore_prompt + "starting" + aft_prompt + general_instr; read_coordinate(point1_x, point1_y); cout << "Now" + fore_prompt + "destination" + aft_prompt + general_instr; read_coordinate(point2_x, point2_y); cout << "Travelling from (" << point1_x << ", " << point1_y << ") to (" point2_x << ", " << point2_y << ")." << endl; .... } -------------------------------------------------------------------- void read_coordinate(double & x, double & y) { char t; cin >> t >> x >> t >> y >> t; return; } --------------------------------------------------------------------Again, our function is clean and clear of specifics that would prevent its easy re-use in many programs/applications.
Caveat
Please note, though, that the actual arguments passed through a reference argument (as those with the & are called) MUST be variables. They cannot be literal values. They cannot be constants. They cannot be expressions (including function call results). They can only be variables.
(Okay...so I'm lying...but only a little bit! The actual arguments that line up with a reference argument must be memory locations to which the function's formal argument can refer. For right now, this means they have to be variables ...or at least parts of variables. Note that you can pass a single character of a string object to a function with the reference mechanism in such a way as:
-------------------------------------------------------------------- // Swaps the contents of caller's two specified memory locations. void swap(char & a, char & b); -------------------------------------------------------------------- .... a_caller(....) { string s; string::size_type left, right; .... swap(s[left], s[right]); .... } -------------------------------------------------------------------- // Swaps the contents of caller's two specified memory locations. void swap(char & a, char & b) { char c = a; // remember first one a = b; // move second to first location b = c; // overwrite second with memory of first return; // content of a and b is now reversed } --------------------------------------------------------------------
Here the characters at positions left and right within the string s will be switched with one another. That is, if s had the value boozes and left was 2 and right was 3 before the call to swap, then after the call, s would have the value bozoes (leaving left and right as 2 and 3, respectively, btw).
A Program
As mentioned above, when actual arguments are passed into formal arguments "by value" (or "using the value mechanism" or "pass-by-value mechanism" or "the formal arguments are value arguments" or...), merely the values of the actual arguments are copied into the formal arguments. This implies that the formal arguments get their own memory space. Indeed, this is true. Let's examine the following program:
#include <iostream> #include <cmath> using namespace std; // Reads coordinates for a 2D point from the user and stores // them in the caller's 'x' and 'y' memory locations. Standard // notation is expected, but not specifically enforced. Caller // is expected to provide a prompt if desired!!! void read_coordinate(double & x, double & y); // Calculates the distance between Euclidean points (x1,y1) and // (x2,y2). Returns result. double distance(double x1, double y1, double x2, double y2); int main(void) { double start_x, start_y, // starting position end_x, end_y, // ending position dist_between; // distance between start // and end positions cout << "First enter information about where you'll be starting:" << endl; read_coordinate(start_x, start_y); cout << "Next enter information about where you'll be ending:" << endl; read_coordinate(end_x, end_y); dist_between = distance(start_x, start_y, end_x, end_y); cout << "While travelling from (" << start_x << ", " << start_y << ") to (" end_x << ", " << end_y << ")\nyou'll go " << dist_between << " miles." << endl; return 0; } // Calculates the distance between Euclidean points (x1,y1) and // (x2,y2). Returns result. double distance(double x1, double y1, double x2, double y2) { return sqrt( pow(x1-x2, 2.0) + pow(y1-y2, 2.0) ); } // Reads coordinates for a 2D point from the user and stores // them in the caller's 'x' and 'y' memory locations. Standard // notation is expected, but not specifically enforced. Caller // is expected to provide a prompt if desired!!! void read_coordinate(double & x, double & y) { char t; cin >> t >> x >> t >> y >> t; return; }
In memory, main [can be thought to] look[s] something like this:
+--------------------------------------------------------------+ | +------------+ | | main | | | | +------------+ | +--------------------------------------------------------------+ | +------------+ +------------+ | | start_x | | start_y | | | | +------------+ +------------+ | | +------------+ +------------+ | | end_x | | end_y | | | | +------------+ +------------+ | | +------------+ | | dist_between | | | | +------------+ | +--------------------------------------------------------------+
Each variable has its own box and main has a box for its return value.
The Value
Mechanism
Let's start with the call to the distance function -- about half-way down the main program there. (In other words, we're assuming for the moment that all coordinates have been read in already.) Let's look at exactly how the main's variable values end up in the distance function during this call. To begin, note that main's memory might look something like this before the call:
+--------------------------------------------------------------+ | +------------+ | | main | | | | +------------+ | +--------------------------------------------------------------+ | +------------+ +------------+ | | start_x | 42 | start_y | 2 | | | +------------+ +------------+ | | +------------+ +------------+ | | end_x | 42 | end_y | 12 | | | +------------+ +------------+ | | +------------+ | | dist_between | | | | +------------+ | +--------------------------------------------------------------+
During the call, the actual arguments are lined up with the respective formal arguments and the values are copied -- position-wise -- from left to right -- 'down' into the formal arguments from the actual arguments:
distance( start_x, start_y, end_x, end_y); || || || || \/ \/ \/ \/ distance(double x1, double y1, double x2, double y2);
To place all these values in the formal arguments, distance must be fleshed out in memory:
+--------------------------------------------------------------+ | +------------+ | | distance | | | | +------------+ | +--------------------------------------------------------------+ | +------------+ +------------+ | | x1 | 42 | y1 | 2 | | | +------------+ +------------+ | | +------------+ +------------+ | | x2 | 42 | y2 | 12 | | | +------------+ +------------+ | +--------------------------------------------------------------+ +--------------------------------------------------------------+ | +------------+ | | main | | | | +------------+ | +--------------------------------------------------------------+ | +------------+ +------------+ | | start_x | 42 | start_y | 2 | | | +------------+ +------------+ | | +------------+ +------------+ | | end_x | 42 | end_y | 12 | | | +------------+ +------------+ | | +------------+ | | dist_between | | | | +------------+ | +--------------------------------------------------------------+
Note how the distance function is stacked on top of the main function in memory. This arrangement is known as the "call stack". Each function that is called has its memory stacked on top of that of the function which called it. (Because there is only so much room in the call stack, too many function calls can be deadly. This is why we won't be allowing a function to call itself either explicitly or implicitly -- the practice known as recursion -- until a later semester. If a function fails to stop calling itself this way, it will eat up all the memory in the call stack and crash the program!) Note also that the values have all been copied appropriately into the new memory locations for distance's formal arguments.
Now the return value of distance is calculated as 10.0 and stored in the box next to it. Also, all local memory space (formal arguments included) is destroyed/released:
+--------------------------------------------------------------+ | +------------+ | | distance | 10 | | | +------------+ | +--------------------------------------------------------------+ +--------------------------------------------------------------+ | +------------+ | | main | | | | +------------+ | +--------------------------------------------------------------+ | +------------+ +------------+ | | start_x | 42 | start_y | 2 | | | +------------+ +------------+ | | +------------+ +------------+ | | end_x | 42 | end_y | 12 | | | +------------+ +------------+ | | +------------+ | | dist_between | | | | +------------+ | +--------------------------------------------------------------+
Next the assignment in main copies the 10.0 from distance to the local variable dist_between and the remainder of the distance's memory use is wiped out:
+--------------------------------------------------------------+ | +------------+ | | main | | | | +------------+ | +--------------------------------------------------------------+ | +------------+ +------------+ | | start_x | 42 | start_y | 2 | | | +------------+ +------------+ | | +------------+ +------------+ | | end_x | 42 | end_y | 12 | | | +------------+ +------------+ | | +------------+ | | dist_between | 10 | | | +------------+ | +--------------------------------------------------------------+
After that, the last cout sends its information to the screen:
While travelling from (42, 2) to (42, 12) you'll go 10 miles.
Finally the return in main puts a 0 in the box on main and main's variables are destroyed/released:
+--------------------------------------------------------------+ | +------------+ | | main | 0 | | | +------------+ | +--------------------------------------------------------------+
When the operating system retrieves this value, main itself will be wiped out. (At this point you'll return to the OS/shell prompt.)
The Reference
Mechanism
But what about read_coordinate? How did we get those points in the first place? Well, the arguments on read_coordinate are reference arguments (outputs) and act differently from your garden-variety input argument. An output argument merely refers back to space allocated for the actual argument. In other words, it doesn't get its own memory space, but merely uses someone else's. So, when read_coordinate is called for the first time, it lines up as:
read_coordinate( start_x, start_y); /\ /\ || || \/ \/ read_coordinate(double & x, double & y);
(Note how the arrow goes both ways to show they are linked -- not just copied.) Then, in memory, read_coordinate will look something like this:
+--------------------------------------------------------+ | | | read_coordinate | | | +--------------------------------------------------------+ | | +---|-------x y------------------|---+ | | | | | +--------------------------------------------------------+ | | | | +--------------------------------------------------------+ | | | +------------+ | | | | main | | | | | | +------------+ | | | +--------------------------------------------------------+ | | | +------------+ +------------+ | | +---|>start_x | | start_y | |<--|---+ | +------------+ +------------+ | | +------------+ +------------+ | | end_x | | end_y | | | | +------------+ +------------+ | | +------------+ | | dist_between | | | | +------------+ | +--------------------------------------------------------+
This shows that anything that happens to x within read_coordinate is immediately reflected in the referred actual argument (start_x) in the calling function (main). Likewise for y and start_y. Thus, when the user enters 42 2, we see something like this in memory:
+--------------------------------------------------------+ | | | read_coordinate | | | +--------------------------------------------------------+ | | +---|-------x y------------------|---+ | | | | | +--------------------------------------------------------+ | | | | +--------------------------------------------------------+ | | | +------------+ | | | | main | | | | | | +------------+ | | | +--------------------------------------------------------+ | | | +------------+ +------------+ | | +---|>start_x | 42 | start_y | 2 |<--|---+ | +------------+ +------------+ | | +------------+ +------------+ | | end_x | | end_y | | | | +------------+ +------------+ | | +------------+ | | dist_between | | | | +------------+ | +--------------------------------------------------------+
Nothing happens to x and y themselves because they have no memory space. The values read in are stored directly in start_x and start_y.
When the return on read_coordinate is executed, all of the memory for read_coordinate is wiped (no return value to hold onto or store):
+--------------------------------------------------------+ | +------------+ | | main | | | | +------------+ | +--------------------------------------------------------+ | +------------+ +------------+ | | start_x | 42 | start_y | 2 | | | +------------+ +------------+ | | +------------+ +------------+ | | end_x | | end_y | | | | +------------+ +------------+ | | +------------+ | | dist_between | | | | +------------+ | +--------------------------------------------------------+
Next, when read_coordinate is called a second time, the actual and formal arguments are lined up again:
read_coordinate( end_x, end_y); /\ /\ || || \/ \/ read_coordinate(double & x, double & y);
And read_coordinate is loaded into memory again:
+--------------------------------------------------------+ | | | read_coordinate | | | +--------------------------------------------------------+ | | +---|-------x y------------------|---+ | | | | | +--------------------------------------------------------+ | | | | +--------------------------------------------------------+ | | | +------------+ | | | | main | | | | | | +------------+ | | | +--------------------------------------------------------+ | | | +------------+ +------------+ | | | | start_x | 42 | start_y | 2 | | | | | +------------+ +------------+ | | | | +------------+ +------------+ | | +---|-->end_x | | end_y | |<--|---+ | +------------+ +------------+ | | +------------+ | | dist_between | | | | +------------+ | +--------------------------------------------------------+
Now x is linked to end_x and y refers to end_y. When the user types 42 12, we see:
+--------------------------------------------------------+ | | | read_coordinate | | | +--------------------------------------------------------+ | | +---|-------x y------------------|---+ | | | | | +--------------------------------------------------------+ | | | | +--------------------------------------------------------+ | | | +------------+ | | | | main | | | | | | +------------+ | | | +--------------------------------------------------------+ | | | +------------+ +------------+ | | | | start_x | 42 | start_y | 2 | | | | | +------------+ +------------+ | | | | +------------+ +------------+ | | +---|-->end_x | 42 | end_y | 12 |<--|---+ | +------------+ +------------+ | | +------------+ | | dist_between | | | | +------------+ | +--------------------------------------------------------+
Again, as read_coordinate's return executes, the whole function leaves memory and we are back to where we assumed input (far) above:
+--------------------------------------------------------------+ | +------------+ | | main | | | | +------------+ | +--------------------------------------------------------------+ | +------------+ +------------+ | | start_x | 42 | start_y | 2 | | | +------------+ +------------+ | | +------------+ +------------+ | | end_x | 42 | end_y | 12 | | | +------------+ +------------+ | | +------------+ | | dist_between | | | | +------------+ | +--------------------------------------------------------------+