Topics To Cover

Pass by address vs. Pass by reference

Remember that one of the reasons why you might pass a pointer to a function, is if you want to allow that function to change the value of what you passed into it. For example, here’s a super simple function to increase the first number's value by 3:

Pass by address


void add3(int* xptr) 
{
  *xptr = *xptr + 3;
}
That works, but can be a bit cumbersome to use because we have to use the dereference operator * all over the place in our function body.

Besides that, we have to remember to add the address-of operator whenever we call this function, like:


int a = 10;
add3(&a);
add3(&a);
// now a equals 16
Moreover, pointers can be tricky and sometimes "dangerous" to use (e.g., segmentation faults due to passing an invalid address), and C++ allows us to avoid some of the situations where we might have to use them.

Pass by reference

We can use a reference as the parameter to the add3 function, in order to address these problems.


void add3(int& x)   // int& means a reference will be passed
{ 
  x = x + 3;  // Good: no more deference!
}
Specifying a reference type is similar to specifying a pointer type, except you use an int& (instead of int*) when specifying a function parameter.

What that means is that the x passed to the function is a reference to whatever variable the function is called on.


int a = 10;
add3(a); // Good: no more address-of operator!
add3(a);
As with passing by address, changing x inside the function also changes the argument a!

It's quite convenient! We don't need to use deference or address-of operators anymore.

Passing streams by reference

As you know, copying is not allowed for streams. However, we can pass stream objects (both ostream and istream) by reference. Compared to using pass-by-address, we can write the same code much simpler using pass-by-reference.

int readtime(istream&);
...
int readtime(istream& in)
{
	int h, m, s;
	char c;
	in >> h >> c >> m >> c >> s;
	return h*3600 + m*60 + s;
}

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

With these definitions, I can say


int k = readtime(cin);
when I need to read in a time from the keyboard.

Note: Both cin and all the ifstream objects can be passed as a reference to istream. Therefore, I can also say:


ifstream fin("data.txt");
int m = readtime(fin);  // pass fin as a reference to istream
... when I need to read in a time from a file data.txt using the same function readtime.

It would be 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.

Operator overloading

C++ allows the programmer to make new types that really act like the builtin types, by which I mean that the usual operators, like *, <, ++, work with the new types, and I/O with << and >>, and so on.

Although, this is not going to be a major theme for this course, I'd like to show it to you so that you understand that we really can build new types in C++ if we want to. Also, I should note, this is why + and >> and so on work with C++ string objects. The implementers of the string library defined all these operators for their nice string type.

Simple program

Let's write a program that sort of plots a course this way.
  1. You start out at (0,0).
  2. Then, you can enter in "moves". A move of (-2,4) means move down 2 units and to the right 4 units from wherever your current position is.
  3. The user enters moves continually until he finally enters a q to quit.
First solution. My first solution is a pretty simple program.
The meat the program is:

Operator overloading. While it may be simple, it would be nice to be able to write the function as if point were a built-in type, meaning that I could
  • add points p and m by saying p + m.
  • read a point by saying cin >> m.

// Initialization
ofstream fout("out.txt");
point p,m;
p.x = p.y = m.x = m.y = 0;

// Get moves & write moves
do {
  // Compute new position p from move m 
  p.x = p.x + m.x;
  p.y = p.y + m.y; 
  // Write move
  fout << p.x << '\t' << p.y << endl;
}while(getmove(m,cin));

// Initialization -- same as before
ofstream fout("out.txt");
point p,m;
p.x = p.y = m.x = m.y = 0;


do {
  p = p + m;  //*** LOOK HERE ***



  fout << p.x << '\t' << p.y << endl;
}while( cin >> m );  // *** LOOK HERE ***
This wouldn't change what I could accomplish, but it would add a little "syntactic sugar" to sweeten the program a bit.

Prototype and definition?

First, we need to be able to tell the compiler what p + m means for point objects p and m. Doing this is quite easy once you understand the following:

a + b is just the same as the function call operator+(a,b) in C++.

So if you want to tell the compiler what + means for two point objects, you need to define the function operator+(point a,point b) --- i.e. overload the + operator for points. The prototype is clear:

point operator+(point a, point b);
At least I hope it's clear that we should return a point when we add two points. The function definition is just like any other function definition:

point operator+(point a, point b)
{
  point S = {a.x + b.x, a.y + b.y};
  return S;
}
Note on initalizing a struct object: Using the {} clause, variable S is initalized as follows:
S.x = a.x + b.y;
S.y = a.y + b.y;
This initializaiton syntax is quite simple and handy!

I/O and operator overloading

Just to round out our examples, here's how to overload << and >>.

istream& operator>>(istream& in, point& A)
{
  char c;
  return in >> c >> A.x >> c >> A.y >> c;
}
The prototypes of these two should actually make some sense. For example, we've talked before about how cin >> x actually evaluates back to cin.
What is new, however, is the "return by reference".
Normally, when a function returns an object X, the returned object in the calling function is a copy of that X. By using return by reference, we're saying "no, I want the calling function to get the exact same object I returned, not a copy". Of course, this can only be done with something that doesn't go out of scope and die at the end of the function call.

Mandatory Practice Problem

  1. Add code so that the following code correctly reads the two points and prints out the midpoint.
    
    int main()
    {
      point p, q;
      cin >> p >> q;
    
      cout << "midpoint: " << (p+q)/2.0 << endl;
      return 0;
    }
    
    Sample run:
    $ ./a.out
    (2.2, 8.8)  (-1.1, 1.2)
    midpoint: (0.55, 5)
    

    Solution

Practice Problems

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