Operator Overloading

C++ supports operator overloading; that is, letting a method of a class be disguised to look like an operator. The priority and associativity of the operators are unaffected as well as their number of operands. As an example, let us look at a class handling rational numbers. A rational number is a number that can be expressed as a quotient between two integers, for instance 1/2 or 3/4. The integers are called numerator and denominator, respectively.

Rational.h

class Rational
{
  public:
    Rational(int iNumerator = 0, int iDenominator = 1);

    Rational(const Rational& rational);
    Rational operator=(const Rational& rational);

    bool operator==(const Rational &number) const;
    bool operator!=(const Rational &number) const;
    bool operator< (const Rational &number) const;
    bool operator<=(const Rational &number) const;
    bool operator> (const Rational &number) const;
    bool operator>=(const Rational &number) const;
    Rational operator+(const Rational &number) const;
    Rational operator-(const Rational &number) const;
    Rational operator*(const Rational &number) const;
    Rational operator/(const Rational &number) const;

    friend istream &operator>>(istream &inputStream, Rational &number);
    friend ostream &operator<<(ostream &outputStream,
                               const Rational &number);

  private:
    int m_iNumerator, m_iDenominator;

    void Normalize();
    int GreatestCommonDivider(int iNum1, int iNum2);
};

In the class, we again use the assert macro in the constructor to avoid division by zero, every integer is acceptable as numerator and denominator, except that the denominator cannot be zero. The zero rational number is represented by zero as numerator and one as denominator.

A rational number can be assigned another number as the class overloads the assignment operator. The operator works in a way similar to the copy constructor. One difference, however, is that while the constructor does not return a value, the assignment operator has to return its own object. One way to solve that problem is to use the this pointer, which is a pointer to the object. Every non-static method of the class can access it. As the object itself shall be returned rather than a pointer to the object, we first derefer the this pointer.

Two rational numbers are equal if their numerators and denominators are equal. Or are they? How about 1/2 and 2/4? They should be regarded as equal. So let us refine the rule to be that two rational numbers are equal if their numerators and denominators in their normalized forms are equal. A normalized rational number is a number where the numerator and the denominator have both been divided with their Greatest Common Divider, which is the greatest integer that divides both the numerator and the denominator. In every equality method of the class, we assume that the numbers are normalized.

When testing whether two rational numbers are equal or not, we do not have to re-invent the wheel. We just call the equality operator. The same goes for the less-than-or-equal-to, greater-than, and greater-than-or-equal-to operators. We just have to implement the less-than operator.

Rational.h

The four rules of arithmetic are implemented in their traditional mathematical way. The result of each operation is normalized.

Rational.h

and

Rational.h
Rational.h

and

Rational.h

We can also overload the stream operators to read and write whole rational numbers. The predefined classes istream and ostream are used. We read from the given input stream and write to the given output stream. In this way, we can read from and write to different sources, not only the keyboard and screen, but also different kinds of files. The stream operators are not methods of the class. Instead, they are freestanding functions. They are, however, friends of the class, which means that they can access the private and protected members of the class; in this case, the fields m_iNumerator and m_iDenominator. The friend feature is a rather debated way to circumvent the encapsulation rules. Therefore, I advise you to use it with care.

The Greatest Common Divider algorithm is known as the world's oldest algorithm, it was invented by the Greek mathematician Euclid in approximately 300 B.C. It is often abbreviated gcd.

Rational.h

Rational.cpp

#include <iostream>
using namespace std;

#include <cstdlib>
#include <cassert>

#include "Rational.h"

Rational::Rational(int iNumerator, int iDenominator)
 :m_iNumerator(iNumerator),
  m_iDenominator(iDenominator)  
{
  assert(m_iDenominator != 0);
  Normalize();
}

Rational::Rational(const Rational &rational)
 :m_iNumerator(rational.m_iNumerator),
  m_iDenominator(rational.m_iDenominator)  
{
  // Empty.
}

Rational Rational::operator=(const Rational &rational)
{
  m_iNumerator = rational.m_iNumerator;
  m_iDenominator = rational.m_iDenominator;
  return *this;
}

bool Rational::operator==(const Rational &rational) const
{
  return (m_iNumerator == rational.m_iNumerator) &&
         (m_iDenominator == rational.m_iDenominator);
}

bool Rational::operator!=(const Rational &rational) const
{
  return !operator==(rational);
}

