First Next Previous Last Glossary About

Functions - passing arguments by reference


Introduction

You probably are starting to gain some understanding of why we use functions, it is almost instinctive. Functions are useful when we want to encapsulate, in one chunk of code and data, some process that is called repeatedly. Functions are also the building blocks of the "top-down" approach to program design. In top-down design we define a program in terms of higher-level functions which call lower-level functions until we reach a point where the thing we want the program to do can be defined in terms of statements which are a part of the base language, whatever it may be.

Our use of functions up to now has been fairly minimal but you no doubt understand the techniques we are using. We declare a function prototype before the main function and after the main function we define the functions which were prototyped. Most of the functions we have used have been "typed", that is they can return a value of the particular type to the caller.

 ...
 
int main()
{ int a, b, c;
  float e, f, g;

  a = 1; b = 10;
  
//call product_int with values
//of a and b
  c = product_int(a,b); 
  
  e = 24.67; f = 65.43;

//call product_float with values
//of e and f
  g = product_float(e,f);
  
  return c; 
}  
int product_int(int x,int y)
{
  return x * y;
}

float product_float(float x,float y)
{
  return x * y;
}

You should be familiar with the kind of use of functions shown here. The caller hands over some values to the function, the function does it work and returns a result.

The caller (the main() function) hands over the values of a and b, e and f to each of the functions, no changes are made to the callers arguments by the functions. Indeed, no change can be made by the called function that will affect the calling arguments since only the values of the calling arguments are handed over. This is known as a "pass by value".

You might have run up against some limitations with this mechanism of returning a value to the caller. Functions which work with simple data types are fine but what happens when you need a function that works more complex, ie structured, data types? And what do you do if you want the function to change an argument you hand over to it?



Return to top of page

Using reference arguments

For the first question, there are two ways we can handle structured data types:

  1. declaring a function which returns a structured type,
  2. declare a function which uses reference arguments, this is a pass by reference. The caller hands over references to the arguments. Any change the called function makes to an argument is reflected in the called argument.

We won't look at the first technique yet, I will save that for when we know more about structures, unions and pointers. The second technique though is quite straightforward.

Code Explanation
#include <iostream>

void ref_int(int&);
Here a function is declared in prototype form. Note the function argument int&. It reads integer reference, or, reference to an integer and means that the integer argument passed to it will be a reference to an integer.
int main()
{ int x = 22;

  cout << x << endl;
  ref_int(x);
  cout << x << endl;
  
  return 0;
}
The main function has a local variable x which is initially 22. The ref_int function is called with what appears to be x as its argument. When ref_int() returns x has a value of 67. The reason for this is that a reference to x is handed over to the function, not the current value of x. In effect the address of x is handed over. It is rather like handing over a pointer but if a pointer was used it would have to be dereferenced in order to get at the value. If a reference variable is used we don't have to worry about dereferencing it, the compiler knows what to do. There is an example here that shows reference and pointer arguments.
void ref_int(int& y)
{
  for (int i = 0; 
           i < 10; 
           i++)
   y = y + i;
}
Here the ref_int function is defined and you can see that argument y follows the prototype: y is a reference to an integer. Since ref_int() receives a reference to a variable, that is, what is effectively the address of the calling argument, then whatever is done to y is done to the referred variable.

Here is another example but one which shows that the actual argument and the formal argument have the same address.

Code Explanation
#include <iostream>
#include <iomanip>

void by_ref(int &);
The prototype has a single reference argument or parameter. We need iomanip.h because we will use a manipulator - setw().
int main ()
{
 int x = 100;

 cout << "This is x before calling "
      << "the function "
      << x 
      << endl;
 cout << "This is the address of x "
      << setw(8)
      << &x 
      << endl;
 by_ref(x);
 cout << "This is x after calling "
      << "the function "
      << x 
      << endl;
 return 0;
}
x is initialised and displayed then the address of x is displayed. Note that this uses the address-of operator &x which you haven't seen before and is the same as the reference operator. This is not surprising since both do the same thing - they yield the address of something.
void by_ref(int & z)
{
 cout << "This is z before being "
      << "modified "
      << z 
      << endl;
 cout << "This is the address of z "
      << setw(8)
      << &z 
      << endl;
 z = 92;
 cout << "This is z after being "
      << "modified "
      << z 
      << endl;
}
The function shows the values of whatever it is that z refers to and the address of z. You can see that it is the same as the address of x in the main function.
This is x before calling the function 100
This is the address of x 0x254fe1c
This is z before being modified 100
This is the address of z 0x254fe1c
This is z after being modified 92
This is x after calling the function 92
This is the output from the program. It is unlikely that the addresses your program generates will be the same as those shown here but both x and z should have the same address.


Taking care with references

//func2_ex2.cc
int main()
{ int& y;
  return 0;
}

`y' declared as reference but
not initialized

By far the most common use of references is as function arguments but they can also be used as function types, ie function return values and as variables. In all cases the reference must refer to something, ie it must refer to a piece of memory.

If you try to compile the example here you will get the error message stating that the reference hasn't been initialised.


#include <iostream>
int main()
{ int& y = 99;
  cout << y;
  return 0;
}
initialization of non-const
reference type `int &'

This example also has a problem as shown by the error message. A reference must be declared as a const if it is to be initialised and does not refer to some existing storage. If we change the line declaring the integer reference to:

const int& y = 99;

then the compiler is quite happy. Of course you won't be able to change y since it has been declared as a constant.

#include <iostream>
int main()
{ int z = 99;
  int& y = z;
  cout << y;
  return 0;
}

This example shows a safer way to declare and initialise a reference. It refers to some existing storage. Now anything that is done to z is done to y since y refers to z, and vice-versa of course. Statements like:

z++;
y++;

are quite legitimate and will both operate on the same piece of memory.



Return to top of page



Tutorial

Exercise 1

Try the example programs first and make sure they work.

Exercise 2

Write a short program which uses the following declaration:

const int& y = 99;

and which attempts to change y. What kind of error (if any) do you get?

Return to top of page


First Next Previous Last Glossary About


Copyright © 1999 - 2001 David Beech