Section 4 -  Formal Parameters and Overloading

5B.4.1 Parameters vs. Global Variables

Many students ask why use parameters at all, since we can do the same thing with global variables more easily.

The answer is that the question contains a fallacy. We can't do the same thing, and, besides, it wouldn't be any easier if we could. Here's why.

Consider our friend adder():

double adder(double a, double b)
{
return a + b;
}

(I got rid of temp, because it was not really needed.) Using parameters (as we should) we can call adder() with many different arguments:

y = adder(3, 5);
y = adder(x - 1, z + 3);
y = adder(a, b); // a, b local to main,
// different from adder's params

However, what if we removed the formal parameters from the definition of adder() and instead tried to use global variables, a and b to pass the information?

// global data
double a, b;

// method definitions
double adder()
{
return a + b;
}

// main and rest of program ...

The method calls would look very different and much more confusing.  We could no longer pass adder() arguments.  Instead we would have to "prep" the global variables prior to the method call:

a = 3;
b = 5;
y = adder();

or

a = x - 1;
b = z + 3;
y = adder();

While this is bad enough, if we were to try to translate this call:

y = adder(a, b); // a, b local to main,
// different from adder's params

into a similar parameter-less function call of three statements, we would have a real problem.  I know this might be confusing to you, and it should be - this sort of mind bending confusion is caused when local variables have the same names as global variables making the parameter-less version of this call impossible.  (If you are not confused by all this, you can see what you would have to do to make it work.  But don't try too hard - the whole point is that using parameters rather than global variables is the right thing to do.)

All of this busy nonsense defeats the purpose of methods, which is to hide the messy details from the client and tuck them away into modular functions.

A second reason, equally important, is the "visibility" of global variables. As you know, all methods in the class can use and modify them. The more members you use, the more prone your program is to having one of the methods accidentally modify them.

5B.4.2 The Local Behavior of Formal Parameters

Look at this hypothetical method:

float adder(float a, float b)
{
return a + (b++);
}

If it is called by:

y = adder(one, two);

is the value of two changed in the client when the call is complete?

No. If you go back to my description of how arguments are passed to methods, you see that the value of the argument is copied down into the formal parameter. That parameter is local, and is only a  copy of the client's object. This is a good thing. It prevents the method from modifying the client's data.

If the client wants a value changed it can go back to using global variables, but we now know that global variables are not meant for this purpose. Or it can copy the return value into one of its objects:

y = f(a, b, c);

But what if we don't want to use global variables, and what if we need more than one value modified by the method?   This actually happened to us in the getInput() method of our mortgage calculator.  We needed three objects returned to the client and since functional returns can give us only one, we were forced to use global variables. 

We have two choices in C++. One, left over from the ancestor language C, involves pointers. Another, introduced in C++, makes use of the concept of reference and reference parameters, which we will get to later.

5B.4.3 An Important Summary

This last discussion was so important it is worth summarizing without all the excess verbiage.

Changes to Parameters from inside Methods

When you pass a value to a method as an ordinary argument,  the method cannot make any permanent change to that argument.  When the method returns to the client, whatever value the variable had before is still there.

Notice that in the above statement I said this applies to ordinary arguments.  When we get to reference parameters, arrays and pointers, we'll find a way around this restriction.

5B.4.4 Method Signatures

When we talk about a function (method) informally, we often omit the parameter details.  I might say, "adder() this ..." or "adder() that ... " in a sentence, but the parentheses are placed there to emphasize that it is a function.  They do not mean that the function takes no parameters.  The signature of the adder() function is shown when we describe it fully.  It is double adder(double a, double b).  This is the official description of the function.  It is called the signature of the function or method.

It is very important that you implement functions according to the signature that is specified.  Even though I might put empty parens after the method name when discussing it in a sentence, like pow() or adder() or SetAge(), you must look for the official description and implement that.  If the function signature that you provide does not match 100% with the function signature that is in the program spec, it is as if  you did not write that function at all.  That's right -- an incorrect signature is worth nothing.  The reason is that the rest of your project group assumed that you were writing to spec and if you did not, they can't use any of your function. They have to go back and write the function correctly from scratch. It is as if you did not exist!  So this is not a fussy detail, but a real requirement.  Don't get creative with methods. Write to spec.

5B.4.5 Method Overloading

Two methods with different signatures like GetInstructions() and double adder(double a, double b) are clearly distinct - they do different things, have different names and take different parameters.  But can we have two methods with the same name that take different parameter types, i.e., two methods with the same name but different signatures, like void AddTime( int minutes ) and void AddTime( string minutes )?  Yes we can.  We simply define the two methods separately, as if they were completely distinct (which, in fact they are).  Each function header will differ only in that the parameter list will be different.  Each can behave differently. 

Why would we do this?

  1. If both methods do virtually the same thing, we may want to give them the same name.
  2. If we want to allow clients to call the method either using one type of parameter (int) or another (string).

If you give two methods (functions) the same name but a different parameter list, then you are overloading the methods (functions)

Here is a simple example of function overloading that demonstrates the idea:

// ---------------- SOURCE -------------------------
#include <iostream>
#include <string>
using namespace std;

// prototypes
void displayTemperature( int temp );
void displayTemperature( string temp);
void sayThanks();
void sayThanks(string special_note);

int main()
{
displayTemperature(98);
cout << endl;
displayTemperature("99.4");
cout << endl;

sayThanks();
cout << endl;
sayThanks("I just wanted to tell you how much we enjoyed the movie.");
cout << endl;

return 0;
}

void displayTemperature(int temp)
{
cout << "The patient's temperature is: " << temp << endl;
}
void displayTemperature(string temp)
{
cout << "The patient's temperature is: " << temp << endl;
}

void sayThanks()
{
cout << "Thank you!" << endl;
}

void sayThanks(string special_note)
{
cout << special_note << endl;
sayThanks();
}
/* ------------- RUN (OUTPUT) ----------------------
The patient's temperature is: 98

The patient's temperature is: 99.4

Thank you!

I just wanted to tell you how much we enjoyed the movie.
Thank you!

Press any key to continue . . .
--------------- END OF RUN ------------------------- */

Notice that we can call one overloaded variant (sayThanks()) from another (sayThanks(string)), and this is often desirable.  If we are going to do many of the same tasks in both methods, then we should try to concentrate those common tasks into one of the variants and let the other variant call the first to reduce code duplication.