You are on page 1of 12

Fundamentals

 There are many operators available that work on built-in types,


like int and double.
 Operator overloading -- is the creation of new versions of these operators for
use with user-defined types.
 It is not as difficult as it sounds. Some things to note:
o An operator in C++ is just a function that is called with special notation
(usually more intuitive or familiar notation). Overloading an operator
simply involves writing a function.
o C++ already does some operator overloading implicitly on built-in
types. Consider the fact that the + operator already works for ints, floats,
doubles, and chars. There is really a different version of the + operator
for each type.
o Operator overloading is done for the purpose of using familiar operator
notation on programmer-defined types (classes).

Operators usually refer to C++ predefined operators:

 arithmetic operators: +, -, *, /, %
 relational operators: <, <=, ==, !=, >, >=
 assignment operator: =
 logical operators: &&, ||, !
 input/output operators: <<, >>

 Overloading operator restrictions


o Cannot overload operators as they applied to built-in data-types
 Can't overload (+) if it is used in the following statement:
int y=x+5;
o Must respect the original "functional" template of the operators.
 Cant convert unary to binary
o Cannot change operators procedence
 Can't make + operator a higher precedence
o C++ allows to overload any operator except class member operator
 dot operator (.)
 Resolution operator (::)
 Conditional operator ( ? )
 Class member deference operator

1
The Thee ways

 There are 3 ways to define overloaded operators:


1. Member function
2. Nonmember function
3. Friend function

 Member function

 The first way is by class method (member function). This is the most popular
way. 

It is more in the spirit of object-oriented programming and is a bit more


efficient since the definition can directly reference member variables."

 Since the operator is applied on a (existing) class object, the number of


parameters to the operator is one less:
o Binary operators have 1 parameter, the second operand to the
operator.  The first operand is the object in which the overloaded
operator is called/invoked.
o Unary operators (e.g. unary - for negative) have 0 parameter.
class Money
{
public:
Money();
Money(int d, int c);
Money(int allc);
...
Money operator+(const Money & mo2) const;
Money operator-(const Money & mo2) const; // binary -
Money operator-() const; // unary -
bool operator==(const Money & mo2) const;
private:
int dollar;
int cent;
};
Money Money::operator+(const Money & m2) const
{
int total = (dollar * 100 + cent) +
(m2.dollar * 100 + m2.cent);
Money local(total);
return local;
}

Money Money::operator-(constMoney & m2) const // *this - mo2


{
int diff = (dollar * 100 + cent) -

2
(m2.dollar * 100 + m2.cent);
Money local(diff);
return local;
}

Money Money::operator-() const // unary -


{
int neg = - (dollar * 100 + cent);
Money local(neg);
return local;
}

bool Money::operator==(const Money & m2) const


{
int thistotal = (dollar * 100 + cent);
int m2total = (m2.dollar * 100 + m2.cent);
return (thistotal == m2total);
}
Top-level (Nonmember) function

 Another way to overload operators is by regular, non-member functions.


 Since the operator is NOT a class method, all operands involved in the
operator become the parameters:
o Binary operators have 2 parameters, the second operand to the
operator.  The first operand is the object in which the overloaded
operator is called/invoked.
o Unary operators have 1 parameter.

 Also the operator cannot access private members in the parameter objects.


class Money
{
public:
Money();
Money(int d, int c);
Money(int allc);

int getDollars() const;


int getCents() const;
...
// note: NO method for operator<
private:
int dollar;
int cent;
};
// Definition of regular, non-member functions.

// mo1 < mo2


bool operator<(const Money & m1, const Money & m2) // note: 2 arguments
and NO Money::
{

3
int thistotal = m1.getDollars() * 100 + m1.getCents();
int m2total = m2.getDollars() * 100 + m2.getCents();
return (thistotal < m2total);
}
Friend function

 Yet another way is to use friend function.  Friend functions are declared


within a class, but they are NOT class methods.
 A friend function is actually a regular function which has a privilege to access
private members in the parameter objects.
class Money
{
public:
Money();
Money(int d, int c);
Money(int allc);
Money operator+(const Money & mo2) const;
...
// friend functions
friend ostream& operator<<(ostream& out, const Money & m); // to be able to
do cout << obj
friend istream& operator>>(istream& in, Money & m); // to be able to do
cint >> obj

private:
int dollar;
int cent;
};
// no keyword "friend" in the function definition
ostream& operator<<(ostream& out, const Money & m)
{
out << "$" << m.dollar // dollar private in m -- OK
<< "." << m.cent; // cent private in m -- OK

return out;
}

