C++

OVERLOADING  FUNCTIONS

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.

Back to Index