Topics to Cover

Limitation of pass by value

Function incHour(): Pass-by-value doesn't work well

Suppose we have a variable that stands for the hour (with a 24 hour clock), and we go through a simulation that keeps incrementing the hour. A simple ++ won't do, because once we hit 25, the hour goes back to 1. So, we might want a function incHour that increments the hour correctly, which means that the hour variable with which the function gets called should be modified.

We might be tempted to write incHour() as follows:


void incHour(int h)
{
  h++;            // increment h
  if (h == 25)    // handle wrap-around issues
    h = 1;
}

int main()
{
  int hour = 18;
  for(int i = 1; i <= 8; i++)
  {
    cout << "Hour " << i 
         << " of my 8 hour day starts at "  
         << hour << ":00" << endl;

    incHour(hour);  // *** call incHour()!
  }
  return 0;
}
However, since pass-by-value gives the function incHour() a copy, nothing really gets changed! (See diagram above.)

Pass by address

To fix the above problem, we need to pass the address of hour. In other words:

int hour = 18;
for(int i = 1; i <= 8; i++)
{
  cout << "Hour " << i 
       << " of my 8 hour day starts at "  
       << hour << ":00" << endl;

  incHour(&hour);  // *** pass by address!! *** 
}

Parameter is a pointer type.

Note that the type of &hour is int*. So, now the input parameter should be of type int*. Using this address (i.e., using the input pointer), the function can directly access the variable hour defined outside the function incHour and modify its value.

Of course, in order to access the object with a given address, the function must use the dereference operation.


void incHour(int* ph);  // ph contains the address of hour
{
  *ph += 1;        // *ph: dereference (see the right diagram). 
  if (*ph == 25)
    *ph = 1;
}

Be careful!!

Be careful when you increment *ph by 1.

Wrong Correct
*ph++ (*ph)++
or
*ph += 1

Explanation: Because of the operator precedence, *ph++ means *(ph++).

The above code fragment will print out something like this:
Hour 1 of my 8 hour day starts at 18:00
Hour 2 of my 8 hour day starts at 19:00
Hour 3 of my 8 hour day starts at 20:00
Hour 4 of my 8 hour day starts at 21:00
Hour 5 of my 8 hour day starts at 22:00
Hour 6 of my 8 hour day starts at 23:00
Hour 7 of my 8 hour day starts at 24:00
Hour 8 of my 8 hour day starts at 1:00

The Famous swap!


int a, b;
cin >> a >> b; 
// assumption: b is larger
for(int i = a; i < b; i++)
  cout << i << ',';
cout << b << endl;
With multi-parameter functions, we really start to see some interesting reasons to use pass-by-address. For example, one of the most common operations in computing is the swap.

For example, suppose we read two int's in from the user and we want to print out all the integer values from the smaller of the two up to the larger (comma-separate). If the user is kind enough to enter the numbers so that the frst is the smaller, we would write:

But what if we want to allow the user to enter the two numbers in either order, and have the program work regardless?

If we had a function swap that took two ints and swapped their values, we could do the following:


int a, b;
cin >> a >> b;
if (b < a)
  swap(&a,&b);                // the famous swap: pass by address!
for(int i = a; i < b; i++)
  cout << i << ',';
cout << b << endl;
... which is a whole lot more convenient than, for example, writing separate for-loops for the a<b case and the b<a case.

The function swap will need to change the values of the variables it's passed, so they must be passed by address.


void swap(int* pa, int* pb)
{
  int temp = *pa;  // We need a temporary variable. Why?
  *pa = *pb;
  *pb = temp;
}

Functions with Multiple Output Values

Sometimes there are several things we'd like to return with a function.

For example, you may want to have a function fileMinMax that works as follows:

  1. Open "data.txt". Let's say the file looks like this:
    N = 4 
    3.1 4.1 5.1 6.1
    
  2. Read the file data and return the maximum and the minimum. That is, we would like the function to return more than one values.
In this case, we can write the prototype of the function as follows:

void fileMinMax(double* pMax, double* pMin); 
That is, using the pass-by-address (pMax and pMin above), the function can return two values.

In the main function, you can call the function like:


double min, max;
fileMinMax(&min, &max);
cout << "min= " << min << " max= " << max << endl;
Please define fileMinMax (see the mandatory practice problem below).

Passing Streams to a Function

Disallowed: Assignments for streams

Consider the following code:

#include <fstream>
using namespace std;
int main()
{
  ofstream f1("data.txt");
  ofstream f2;
  f2 = f1; // not allowed
  return 0;
}
Q: Compile the code. Does this code compile successfully?
Answer:
No, I see a massive amount of error messages.

Disallowed: Pass-by-value for streams

For the same reason (pass-by-value involves copying the argument into the parameter), the following code will not compile, either.

void foo(ifstream fin);

int main()
{
  ifstream fin("data.txt");
  foo(fin);  // pass by value doesn't work 
  //...
}

Fix: Passing the address of istream

Copying is disallowed for streams. So, we must pass stream objects (both ostream and istream) by address.

In other words, when I need to read in a time from the keyboard, we call readtime() function as follows:


int k = readtime(&cin);

With this in mind, we'd define a function:


int readtime(istream* pin)
{
  int h, m, s;
  char c;
  (*pin) >> h >> c >> m >> c >> s;  // dereference: *pin
  return h*3600 + m*60 + s;
}

As you see, the function reads the elapsed amount of time in our hh:mm:ss format.

Cool stuff

Note: The address of cin and the address of all the ifstream objects can be all passed as a pointer to istream.

As you know, ifstream almost works the same as istream. In fact, ifstream a is sub-type of istream, so the conversion (i.e., ifstream* → istream*) is implicited executed. Therefore, I can also say:


ifstream fin("data.txt");
int m = readtime(&fin);  // typeof(&fin) is ifstream*, then conversion(ifstream* → istream*)
... when I need to read in a time from a file data.txt using the same function readtime.

It is wonderful to have a function readtime that can take either cin or fin as an argument, read the elapsed time in hh:mm:ss format, and return it in seconds.

Mandatory Practice Problem

Define fileMinMax: Answer.

Other Practice Problems

  1. Incrementing a Military Clock (You might prefer this solution.) This is a simple pass-by-address example that modifies its argument.
  2. Reading Binary Numbers Here's a simple example in which we use pass-by-address to avoid making a copy of an istream object.

    Note: The get() function reads in a character without skipping space. That is, when the input buffer has a newline, that get() will return 10, which is the ASCII code for the new line. The code uses this feature in order to see when to exit the while loop.