istream& operator>>(istream& out, Money & m)


{
char dollarsign;
double moneyAsDouble;

in >> dollarSign; // first eat up '$'


in >> moneyAsDouble; // xx.yy

m.dollar = static_cast<int>(moneyAsDouble);
m.cent = static_cast<int>(moneyAsDouble * 100) % 100;

return in;
}

4
friend functions vs. member functions

 Some operators can be written as member functions of a class


 Some operators can be written as stand-alone functions -- it's common to
use friend on these
 Some operators can be written either way (your choice)
 A binary operator has two operands
o Written as a stand-alone function, both operands would be sent as
parameters, so it would be a function with two parameters
o Written as a member function, the first operand would be the calling
object, and the other would be sent as a parameter (i.e. a function with
one parameter)
 A unary operator has one operand
o As a stand-alone function, the operand is sent as a parameter
o As a member function, one calling object, no parameters

Operator overloading format

 An operator is just a function. This means that it must be created with a return
type, a name, and a parameter list
 The rules above give some restrictions on the parameter list
 The name of an operator is always a conjunction of the keyword operator and
the operator symbol itself. Examples:
o operator+
o operator++
o operator<<
o operator==
 So the format of an operator overload declaration is just like that of a function,
with the keyword operator as part of the name:
 returnType operatorOperatorSymbol (parameterList);

Why operator overloading


Example 1

 Consider the arithmetic operators. These are already familiar notations from
algebra and earlier.
int x = 3, y = 6, z;
float a = 3.4, b = 2.1, c;
z = x + y;

5
c = a / b;

 Now consider this notation. This should also be familiar from high school or
earlier:
1/2 + 1/3 // evaluates to 5/6
2/3 - 1/3 // evaluates to 1/3

 Now, what about using arithmetic operators on our Fraction class (a user-


defined type):
Fraction n1, n2, n3;
n3 = n1 + n2; // will the compiler accept this?

o It should be clear that this would not make sense to the compiler.


Fraction is a programmer-defined type. How would the computer know
about common denominators, etc?
 These code statements would be nice to use, however, because it's the same
way we use other numeric types (like int and double).
 Operator overloading makes this possible

Example

 Consider screen output. We know that this is legal:


int x = 5, y = 0;
cout << x << y;

 But how about this?


Fraction f(3,4);
cout << "The fraction f = " << f << '\n';

 There is no insertion operator in the iostream library to


handle Fraction objects. How would the library designers know what types we
were going to build?
 But with operator overloading, we can define a new version of the insertion
operator << to make this work

Overloading the Arithmetic operators


The arithmetic operators can be overloaded either as stand-alone functions or as
member functions.

6
Overloading an operator as a member function

 One member function version of Add was:


Fraction Add(const Fraction& f) const;

A sample call to this function:

Fraction n1, n2, n3;


n3 = n1.Add(n2);

 The corresponding operator overload. Again, we change the name


to operator+
Fraction operator+(const Fraction& f) const;

Again, we could use typical function notation, and the dot-operator:

n3 = n1.operator+(n2);

But the whole point is to be able to use the more familiar notation, which still
works, and no dot-operator required:

n3 = n1 + n2; // n1 is the calling object, n2 is the argument

 A full definition of this version of operator+


Fraction Fraction::operator+(const Fraction& f2) const
{
Fraction r; // result
r.numerator = (numerator * f2.denominator)
+ (f2.numerator * denominator);
r.denominator = (denominator * f2.denominator);
return r;
}

 Link to the Fraction class with the + operator as a member function . It’s a text
file.

Overloading as a friend function

 To add objects, we could write a function called Add, of course. Recall this


example:
friend Fraction Add(Fraction f1, Fraction f2);

7
With this function prototype, a sample call would be:

Fraction n1, n2, n3;


n3 = Add(n1, n2);

 The + notation would certainly be more convenient. The operator version just
has a different name:
friend Fraction operator+(Fraction f1, Fraction f2);

The usual function style call would look like this:

n3 = operator+(n1, n2);

While this is legal, the advantage is being able to use the more
common infix notation:

n3 = n1 + n2; // this becomes legal

 Here's a full definition of the + operator for class Fraction. Note that this is
