CS 250 Computer Programming and Problem Solving - FALL 1998 

[ Home | Syllabus | Notes | Glossary | CS250 WEB Version Home Page | Project Page | Final Study Guide]


Overloading Operators

We have already seen many examples of overloaded operators - particularly the I/O operators << and >>.

Operators can either be:

A full listing of operators is given in the book on pages 304-306.

Almost every operator in c++ can be overloaded. The only exceptions are:

overloaded operators must be either:

 


Let's look at an example which overloads the unary operator "++".

"++" can be used either as a postfix or a prefix operator. We will show both.


// p307.txt - Time class, Chapter 9 example.

// Time if day, expressed in hours and minutes,
// using a 24-hour clock.

// Demonstration of prefix/postfix increment
// operators and stream output.
// Uses the RangeError exception class.

#include <iostream.h>
#include <iomanip.h>
#include "range.h"    // RangeError class

const unsigned HourMax = 23;
const unsigned MinuteMax = 59;

class Time {
public:
  Time( unsigned c = 0 );
  Time( const Time & t );

  void SetHours( unsigned h );
  void SetMinutes( unsigned m );

  // prefix and postfix increment operators
  // increment the minutes.
  const Time & operator ++(); 
// above is prefix - whihc increments Time first and then returns the object
// why "const Time &" for the same reasons we use this form instead of call by value
// this is an alternate to return by value which is more efficient - but prevents
// changes to the underlying object
// Why no parameter - becuase as a operator member function the first parameter
// is always the object (this) itself
  Time operator ++(int);
// the above is the postfix - it returns the object and THEN does the increment
// Notice that this uses return by value to return a copy (using the copy constructor)
// of the original value of the Time before incrementing it
// The dummy "int" parameter is the way for the compiler to distinguish pre from post fix

  const Time & operator +=( unsigned n );
// the above is an efficient way to update an object
// This is a BINARY operator But the first parameter is assumed to be the object itself.
  // Add n minutes to the time.

  friend ostream & operator <<( ostream & os, const Time & h );
// can this be a member function?

private:
  unsigned hours;
  unsigned minutes;
};

// Construct from unsigned integer.

Time::Time( unsigned tv )
//if tv == 1015, this is 10 hundred hours and 15 minutes
{
  SetHours(tv / 100); // strip off the minutes, leaving the hours
  SetMinutes(tv % 100); //strip off the hours. leaving the minutes
}

// Copy constructor.

Time::Time( const Time & t2 )
// automatic copy constructor would have done the same
{
  minutes = t2.minutes;
  hours = t2.hours;
}

void Time::SetHours( unsigned h )
//PRE: h is not greater than 23 (else exception)
{
  if( h > HourMax )
    throw RangeError(__FILE__,__LINE__,h);
  hours = h;
}

void Time::SetMinutes( unsigned m )
//PRE: m is not greater than 59 (else exception)
{
  if( m > MinuteMax )
    throw RangeError(__FILE__,__LINE__,m);
  minutes = m;
}

// prefix increment: add 1 to minutes, if
// end of hour reached, reset to beginning
// of next hour.

const Time & Time::operator ++()
{
  if( ++minutes > MinuteMax )
  {
// roll over to next hour or even next day
    minutes = 0;
    hours = (hours + 1) % (HourMax + 1);
  }
  return *this;
// return a pointer to the object (but forbid messing around with it)
// this allows you to use it in an expression, e.g.
// cout << ++myTime;
}

// postfix increment: return the current
// time value, then add 1 to the time.

Time Time::operator ++( int )
{
  Time save( *this );  // construct a copy
// need to preserve the current value, so it can be used in an expression
// for example
// cout << myTime++;
// should write out the old time and then increment it
  operator ++();       // increment the time
// strange way to call the prefix (defined above) on yourself
// although this would make sense as
// this.operator ++()
// below is another way to do it
// *this is for call by reference
/* or could do this: ++(*this); */
  return save;         // return the copy (return by value of local variable)
}

// Add n minutes to the time.

const Time & Time::operator +=( unsigned n )
{
  unsigned t = minutes + n;
  minutes = t % (MinuteMax + 1);  // remaining minutes
  hours += t / (MinuteMax + 1);   // add to hours
  hours = hours % (HourMax + 1);  // roll over to next day
  return *this;
// so can be used in an expression, e.g.
// yourTime = (myTime+= 15);
}


// Display in "hh:mm" format.

ostream & operator <<( ostream & os, const Time & t )
{
  os.fill('0');
  os << setw(2) << t.hours << ':' << setw(2) << t.minutes;
  return os;
}

void test()
{
Time a;
Time b(1845);
Time c(2359);

cout << ++a << '\n'        // 00:01
     << b++ << endl;       // 18:45 but b is 18:46

cout << (c += 15) << '\n'   // 00:14
     << (b += 20) << '\n';  // 19:06

// Increment a Time object in a loop: to see correct roll over at hour

Time d(1230);
for(unsigned i = 0; i < 50; i++)
  cout << ++d << ',';
cout << endl;

}


int main()
{
try {
  test();
}
catch( const RangeError & R ) {
  cout << R;
}

return 0;
}

 


Here is an example which overloads many operators.

The interesting ones are the assignment, append (+) operators and cast operators


// FSTRING.H - FString class definition (p.312)

// Chapter 9 example, uses operator overloading.

#ifndef FSTRING_H
#define FSTRING_H

