Section 2.3
Functions (II).
cplusplus.com

Arguments passed by value and by reference.

Until now, in all functions that we have seen, all the parameters passed to the functions have been passed by value. This means that when calling to a function with parameters, what we have passed to the function were values but never the specified variables themselves. For example, suppose that we called to our first function addition using the following code :
int x=5, y=3, z;
z = addition ( x , y );
What we did in this case was to call function addition passing the values of x and y, that means 5 and 3 respectively, not the variables themselves.

This way, when function addition is being called the value of its variables a and b become 5 and 3 respectively, but any modification of a or b within the function addition will not affect the values of x and y outside it, because variables x and y were not passed themselves to the the function, only their values.

But it might be a case where you need to manipulate from inside a function the value of an external variable. For that we will have to use arguments passed by reference, as in the function duplicate of the following example:

// passing parameters by reference
#include <iostream.h>

void duplicate (int& a, int& b, int& c)
{
  a*=2;
  b*=2;
  c*=2;
}

int main ()
{
  int x=1, y=3, z=7;
  duplicate (x, y, z);
  cout << "x=" << x << ", y=" << y << ", z=" << z;
  return 0;
}
x=2, y=6, z=14

The first thing that should call your attention is that in the declaration of duplicate the type of each argument went followed by an ampersand sign (&), that indeed serves to specify that the variable has to be passed by reference instead of by value, as usual.

When passing a variable by reference we are passing the variable itself and any modification that we do to that parameter within the function will have effect in the passed variable outside it.

To express it somehow, we have associated a, b and c with the parameters used when calling the function (x, y and z) and any change that we do on a within the function will affect the value of x outside. Any change that we do on b will affect y, and the same with c and z.

That is why our program's output, that shows the values stored in x, y and z after the call to duplicate, shown the values of the three variables of main duplicated.

If when declaring the following function:

void duplicate (int& a, int& b, int& c)
we had declared it thus:
void duplicate (int a, int b, int c)
that is without the ampersand (&) signs, we had not passed the variables by reference, but its value, and therefore, the output on screen of our program would have been the values of x, y and z without having been modified.

This type of declaration "by reference" using the ampersand (&) sign is exclusive of C++. In C language we had to use pointers to do something equivalent.

Passing by reference is an effective way to allow a function to return more than one single value. For example, here is a function that returns the previous and next numbers of the first parameter passed.

// more than one returning value
#include <iostream.h>

void prevnext (int x, int& prev, int& next)
{
  prev = x-1;
  next = x+1;
}

int main ()
{
  int x=100, y, z;
  prevnext (x, y, z);
  cout << "Previous=" << y << ", Next=" << z;
  return 0;
}
Previous=99, Next=101

Dafault values in arguments.

When defining a function we can specify default values that will be taken by the argument variables in case that these are avoided when the function is called. The way to do so is simply by assigning a value to the arguments when declaring the function, these values will be used if that parameter is not passed when the function is called. If the value of that parameter is finally set when calling, the default value is stepped on. For example:

// default values in functions
#include <iostream.h>

int divide (int a, int b=2)
{
  int r;
  r=a/b;
  return (r);
}

int main ()
{
  cout << divide (12);
  cout << endl;
  cout << divide (20,4);
  return 0;
}
6
5

As we can see in the body of the program there are two calls to the function divide. In the first one:

divide (12)
we have only specified one argument, but the function divide allows up to two. So the function divide has assumed that the second parameter is 2 since that it is what we have specified to it to do if this parameter lacks (notice the function declaration, which finishes by int b=2). Therefore the result of this call is 6 (12/2).

In the second call:

divide (20,4)
there are two parameters, so the default assignation (int b=2) is being stepped on by the passed parameter, that is 4. Being the result equal to 5 (20/4).

Overloaded functions.

Two functions can have the same name if the prototype of their arguments are different, that means that you can put the same name to more than a function if they have either a different number of arguments or different types in the arguments. For example,

// overloaded function
#include <iostream.h>

int divide (int a, int b)
{
  return (a/b);
}

float divide (float a, float b)
{
  return (a/b);
}

int main ()
{
  int x=5,y=2;
  float n=5.0,m=2.0;
  cout << divide (x,y);
  cout << "\n";
  cout << divide (n,m);
  return 0;
}
2
2.5