also a possible definition for the Add function:
Fraction operator+(Fraction f1, Fraction f2)
{
Fraction r; // declare a Fraction to hold the result

// load result Fraction with sum of adjusted numerators


r.numerator = (f1.numerator*f2.denominator)
+ (f2.numerator*f1.denominator);

// load result with the common denominator


r.denominator = f1.denominator * f2.denominator;

return r; // return the result Fraction


}

 Once this operator overload is defined, then the following is legal. Note that
cascading also works (because the operation returns a Fraction). Now we have
the standard intuitive use of +
Fraction n1, n2, n3, n4, n5;
n5 = n1 + n2 + n3 + n4; // now it is legal!

 Note: This function could also be written with const reference parameters
friend Fraction operator+(const Fraction& f1, const Fraction& f2);

8
 Link to the Fraction class with operator+ added in (as a friend function)

Overloading comparison operators


 The comparison operators can also be overloaded either as stand-alone
functions or member functions
 Consider the Equals function example:
friend bool Equals(const Fraction& f1, const Fraction& f2);

We can easily write this as an operator overload:

friend bool operator== (const Fraction& f1, const Fraction& f2);

 Here are corresponding sample calls:


Fraction n1, n2;
if (Equals(n1, n2))
cout << "n1 and n2 are equal";

Contrast with this:

Fraction n1, n2;


if (n1 == n2)
cout << "n1 and n2 are equal";

 To get a full set of comparison operations, overload all 6 of them:


o operator ==
o operator !=
o operator <
o operator >
o operator <=
o operator >=

Overloading the insertion << and extraction >> operators:

As with other operators, the << and >> operators are defined for the basic types.  If
you build your own class, don't expect << to automatically work with your new types

9
of objects!   If you want it to work, you have to teach the computer how to do such
output.  Consider the following:
Fraction f;
cout << f; // how would the machine know how to do this?

We have no reason to expect the second line to work!  The insertion operator << is
only pre-defined for built-in types.  The iostream.h library doesn't know about the
Fraction type.

The << operator is a binary operator (2 parameters, left side and right side).  The first
parameter is always an ostream object (we've mostly used cout, so far).  Because of
this, it cannot be defined as a member function (it would have to be a member of the
ostream class, which we cannot change).  The << and >> operators should always be
defined as outside functions (usually friend functions).  The second parameter is
whatever new type it is being overloaded to print:
friend ostream& operator << (ostream& s, Fraction f);

This declaration has all of the usual parts for defining a function.  The name
is operator<< (the keyword operator and the operator symbol). The return type
is ostream&. The parameters are (ostream& s, Fraction f). When defining overloads
of << and >>, always pass the stream parameters by reference. A better way to write
this operator is:
friend ostream& operator << (ostream& s, const Fraction& f);

Notice that the first one passes the Fraction by value (and makes a copy). The second
passes by reference (avoiding the overhead of a copy). It is declared as a const
because the Fraction does not need to change if we are just doing output.

Here is the corresponding prototype for extraction >>


friend istream& operator >> (istream& s, Fraction& f);

Notice that the Fraction parameter for >> is also a reference parameter.  This is
because we are getting input into the object, so we need to work on the original, not a
copy.

Remember the Show() function of the Fraction class?


void Fraction::Show()
{
cout << numerator << '/' << denominator;
}

10
Here is how the << operator might be defined for Fraction.  Notice how similar it is to
the Show() function.
ostream& operator << (ostream& s, const Fraction& f)
{
s << f.numerator << '/' << f.denominator;
return s;
}

Note the differences between this and the Show() function.

 In this function, we must have a return statement, because we have a return


type (as opposed to "void" in the original Show function). We return the
ostream itself.
 Note also that we must use "s" (not "cout") in the function body.  This is the
formal parameter, and a nickname for whatever was passed in (which could
be cout, but also could be a different ostream).
 Last, notice that this is not a member function of the Fraction class, but rather,
a friend function.  So, we can access the private data, but we must do it
through the object:

Once this is defined, we can use a Fraction object in a cout statement:


Fraction f1;

So now, instead of:


cout << "Fraction f1 is ";
f1.Show();
cout << '\n';

We can write:
cout << "Fraction f1 is " << f1 << '\n';

Click here to see the Fraction class with the << overload used instead of Show().

Now, what would the definition of the >> overload look like for Fraction?  Try it!
You can check out sample prototypes for operator overloads here.
Here are some overly simplified iostream, ostream, and istream header files.

11
12

You might also like