In this lecture, we overview a couple of advanced features in C++ that the C langauge doesn't provide.

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:

void add3(int* xptr) {
  *xptr = *xptr + 3;
}
That works, but can be a bit annoying because we have to use the dereference operator * all over the place in our function. Besides that, we have to remember to add the address-of operator wherever we might call this function, like:

int a = 10;
add3(&a);
add3(&a);
// now a equals 16
Since pointers can be tricky and sometimes “dangerous” to use, C++ tries to avoid some of the situations where we might have to use them. The answer in this case is to use a reference variable as the parameter to the add3 function, like so:

void add3(int& x) {
  x = x + 3;
}
Specifying a reference type is similar to specifying a pointer type, except you use an & with the type instead of a *. What that means is that the x passed to the function is a reference to whatever variable the function is called on. Changing x in the function, when x is a reference variable, also changes the original value!

int a = 10;
add3(a); // works now, no pointer needed!
add3(a);
// now a = 16
Pass by reference is also used in C++ to return multiple values from a function (by passing in multiple arguments by reference), or to avoid copying something that is large or otherwise wouldn’t make sense to copy.

Passing streams by reference

As you know, copying is not allowed for streams. So, 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, we can say


int k = readtime(cin);
when we 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, we can also say:


ifstream fin("data.txt");
int m = readtime(fin);  // pass fin as a reference to istream
... when we 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 we 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, we'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, we 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. A 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 we could add points p and m by saying p + m.

// Initialization
ofstream OUT("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
  OUT << p.x << '\t' << p.y << endl;
}while(getmove(m,cin));

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


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



  OUT << p.x << '\t' << p.y << endl;
}while(getmove(m,cin));
This wouldn't change what we could accomplish, but it would add a little "syntactic sugar" to shorten the program a bit.

Prototype and definition?

To do this, 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 it is hopefully 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;
  S.x = a.x + b.x;
  S.y = a.y + b.y;
  return S;
}

Using the overloaded operator

So with that addition, here's a second verison of the program. Now, the syntactic sugar may not seem worth the effort here, but you'll probably be using the point struct over and over, and you'll like being able to add points. Wouldn't it be nice to define the midpoint function like this:

point midpoint(point a, point b)
{
  return (a + b)/2.0;
}

Overloading other operators

"Operator overloading" is the term used for defining versions of the C++ operators for the new structs we define. In general, if you have an expression A Π B, where "Π" stands for some operator, then that is equivalent to a function call operatorΠ(A,B). So, to subtract two points we'd define
point operator-(point A, point B);
... and to compare two points with less than we'd define
bool operator<(point A, point B);
... or to multiply a point (on the left) by a real number (on the right) we'd define
point operator*(point A, double w);

Operator for scalar division

Now, in addition to defining operator+ for two point objects, what else would you need? Well, (a + b) is an object of type point, and we are dividing it by an object of type double, so we need to define operator/(point,double). What type of object should be returned here?

point operator/(point P, double z)
{
  point Q;
  Q.x = P.x / z;
  Q.y = P.y / z;
  return Q;
}

I/O and 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;
}

ostream& operator<<(ostream &out, point A)
{
  return out << '(' << A.x << ',' << A.y << ')';
}
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.

To wrap all of this function overloading stuff up, consider this example: point.h and point.cpp.

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.