|

C++
enables you to create more than one function with
the same name. This is called function overloading.
The functions must differ in their parameter list,
with a different type of parameter, a different
number of parameters, or both. Here’s an example:
int
myFunction (int, int);
int
myFunction (long, long);
int
myFunction (long);
myFunction()
is overloaded with three different parameter lists.
The first and second versions differ in the types
of the parameters, and the third differs in the
number of parameters.
The
return types can be the same or different on overloaded
functions. You should note that two functions with
the same name and parameter list, but different
return types, generate a compiler error.
New Term: Function overloading
i s also called function polymorphism.
Poly means many, and morph means form: a polymorphic
function is many-formed.
Function
polymorphism refers to the ability to "overload"
a function with more than one meaning. By changing
the number or type of the parameters, you can give
two or more functions the same function name, and
the right one will be called by matching the parameters
used. This allows you to create a function that
can average integers, doubles, and other values
without having to create individual names for each
function, such as AverageInts(), AverageDoubles(),
and so on.
Suppose
you write a function that doubles whatever input
you give it. You would like to be able to pass in
an int, a long, a float, or a double. Without function
overloading, you would have to create four function
names:
int
DoubleInt(int);
long
DoubleLong(long);
float
DoubleFloat(float);
double
DoubleDouble(double);
With
function overloading, you make this declaration:
int
Double(int);
long
Double(long);
float
Double(float);
double
Double(double);
This
is easier to read and easier to use. You don’t have
to worry about which one to call; you just pass
in a variable, and the right function is called
automatically. Listing 5.8 illustrates the use of
function overloading.
Listing
5.8. A demonstration of function polymorphism.
1:
// Listing 5.8 - demonstrates
2:
// function polymorphism
3:
4:
#include <iostream.h>
5:
6:
int Double(int);
7:
long Double(long);
8:
float Double(float);
9:
double Double(double);
10:
11:
int main()
12:
{
13:
int myInt = 6500;
14:
long myLong = 65000;
15:
float myFloat = 6.5F;
16:
double myDouble = 6.5e20;
17:
18:
int doubledInt;
19:
long doubledLong;
20:
float doubledFloat;
21:
double doubledDouble;
22:
23:
cout << "myInt: " << myInt <<
"\n";
24:
cout << "myLong: " << myLong <<
"\n";
25:
cout << "myFloat: " << myFloat <<
"\n";
26:
cout << "myDouble: " << myDouble <<
"\n";
27:
28:
doubledInt = Double(myInt);
29:
doubledLong = Double(myLong);
30:
doubledFloat = Double(myFloat);
31:
doubledDouble = Double(myDouble);
32:
33:
cout << "doubledInt: " << doubledInt
<< "\n";
34:
cout << "doubledLong: " << doubledLong
<< "\n";
35:
cout << "doubledFloat: " << doubledFloat
<< "\n";
36:
cout << "doubledDouble: " << doubledDouble
<< "\n";
37:
38:
return 0;
39:
}
40:
41:
int Double(int original)
42:
{
43:
cout << "In Double(int)\n";
44:
return 2 * original;
45:
}
46:
47:
long Double(long original)
48:
{
49:
cout << "In Double(long)\n";
50:
return 2 * original;
51:
}
52:
53:
float Double(float original)
54:
{
55:
cout << "In Double(float)\n";
56:
return 2 * original;
57:
}
58:
59:
double Double(double original)
60:
{
61:
cout << "In Double(double)\n";
62:
return 2 * original;
63:
}
Output:
myInt: 6500
myLong: 65000
myFloat: 6.5
myDouble: 6.5e+20
In Double(int)
In Double(long)
In Double(float)
In Double(double)
DoubledInt: 13000
DoubledLong: 130000
DoubledFloat: 13
DoubledDouble: 1.3e+21
Analysis: The Double()function is overloaded
with int, long, float, and double. The prototypes
are on lines 6-9, and the definitions are on lines
41-63. In the body of the main program, eight local
variables are declared. On lines 13-16, four of
the values are initialized, and on lines 28-31,
the other four are assigned the results of passing
the first four to the Double() function. Note that
when Double() is called, the calling function does
not distinguish which one to call; it just passes
in an argument, and the correct one is invoked.
The
compiler examines the arguments and chooses which
of the four Double() functions to call. The output
reveals that each of the four was called in turn,
as you would expect.
Special
Topics About Functions
Because
functions are so central to programming, a few special
topics arise which might be of interest when you
confront unusual problems. Used wisely, inline functions
can help you squeak out that last bit of performance.
Function recursion is one of those wonderful, esoteric
bits of programming which, every once in a while,
can cut through a thorny problem otherwise not easily
solved.
Inline
Functions
When
you define a function, normally the compiler creates
just one set of instructions in memory. When you
call the function, execution of the program jumps
to those instructions, and when the function returns,
execution jumps back to the next line in the calling
function. If you call the function 10 times, your
program jumps to the same set of instructions each
time. This means there is only one copy of the function,
not 10.
There
is some performance overhead in jumping in and out
of functions. It turns out that some functions are
very small, just a line or two of code, and some
efficiency can be gained if the program can avoid
making these jumps just to execute one or two instructions.
When programmers speak of efficiency, they usually
mean speed: the program runs faster if the function
call can be avoided.
If
a function is declared with the keyword inline,
the compiler does not create a real function: it
copies the code from the inline function directly
into the calling function. No jump is made; it is
just as if you had written the statements of the
function right into the calling function.
Note
that inline functions can bring a heavy cost. If
the function is called 10 times, the inline code
is copied into the calling functions each of those
10 times. The tiny improvement in speed you might
achieve is more than swamped by the increase in
size of the executable program. Even the speed increase
might be illusory. First, today’s optimizing compilers
do a terrific job on their own, and there is almost
never a big gain from declaring a function inline.
More important, the increased size brings its own
performance cost.
What’s
the rule of thumb? If you have a small function,
one or two statements, it is a candidate for inline.
When in doubt, though, leave it out. Listing 5.9
demonstrates an inline function.
Listing
5.9. Demonstrates an inline function.
1:
// Listing 5.9 - demonstrates inline functions
2:
3:
#include <iostream.h>
4:
5:
inline int Double(int);
6:
7:
int main()
8:
{
9:
int target;
10:
11:
cout << "Enter a number to work with: ";
12:
cin >> target;
13:
cout << "\n";
14:
15:
target = Double(target);
16:
cout << "Target: " << target <<
endl;
17:
18:
target = Double(target);
19:
cout << "Target: " << target <<
endl;
20:
21:
22:
target = Double(target);
23:
cout << "Target: " << target <<
endl;
24:
return 0;
25:
}
26:
27:
int Double(int target)
28:
{
29:
return 2*target;
30:
}
Output:
Enter a number to work with: 20
Target: 40
Target: 80
Target: 160
Analysis: On line 5, Double() is declared
to be an inline function taking an int parameter
and returning an int. The declaration is just like
any other prototype except that the keyword inline
is prepended just before the return value.
This
compiles into code that is the same as if you had
written the following:
target
= 2 * target;
everywhere
you entered
target
= Double(target);
By
the time your program executes, the instructions
are already in place, compiled into the OBJ file.
This saves a jump in the execution of the code,
at the cost of a larger program.
NOTE: Inline is a hint to the compiler
that you would like the function to be inlined.
The compiler is free to ignore the hint and make
a real function call.
Recursion
A
function can call itself. This is called recursion,
and recursion can be direct or indirect. It is direct
when a function calls itself; it is indirect recursion
when a function calls another function that then
calls the first function.
Some
problems are most easily solved by recursion, usually
those in which you act on data and then act in the
same way on the result. Both types of recursion,
direct and indirect, come in two varieties: those
that eventually end and produce an answer, and those
that never end and produce a runtime failure. Programmers
think that the latter is quite funny (when it happens
to someone else).
It
is important to note that when a function calls
itself, a new copy of that function is run. The
local variables in the second version are independent
of the local variables in the first, and they cannot
affect one another directly, any more than the local
variables in main() can affect the local variables
in any function it calls, as was illustrated in
Listing 5.4.
To
illustrate solving a problem using recursion, consider
the Fibonacci series:
1,1,2,3,5,8,13,21,34...
Each
number, after the second, is the sum of the two
numbers before it. A Fibonacci problem might be
to determine what the 12th number in the series
is.
One
way to solve this problem is to examine the series
carefully. The first two numbers are 1. Each subsequent
number is the sum of the previous two numbers. Thus,
the seventh number is the sum of the sixth and fifth
numbers. More generally, the nth number is the sum
of n - 2 and n - 1, as long as n > 2.
Recursive
functions need a stop condition. Something must
happen to cause the program to stop recursing, or
it will never end. In the Fibonacci series, n <
3 is a stop condition.
The
algorithm to use is this:
1.
Ask the user for a position in the series.
2.
Call the fib() function with that position, passing
in the value the user entered.
3.
The fib() function examines the argument (n).
If n < 3 it returns 1; otherwise, fib() calls
itself (recursively) passing in n-2, calls itself
again passing in n-1, and returns the sum.
If
you call fib(1), it returns 1. If you call fib(2),
it returns 1. If you call fib(3), it returns the
sum of calling fib(2) and fib(1). Because fib(2)
returns 1 and fib(1) returns 1, fib(3) will return
2.
If
you call fib(4), it returns the sum of calling fib(3)
and fib(2). We’ve established that fib(3) returns
2 (by calling fib(2) and fib(1)) and that fib(2)
returns 1, so fib(4) will sum these numbers and
return 3, which is the fourth number in the series.
Taking
this one more step, if you call fib(5), it will
return the sum of fib(4) and fib(3). We’ve established
that fib(4) returns 3 and fib(3) returns 2, so the
sum returned will be 5.
This
method is not the most efficient way to solve this
problem (in fib(20) the fib() function is called
13,529 times!), but it does work. Be careful: if
you feed in too large a number, you’ll run out of
memory. Every time fib() is called, memory is set
aside. When it returns, memory is freed. With recursion,
memory continues to be set aside before it is freed,
and this system can eat memory very quickly. Listing
5.10 implements the fib() function.
WARNING: When you run Listing 5.10,
use a small number (less than 15). Because this
uses recursion, it can consume a lot of memory.
Listing
5.10. Demonstrates recursion using the Fibonacci
series.
1:
// Listing 5.10 - demonstrates recursion
2:
// Fibonacci find.
3:
// Finds the nth Fibonacci number
4:
// Uses this algorithm: Fib(n) = fib(n-1) + fib(n-2)
5:
// Stop conditions: n = 2 || n = 1
6:
7:
#include <iostream.h>
8:
9:
int fib(int n);
10:
11:
int main()
12:
{
13:
14:
int n, answer;
15:
cout << "Enter number to find: ";
16:
cin >> n;
17:
18:
cout << "\n\n";
19:
20:
answer = fib(n);
21:
22:
cout << answer << " is the " <<
n << "th Fibonacci number\n";
23:
return 0;
24:
}
25:
26:
int fib (int n)
27:
{
28:
cout << "Processing fib(" << n <<
")... ";
29:
30:
if (n < 3 )
31:
{
32:
cout << "Return 1!\n";
33:
return (1);
34:
}
35:
else
36:
{
37:
cout << "Call fib(" << n-2 <<
") and fib(" << n-1 << ").\n";
38:
return( fib(n-2) + fib(n-1));
39:
}
40:
}
Output:
Enter number to find: 5
Processing fib(5)... Call fib(3) and fib(4).
Processing fib(3)... Call fib(1) and fib(2).
Processing fib(1)... Return 1!
Processing fib(2)... Return 1!
Processing fib(4)... Call fib(2) and fib(3).
Processing fib(2)... Return 1!
Processing fib(3)... Call fib(1) and fib(2).
Processing fib(1)... Return 1!
Processing fib(2)... Return 1!
5 is the 5th Fibonacci number
Analysis: The program asks for a number
to find on line 15 and assigns that number to target.
It then calls fib() with the target. Execution branches
to the fib() function, where, on line 28, it prints
its argument. The argument n is tested to see whether
it equals 1 or 2 on line 30; if so, fib() returns.
Otherwise, it returns the sums of the values returned
by calling fib() on n-2 and n-1.
In
the example, n is 5 so fib(5) is called from main().
Execution jumps to the fib() function, and n is
tested for a value less than 3 on line 30. The test
fails, so fib(5) returns the sum of the values returned
by fib(3) and fib(4). That is, fib() is called on
n-2 (5 - 2 = 3) and n-1 (5 - 1 = 4). fib(4) will
return 3 and fib(3) will return 2, so the final
answer will be 5.
Because
fib(4) passes in an argument that is not less than
3, fib() will be called again, this time with 3
and 2. fib(3) will in turn call fib(2) and fib(1).
Finally, the calls to fib(2) and fib(1) will both
return 1, because these are the stop conditions.
The
output traces these calls and the return values.
Compile, link, and run this program, entering first
1, then 2, then 3, building up to 6, and watch the
output carefully. Then, just for fun, try the number
20. If you don’t run out of memory, it makes quite
a show!
Recursion
is not used often in C++ programming, but it can
be a powerful and elegant tool for certain needs.
NOTE: Recursion is a very tricky part
of advanced programming. It is presented here because
it can be very useful to understand the fundamentals
of how it works, but don’t worry too much if you
don’t fully understand all the details.
How
Functions Work (A Look Under the Hood)
When
you call a function, the code branches to the called
function, parameters are passed in, and the body
of the function is executed. When the function completes,
a value is returned (unless the function returns
void), and control returns to the calling function.
How
is this task accomplished? How does the code know
where to branch to? Where are the variables kept
when they are passed in? What happens to variables
that are declared in the body of the function? How
is the return value passed back out? How does the
code know where to resume?
Most
introductory books don’t try to answer these questions,
but without understanding this information, you’ll
find that programming remains a fuzzy mystery. The
explanation requires a brief tangent into a discussion
of computer memory.
|