bool Rational::operator<(const Rational &rational) const
{
  return (m_iNumerator * rational.m_iDenominator) <
         (rational.m_iNumerator * m_iDenominator);
}

bool Rational::operator<=(const Rational &rational) const
{
  return operator<(rational) || operator==(rational);
}

bool Rational::operator>(const Rational &rational) const
{
  return !operator<=(rational);
}

bool Rational::operator>=(const Rational &rational) const
{
  return !operator<(rational);
}

Rational Rational::operator+(const Rational &rational) const
{
  int iResultNumerator = m_iNumerator*rational.m_iDenominator
                       + rational.m_iNumerator*m_iDenominator;
  int iResultDenominator = m_iDenominator *
                           rational.m_iDenominator;

  Rational result(iResultNumerator, iResultDenominator);
  result.Normalize();
  return result;
}

Rational Rational::operator-(const Rational &rational) const
{
  int iResultNumerator = m_iNumerator*rational.m_iDenominator-
                         rational.m_iNumerator*m_iDenominator;
  int iResultDenominator = m_iDenominator *
                           rational.m_iDenominator;

  Rational result(iResultNumerator, iResultDenominator);
  result.Normalize();
  return result;
}

Rational Rational::operator*(const Rational &rational) const
{
  int iResultNumerator = m_iNumerator * rational.m_iNumerator;
  int iResultDenominator = m_iDenominator *
                           rational.m_iDenominator;

  Rational result(iResultNumerator, iResultDenominator);
  result.Normalize();
  return result;
}

Rational Rational::operator/(const Rational &rational) const
{
  assert(rational.m_iNumerator != 0);

  int iResultNumerator=m_iDenominator*rational.m_iDenominator;
  int iResultDenominator=m_iNumerator*rational.m_iNumerator;
  Rational result(iResultNumerator, iResultDenominator);
  result.Normalize();
  return result;
}

istream &operator>>(istream &inputStream, Rational &rational)
{
  inputStream >> rational.m_iNumerator
              >> rational.m_iDenominator;
  return inputStream;
}

ostream &operator<<(ostream &outputStream,
                    const Rational &rational)
{
  if (rational.m_iNumerator == 0)
  {
    outputStream << "0";
  }
  else if (rational.m_iDenominator == 1)
  {
    outputStream << "1";
  }
  else
  {
    outputStream << "(" << rational.m_iNumerator << "/"
                 << rational.m_iDenominator << ")";
  }

  return outputStream;
}

void Rational::Normalize()
{
  if (m_iNumerator == 0)
  {
    m_iDenominator = 1;
    return;
  }

  if (m_iDenominator < 0)
  {
    m_iNumerator = -m_iNumerator;
    m_iDenominator = -m_iDenominator;
  }

  int iGcd = GreatestCommonDivider(abs(m_iNumerator),
                                   m_iDenominator);

  m_iNumerator /= iGcd;
  m_iDenominator /= iGcd;
}

int Rational::GreatestCommonDivider(int iNum1, int iNum2)
{
  if (iNum1 > iNum2)
  {
    return GreatestCommonDivider(iNum1 - iNum2, iNum2);
  }

  else if (iNum2 > iNum1)
  {
    return GreatestCommonDivider(iNum1, iNum2 - iNum1);
  }

  else
  {
    return iNum1;
  }
}

Main.cpp

#include <iostream>
using namespace std;

#include "Rational.h"

void main()
{
  Rational a, b;
  cout << "Rational number 1: ";
  cin >> a;


  cout << "Rational number 2: ";
  cin >> b;
  cout << endl;

  cout << "a: " << a << endl;
  cout << "b: " << b << endl << endl;

  cout << "a == b: " << (a == b ? "Yes" : "No") << endl;
  cout << "a != b: " << (a != b ? "Yes" : "No") << endl;
  cout << "a <  b: " << (a <  b ? "Yes" : "No") << endl;
  cout << "a <= b: " << (a <= b ? "Yes" : "No") << endl;
  cout << "a >  b: " << (a >  b ? "Yes" : "No") << endl;
  cout << "a >= b: " << (a >= b ? "Yes" : "No") << endl
       << endl;
  cout << "a + b: " << a + b << endl;
  cout << "a - b: " << a - b << endl;
  cout << "a * b: " << a * b << endl;
  cout << "a / b: " << a / b << endl;
}