#include <iostream.h>
#include <strstrea.h>
#include <iomanip.h>
#include <string.h>
#include <stdlib.h>  // for strtol()


class FString {
public:
  // Constructors
  FString();
  FString( const char * s );
  FString( const FString & s );
  FString( const char ch );

  // Assignment operators
  // why is const reference a good idea
  // why does it work?
  const FString & operator =( const char * s );
  const FString & operator =( const FString & s );
  const FString & operator =( char ch );

  // Append & Concatenate operators
  // why return const reference
  // and why not a const member function?
  const FString & Append( const FString & s ); 
  const FString & operator +=( const FString & s );
  const FString & operator +=( const char * s );
  const FString & operator +=( char c );
  FString operator +( const FString & s2 ) const;
  // I don't like the above function - should NOT be a member - see later discussion
  // Why const?

  // Cast operators
  operator const char *() const; // cast to C-style string
  operator long() const;         // cast to long integer

  // Comparison operators and functions
  int operator ==( const FString & s ) const;
  int operator !=( const FString & s ) const;
  int operator < ( const FString & s ) const;
  int operator > ( const FString & s ) const;
  int operator <=( const FString & s ) const;
  int operator >=( const FString & s ) const;

  // Stream I/O operators and functions
  void GetLine( istream & inp );
  friend istream & operator >>( istream & inp, FString & s );
  friend ostream & operator <<( ostream & os, const FString & s );

private:
  enum { MaxSize = 255};
  char str[MaxSize+1];
};

#endif


// . . .

FString::FString( const char ch )
{
  str[0] = ch;
  str[1] = '\0'; // what's going on here?
}

FString::FString( const FString & s )
{
  *this = s; // is this deep or shallow copy?
}

FString::FString( const char * s )
{
  size_t n = strlen( s );
  if( n > MaxSize )
    throw RangeError( __FILE__, __LINE__, n );
  strcpy( str, s );
}

const FString & FString::operator =( const FString & s )
{
  size_t n = strlen( s.str );
  if( n > MaxSize )
    throw RangeError( __FILE__, __LINE__, n );
  strcpy( str, s.str );
  return *this;
}

const FString & FString::operator =( const char * s )
{
  return *this = FString(s); // cast is copy constructor
// uses assignment overload above
}

const FString & FString::operator =( char ch )
{
  str[0] = ch;
  str[1] = '\0';
  return *this; // why is this here
}

const FString & FString::Append( const FString & s )
{
  size_t n = strlen( str ) + strlen( s.str );
  if( n >= MaxSize )
    throw RangeError( __FILE__, __LINE__, n );

  strcat( str, s.str );
  return *this; // why is this here?
}

const FString & FString::operator +=( const char * s )
{
  return *this += FString(s);
}

const FString & FString::operator +=( char c )
{
  return *this += FString(c);
}

const FString & FString::operator +=( const FString & s )
{
  return Append( s );
}

FString FString::operator +( const FString & s2 ) const
// concatenates this string with s2 and returns it as new object
// this object is NOT changed
{
  FString temp( *this );
  return temp += s2;
}

FString::operator const char *() const
{
  return str;
}

FString::operator long() const
{
  return strtol( str, 0, 0 );
}

// . . .

void FString::GetLine( istream & inp )
{
  inp.getline( str, MaxSize+1 );
}

istream & operator >>( istream & inp, FString & s )
{
  inp >> setw(s.MaxSize+1) >> s.str;
  return inp;
}

ostream & operator <<( ostream & os, const FString & s )
{
  os << s.str;
  return os;
}



Members, Friends and PassersBy

Many time operator overloads can be implemented several different ways. For example consider the "+" operator above.

It could be written as a friend or as a regular function.

The way it is written in the book as a member function returning a new object does not make much sense.
Member functions should operate on the object - producing a new object as the return value is more appropriately done as a regular or friend function as shown below.

As A Regular Function


FString operator +(const FString & s1, const FString & s2);
FString operator +(const FString & s1, const FString & s2)
{
	FString temp(s1);
	return temp += s2;
}


As A Friend Function


friend FString operator +(const FString & s1, const FString & s2);
FString operator +(const FString & s1, const FString & s2)
{
	FString temp;
	int n = strlen( s1.str ) + strlen( s2.str );
	if( n >= s1.MaxSize )
		throw RangeError( __FILE__, __LINE__, n );
	
	strcpy(temp.str, s1.str);
	strcat( temp.str, s2.str );
	return temp;
}

As a Member Function which Changes the Object

// NOTE: this is a bad idea
// FIRST: when we say something like "A = B + C;"
// we don't expect that B or C will change
// SECOND: "B + B" will cause an error.

FString & operator +( const FString & s );
// why only one parameter?
// why return const reference?
FString & FString::operator +( const FString & s )
  int n = strlen( str ) + strlen( s.str );
  if( n >= MaxSize )
    throw RangeError( __FILE__, __LINE__, n );

  strcat( str, s.str );
  return *this; // why is this here?
}

 


Could << be a member function?

Yes BUT remember the first parameter of a binary operator member is the object itself - therefore the first operator of "<<" would have to be FString, Thus

ostream & operator <<(ostream & os);
ostream & FString::operator <<(ostream & os)
{
	os << str;
	return os;
}
// but to use it
   FString lastName("wild");
   lastName << cout;
// and it couldn't be strung together

 


Cast Operators


Copyright chris wild 1998.
For problems or questions regarding this website contact [Chris Wild (e-mail:wild@cs.odu.edu].
Last updated: December 04, 1998.