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:
Unary: that is they take a single argument, e.g. "++" and "[ ]" subscript operator
Binary: that is they take two arguments, e.g. "<<" and "<="
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:
"." member access
".*" member access-reference
"::" scope resolution
"?:" arithmetic if
overloaded operators must be either:
non-static member function of a class
have at least one parameter which is a class or enumeration. This prevents users from redefining the operators for built in types.
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