In this case we have defined two functions with the same name, but one of them accepts two arguments of type int and the other accepts them of type float. The compiler knows which to call in each case examining the types when the function is called, if it is called with two ints as arguments it calls to the function that has two int arguments in the prototype and if it is called with two floats it will call to the one which has two floats in its prototype.

For simplicity I have included the same code within both functions, but this is not compulsory. You can build two function with the same name with completely different behaviors.

inline functions.

The inline directive can be included before a function declaration to specify that the function must be compiled as code in the same point where it is called. This is equivalent to declare a macro, and its advantage is only appreciated in very short functions, in which the resulting code from compiling the program is accelerated because it does not have to call a subroutine.

Its format is:
inline type name ( arguments ... ) { instructions ... }
and the call is just like the one for any other function. It is not necessary to include the inline keyword in the call, only before the declaration.

Recursivity.

It is the property that functions have to be called by themselves. It is useful for some tasks like for example some sorting methods or to calculate the factorial of a number. For example, to obtain the factorial of a number (n) its mathematical formula is:
n! = n * (n-1) * (n-2) * (n-3) ... * 1
more concretely, 5! (factorial of 5) would be:
5! = 5 * 4 * 3 * 2 * 1 = 120
and a recursive function to do so could be this:

// factorial calculator
#include <iostream.h>

long factorial (long a)
{
  if (a > 1)
   return (a * factorial (a-1));
  else
   return (1);
}

int main ()
{
  long l;
  cout << "Type a number: ";
  cin >> l;
  cout << "!" << l << " = " << factorial (l);
  return 0;
}
Type a number: 9
!9 = 362880

Notice how in function factorial we included a call to itself, but only if the argument is greater than 1, since if not the function would perform an infinite loop in which once it arrived to 0 it would continue multiplying by all the negative numbers (probably provoking a stack overflow on runtime).

This function has a limitation because of the data type (long) used for designing it for more simplicity: in a standard system, the type long would not allow to store factorials greater than 12!.

Prototyping functions.

Until now, we have defined the functions before the first appearance of a call to it, that generally was in main, leaving the function main for the end. If you try to repeat some of the examples of functions described until now but placing the function main before any other function that is called from within it, you will most likely obtain an error. The reason is that to be able to call to a function this one must have been declared previously (it must be known), like we have done in all our examples.

But there is an alternative way to avoid to write all the code of all functions before they can be used in main or in another function. It is by prototyping functions. This consists on making a previous shorter declaration of the complete definition but quite significant so that the compiler knows the arguments and the return type needed.

Its form is:

type name ( argument_type1, argument_type2, ...);
It is identical to the header of a function definition, except: For example:

// prototyping
#include <iostream.h>

void odd (int a);
void even (int a);

int main ()
{
  int i;
  do {
    cout << "Type a number: (0 to exit)";
    cin >> i;
    odd (i);
  } while (i!=0);
  return 0;
}

void odd (int a)
{
  if ((a%2)!=0) cout << "Number is odd.\n";
  else even (a);
}

void even (int a)
{
  if ((a%2)==0) cout << "Number is even.\n";
  else odd (a);
}
Type a number (0 to exit): 9
Number is odd.
Type a number (0 to exit): 6
Number is even.
Type a number (0 to exit): 1030
Number is even.
Type a number (0 to exit): 0
Number is even.

This example is indeed not an example of effectiveness, I am sure that at this point you can already obtain the same result with half the number of code lines. But this serves to show how protyping works. Moreover, in this concrete case the prototyping of -at least- one of the two functions is necessary.

The first things that we see are the prototypes of functions odd and even:

void odd (int a);
void even (int a);
that allows these functions to be used before they are completely defined, for example, in main, which now is located in a more logical place: the beginning of the program's code. Nevertheless the specific reason why this program needs that at least one of the functions to be prototyped, is because in odd there is a call to even and in even there is a call to odd, reason why if none of the two functions had been previously declared, an error would have happened, since either odd would not be visible from even (because it has not still being declared), or even would not be visible from odd.

Many programmers recommend that all functions should be prototyped. It is also my recommendation, mainly in case that there are many functions or in the case that these are very long, since, having the prototype of all the functions in the same place can spare us some time when consulting how to call it.

© The C++ Resources Network, 2000-2001 - All rights reserved

Previous:
2-2. Functions (I).

index
Next:
3-1. Arrays. String of characters.