Reading: Deitel & Deitel, Chap. 8
Note: You may run into bizzare Microsoft compilation errors while doing the examples in this worksheet. If you do, please see the email Paul sent out regarding fixes and workarounds for Worksheet 9 (the text of the email is pasted here)
When you define your own types using classes, you often want to write code
which allows you to use C++ operators on them. You might want to
add two objects together using the + operator
or to output them using the << operator. This is called
operator overloading, because you are taking a previously
defined operator and giving it a new meaning.
In fact, most of the operators that we commonly use are already
overloaded, in the sense that they have different meanings depending
on the type of their operands. For example, the + operator
performs a different operation if its operands are of type double than if they are of type int.
Note that all operators have arguments (they are typically called
operands rather than arguments, but the meaning is the same) and
they all return a value. Thus, overloading an operator is like
writing a function. For example, the plus operator takes two
operands and returns a third operand, so overloading the plus
operator to add two instances of the class X which returns
an instance of the class X is exactly the same as writing
a function with this signature:
X Add(X x1, X x2)
There are two ways to overload an operator. You can overload the
operator with a member function definition, in which case the
current class instance (pointed by the this pointer) is
an implicit argument, just as with other member functions. The second
way is to overload the operator with a function definition outside the
class and declare that function to be a friend, using a friend
declaration within the class.
The syntax of operator overloading is as follows:
type operator operator (argument list
) { statements }
Here is a simple example which overloads the + operator for the
class Point using the friend method. This allows us to add two points
together to get a third point where the x value of the new point is
the sum of the two x values and the y value of the new point is the
sum of the y values.
#include <iostream.h>
class Point {
private:
double x,y;
public:
Point() { x = y = 0.0;} // default constructor
Point(double a, double b){x=a; y=b;} // another constructor
friend Point operator+(Point, Point);
};
Point operator+(Point a, Point b)
{
Point temp(a.x + b.x, a.y + b.y); // calls the constructor
return temp;
}
int main()
{
Point p1(3.4,5.6);
Point p2(7.8,9.0);
Point p3 = p1 + p2;
return 0;
}
Note that when the overloading is done as a friend function, all class
instance arguments must be written explicitly in the argument list.
Thus, overloading an operator is just like defining any other member or friend function definition except for two syntax differences:
operator.
+,
you write the operator between its two operands (infix notation).
Plus
Point Add(Point a, Point b)
{
Point temp(a.x + b.x, a.y + b.y); // calls the constructor
return temp;
}
p1 and p2,
as
Point p3 = Plus(p1, p2);
#include <iostream.h>
class Point {
private:
double x,y;
public:
Point() { x = y = 0.0;}
Point(double a, double b){x=a; y=b;} // another constructor
Point operator+(Point n) { // note that there is only one argument
Point temp(x + n.x, y + n.y);
return temp;
}
};
int main()
{
Point p1(3.4,5.6);
Point p2(7.8,9.0);
Point p3 = p1 + p2;
return 0;
}
cout << p3 << endl;
<< operator is not defined for an object of
type Point. One simple solution to this would be to define
a member function PrintPoint() of the Point class as follows:
void PrintPoint() {
cout << "X is " << x << " Y is " << y << endl;
}
This is what we've done up to this point.
However, a better solution is to overload the << operator so
that it knows how to output a Point. The << operator is a
binary operator, which means that it takes two operands. The second
operand would be a Point, but what is the first? The left side
of the << can be cout, or an instance of the
class ofstream (file open
for output), among other things. Both of these are derived classes
from the base class ostream,1 an output stream. Thus the left
operand of the << operator should be of type ostream.
There are two other things that you must do to write this function.
The ostream must be a reference pointer, and the operator must return
a reference pointer to an ostream.
Here is the solution:
class Point {
...
friend ostream& operator<<(ostream &, Point);
}; // end of definition of the class Point
ostream& operator<<(ostream &os, Point p) {
os << "X is " << p.x << " Y is " << p.y ;
return os;
}
Overloading the >> operator is similar except that the first
operand is of type istream, and the second argument must be a
reference argument because its value is changed.
class Point {
...
friend istream& operator>>(istream is&, Point&);
}; // end of definition of the class Point
istream& operator>>(istream &is, Point& p)
{
is >> p.x >> p.y;
return is;
}
Note that the code for overloading the >> and <<
operators must be done outside the class using the friend method
because when operators are overloaded inside the class, the instance
of the class is the first operand, and in these cases the first operand
is not the same type as the class.
Exercise 1: Define a class IntArray which has one private member, an array of ten ints. The class should have a single constructor, which takes no arguments and sets all ten values in the array to zero. Define a public member function void Setval(int pos, int val) which sets the value of the array at position pos to val (Note that pos must be in the range 0 .. 9).
Define three operators on IntArray
ostream& << IntArray which displays all of ten values on
the terminal on a single line separated by a space.
IntArray + (IntArray a1, IntArray a2) which returns a
new IntArray whose values at each of the ten positions are the sum of the values
of the two arguments at the same position. For example, if the value
at position 2 of a1 was 17 and the value of position 2 of a2 was
5, the value of position 2 in the array which was returned would be 22.
IntArray - (IntArray a1, IntArray a2) which returns a new
IntArray whose values at each of the ten positions are the value of the
first argument minus the value of the second argument. For example, if the value
at position 2 of a1 was 17 and the value of position 2 of a2 was
5, the value of position 2 in the array which was returned would be 12.
Here is a short main to test your code:
#include <iostream>
using namespace std;
int main()
{
IntArray A, B;
A.setval(0,5);
A.setval(1,7);
A.setval(2.23);
B.setval(0,3);
B.setval(1,11);
B.setval(2,10);
IntArray C = A + B;
cout << C << endl;
// should print 8 18 33 0 0 0 0 0 0 0
C = A - B;
cout << C << endl;
// should print 2 -4 13 0 0 0 0 0 0 0
return 0;
}
Exercise 2 Rewrite the same code defining the last two operations as members of the class.
Any operator which is already defined in C++ can be overloaded with the following restrictions and exceptions.
. .* :: ?: and sizeof cannot
be overloaded (you have not seen some of these yet).
+ is a binary
operator in C and C++, and so if it is overloaded, it must still be a binary
operator.
+ operation is defined for integers,
and so you cannot overload it so that it has a different meaning for
integer operands.
Overloading the ++ and -- operators
Unary operators (i.e., operators which only take one argument)
can be overloaded in the same fashion. Recall that for
integers the ++ operator
takes two forms, the prefix form and the postfix form. Both
increment their operands by 1, but the prefix form returns the
incremented value while the postfix form returns the value before
incrementing. Here is some sample code for integers
to refresh your memory on this:
int i, j; i = 7; j = i++; // postfix cout << i << ' ' << j << endl; // prints 8 7 i = 7; j = ++i; // prefix cout << i << ' ' << j << endl; // prints 8 8When we overload
++ for some new type, we should define
both a prefix and a postfix version, and their meanings should
follow the same conventions as for the built-in integer versions:
they should both increment the object they are applied to
(in some sense), and the prefix form should return the
incremented value while the postfix form returns the value before
incrementing.
Here is code to overload the two versions of the ++ operator
for the class Point.
class Point {
...
friend Point& operator++(Point&); // prefix
friend Point operator++(Point&, int); // postfix
...
};
Point& operator++(Point& p) // prefix
{
++p.x;
++p.y;
return p;
}
Point operator++(Point& p, int) // postfix
{
Point temp = p;
++p.x;
++p.y;
return temp;
}
There are several non-obvious things to notice about these
definitions:
int parameter doing in the
postfix version? It isn't named or used in the body of the function,
so it seems rather useless. In fact, its only purpose is to
distinguish between the prefix and postfix versions of the operator
when defining them, so that the compiler can keep them
separate.2 When you call the
function you do not pass an argument corresponding to the int
parameter, you simply write p++.
Point
it returns the result as a reference, which is more generally useful.
For example, it makes an expressions like ++(++p) work as
most people would expect--i.e., p would be incremented twice.
The decrement operator -- is overloaded in the same way.
Exercise 3 Add the following additional operators to
your class IntArray:
>>, ++ (prefix), ++ (postfix)
To increment an IntArray, add 1 to each of the ten values.