Operator Overloading
Steven Zeil
1 Operators
Lots of Operators
In C++, almost every operator in the language can be declared for our own classes.
-
In most programming languages, we can write things like “
x+y
” or “x<y
” only ifx
andy
are integers, floating point numbers, or some other pre-defined type in the language. -
In C++, we can add these operators to any class we design, if we feel that they are appropriate.
-
These include
+ - * / | & < > <= >= = == != ++ -- -> += -= *= /=
-
For example, the
std::string
class declares these for strings:+ < > <= >= = == != +=
-
The
std::vector
class declares these:< > <= >= = == !=
-
1.1 Operators are Functions
-
Each operator is actually a shorthand for a function named operator? where the ? is replaced by the actual operator symbol.
-
The function takes the same number and type of parameters as does the operator
-
The compiler translates the infix expressions into the equivalent function calls
Examples: operators as functions
-
If you write
a + b*(-c)
, that’s actually just a shorthand foroperator+(a, operator*(b, operator-(c)))
-
and if you write
testValue = (x <= y);
that’s actually a shorthand for
operator=(testValue, operator<=(x, y);
Declaring Operators
Understanding that shorthand, we can declare our own operators by giving the appropriate operator function name:
class BidCollection {
⋮
void addInTimeOrder (const Bid& value);
// Adds this bid into a position such that
// all bids are ordered by the time the bid was placed
//Pre: getSize() < getMaxSize()
void operator+= (const Bid& value)
{addInTimeOrder(value);}
⋮
Then
bids.addInTimeOrder(b);
and
bids += b;
would do the same thing
Keep it Natural
- It’s easy to abuse operator overloading and simply make things confusing.
-
E.g., Don’t use
operator+
unless your class really does something “addition-like”-
String concatenation is a good example:
string s = "abc" + "def"; // == "abcdef"
is enough like addition that most people don’t stumble over it.
-
On the other hand, if I wrote
int operator+ (Bid b1, Bid b2) { return (b1.getAmount().cents + b2.getAmount().cents) / 100; }
no one would understand what was actually happening when I later wrote
int dollarsCarried = bid1 + bid2;
-
-
Most Commonly Overloaded Operators
There are 3 groups of operators that are very commonly overloaded
-
I/O operators
<<
>>
-
Relational operators
< ==
-
The assignment operator
=
2 I/O Operators
Perhaps the most commonly declared operators
-
We have already seen that there is good reason for every class to provide an output function.
-
But the most common form for this is the operator
<<
Providing the Output Operator
For example, our Bid class already looks like:
class Bid {
⋮
public:
Bid (std::string bidder, double amt,
std::string item, Time placedAt);
Bid();
// Access to attribute
std::string getBidder() const {return bidderName;}
double getAmount() const {return amount;}
std::string getItem() const {return itemName;}
Time getTimePlacedAt() const {return bidPlacedAt;}
// Print a bid
void print (std::ostream& out) const;
};
All we need to do is add:
inline
std::ostream& operator<< (std::ostream& out, const Bid& b)
{
b.print(out);
return out;
}
Lots of Ops
The whole program, with output operators:
-
The Bid ADT:
../opoverload-auctionOut/bids.h#ifndef BIDS_H #define BIDS_H #include <iostream> #include <string> #include "time.h" // // Bids Received During Auction // class Bid { std::string bidderName; double amount; std::string itemName; Time bidPlacedAt; public: Bid (std::string bidder, double amt, std::string item, Time placedAt); Bid(); // Access to attribute std::string getBidder() const {return bidderName;} double getAmount() const {return amount;} std::string getItem() const {return itemName;} Time getTimePlacedAt() const {return bidPlacedAt;} // Print a bid void print (std::ostream& out) const; }; inline std::ostream& operator<< (std::ostream& out, const Bid& b) { b.print(out); return out; } #endif
and
../opoverload-auctionOut/bids.cpp#include <string> #include <fstream> using namespace std; // // Bids Received During Auction // #include "bids.h" Bid::Bid (std::string bidder, double amt, std::string item, Time placedAt) : bidderName(bidder), amount(amt), itemName(item), bidPlacedAt(placedAt) { } Bid::Bid () : amount(0.0) { } // Print a bid void Bid::print (std::ostream& out) const { out << "[" << bidderName << " " << amount << " " << itemName << " " << bidPlacedAt << "]"; }
-
The Bidder ADT:
../opoverload-auctionOut/bidders.h#ifndef BIDDERS_H #define BIDDERS_H #include <iostream> #include <string> // // Bidders Registered for auction // class Bidder { // describes someone registered to participate in an auction std::string name; double balance; public: Bidder(); Bidder (std::string theName, double theBalance); // Access to attributes std::string getName() const {return name;} double getBalance() const {return balance;} void setBalance (double newBal) {balance = newBal;} // print a bidder void print (std::ostream& out) const; }; inline std::ostream& operator<< (std::ostream& out, const Bidder& b) { b.print(out); return out; } #endif
and
../opoverload-auctionOut/bidders.cpp#include <string> #include <fstream> #include <iostream> // // Bidders Registered for auction // #include "bidders.h" using namespace std; Bidder::Bidder() { name = ""; balance = 0.0; } Bidder::Bidder (std::string theName, double theBalance) { name = theName; balance = theBalance; } // print a bidder void Bidder::print (std::ostream& out) const { out << "[" << name << " " << balance << "]"; }
-
The Item ADT:
../opoverload-auctionOut/items.h#ifndef ITEMS_H #define ITEMS_H #include <iostream> #include <string> #include "time.h" // // Items up for auction // class Item { std::string name; double reservedPrice; Time auctionEndsAt; public: Item(); Item (std::string itemName, double reserve, Time auctionEnd); // Access to attribute std::string getName() const {return name;} double getReservedPrice() const {return reservedPrice;} Time getAuctionEndTime() const {return auctionEndsAt;} /** * Read one item from the indicated file */ void read (std::istream& in); // Print an item void print (std::ostream& out) const; }; inline std::ostream& operator<< (std::ostream& out, const Item& b) { b.print(out); return out; } #endif
and
../opoverload-auctionOut/items.cpp#include <iostream> #include <fstream> #include "items.h" // // Items up for auction // using namespace std; Item::Item() : reservedPrice(0.0) { } Item::Item (std::string itemName, double reserve, Time auctionEnd) : name(itemName), reservedPrice(reserve), auctionEndsAt(auctionEnd) { } /** * Read one item from the indicated file */ void Item::read (istream& in) { in >> reservedPrice; auctionEndsAt.read (in); // Reading the item name. char c; in >> c; // Skips blanks and reads first character of the name string line; getline (in, line); // Read the rest of the line name = string(1,c) + line; } // print a bidder void Item::print (std::ostream& out) const { out << "[" << name << " " << reservedPrice << " " << auctionEndsAt << "]"; }
-
The Time ADT:
../opoverload-auctionOut/time.h#ifndef TIMES_H #define TIMES_H #include <iostream> /** * Times in this program are represented by three integers: H, M, & S, representing * the hours, minutes, and seconds, respecitvely. */ struct Time { // Create time objects Time(); // midnight Time (int h, int m, int s); // Access to attributes int getHours() const; int getMinutes() const; int getSeconds() const; // Calculations with time void add (Time delta); Time difference (Time fromTime); /** * Read a time (in the format HH:MM:SS) after skipping any * prior whitepace. */ void read (std::istream& in); /** * Print a time in the format HH:MM:SS (two digits each) */ void print (std::ostream& out) const; /** * Compare two times. Return true iff time1 is earlier than or equal to * time2 */ bool noLaterThan(const Time& time2); /** * Compare two times. Return true iff time1 is equal to * time2 * */ bool equalTo(const Time& time2); // From here on is hidden int secondsSinceMidnight; }; inline std::ostream& operator<< (std::ostream& out, const Time& t) { t.print(out); return out; } inline std::istream& operator>> (std::istream& in, Time& t) { t.read(in); return in; } #endif // TIMES_H
and
../opoverload-auctionOut/time.cpp#include "time.h" #include <iomanip> using namespace std; /** * Times in this program are represented by three integers: H, M, & S, representing * the hours, minutes, and seconds, respecitvely. */ // Create time objects Time::Time() // midnight { secondsSinceMidnight = 0; } Time::Time (int h, int m, int s) { secondsSinceMidnight = s + 60 * m + 3600*h; } // Access to attributes int Time::getHours() const { return secondsSinceMidnight / 3600; } int Time::getMinutes() const { return (secondsSinceMidnight % 3600) / 60; } int Time::getSeconds() const { return secondsSinceMidnight % 60; } // Calculations with time void Time::add (Time delta) { secondsSinceMidnight += delta.secondsSinceMidnight; } Time Time::difference (Time fromTime) { Time diff; diff.secondsSinceMidnight = secondsSinceMidnight - fromTime.secondsSinceMidnight; } /** * Read a time (in the format HH:MM:SS) after skipping any * prior whitepace. */ void Time::read (std::istream& in) { char c; int hours, minutes, seconds; in >> hours >> c >> minutes >> c >> seconds; Time t (hours, minutes, seconds); secondsSinceMidnight = t.secondsSinceMidnight; } /** * Print a time in the format HH:MM:SS (two digits each) */ void Time::print (std::ostream& out) const { out << setfill('0') << setw(2) << getHours() << ':' << setfill('0') << setw(2) << getMinutes() << ':' << setfill('0') << setw(2) << getSeconds(); } /** * Compare two times. Return true iff time1 is earlier than or equal to * time2 */ bool Time::noLaterThan(const Time& time2) { return secondsSinceMidnight <= time2.secondsSinceMidnight; } /** * Compare two times. Return true iff time1 is equal to * time2 * */ bool Time::equalTo(const Time& time2) { return secondsSinceMidnight == time2.secondsSinceMidnight; }
-
The BidCollection ADT:
../opoverload-auctionOut/bidcollection.h#ifndef BIDCOLLECTION_H #define BIDCOLLECTION_H #include "bids.h" #include <iostream> class BidCollection { int MaxSize; int size; Bid* elements; // array of bids public: /** * Create a collection capable of holding the indicated number of bids */ BidCollection (int MaxBids = 1000); ~BidCollection (); // Access to attributes int getMaxSize() const {return MaxSize;} int getSize() const {return size;} // Access to individual elements const Bid& get(int index) const {return elements[index];} // Collection operations void addInTimeOrder (const Bid& value); // Adds this bid into a position such that // all bids are ordered by the time the bid was placed //Pre: getSize() < getMaxSize() void remove (int index); // Remove the bid at the indicated position //Pre: 0 <= index < getSize() /** * Read all bids from the indicated file */ void readBids (std::string fileName); // Print the collection void print (std::ostream& out) const; }; inline std::ostream& operator<< (std::ostream& out, const BidCollection& b) { b.print(out); return out; } #endif
and
../opoverload-auctionOut/bidcollection.cpp#include "bidcollection.h" #include "arrayUtils.h" #include <fstream> using namespace std; /** * Create a collection capable of holding the indicated number of bids */ BidCollection::BidCollection (int MaxBids) : MaxSize(MaxBids), size(0) { elements = new Bid [MaxSize]; } BidCollection::~BidCollection () { delete [] elements; } // Collection operations void BidCollection::addInTimeOrder (const Bid& value) // Adds this bid into a position such that // all bids are ordered by the time the bid was placed //Pre: getSize() < getMaxSize() { // Make room for the insertion int toBeMoved = size - 1; while (toBeMoved >= 0 && value.getTimePlacedAt().noLaterThan(elements[toBeMoved].getTimePlacedAt())) { elements[toBeMoved+1] = elements[toBeMoved]; --toBeMoved; } // Insert the new value elements[toBeMoved+1] = value; ++size; } void BidCollection::remove (int index) // Remove the bid at the indicated position //Pre: 0 <= index < getSize() { removeElement (elements, size, index); } /** * Read all bids from the indicated file */ void BidCollection::readBids (std::string fileName) { size = 0; ifstream in (fileName.c_str()); int nBids; in >> nBids; for (int i = 0; i < nBids; ++i) { char c; string bidderName; double amount; in >> bidderName >> amount; Time bidPlacedAt; bidPlacedAt.read (in); string word, line; in >> word; // First word of itemName getline (in, line); // rest of item name string itemName = word + line; addInTimeOrder (Bid (bidderName, amount, itemName, bidPlacedAt)); } } // Print the collection void BidCollection::print (std::ostream& out) const { out << size << "/" << MaxSize << "{"; for (int i = 0; i < size; ++i) { out << " "; elements[i].print (out); out << "\n"; } out << "}"; }
-
The BidderCollection ADT:
../opoverload-auctionOut/biddercollection.h#ifndef BIDDERCOLLECTION_H #define BIDDERCOLLECTION_H #include "bidders.h" #include <iostream> // // Bidders Registered for auction // class BidderCollection { int MaxSize; int size; Bidder* elements; // array of items public: /** * Create a collection capable of holding the indicated number of items */ BidderCollection (int MaxBidders = 1000); ~BidderCollection (); // Access to attributes int getMaxSize() const {return MaxSize;} int getSize() const {return size;} // Access to individual elements Bidder& get(int index) {return elements[index];} // Collection operations int add (const Bidder& value); // Adds this bidder to the collection at an unspecified position. // Returns the position where added. //Pre: getSize() < getMaxSize() void remove (int index); // Remove the item at the indicated position //Pre: 0 <= index < getSize() int findBidder (std::string name) const; // Returns the position where a bidde mathcing the given // name can be found, or getSize() if no bidder with that name exists. /** * Read all items from the indicated file */ void readBidders (std::string fileName); // Print the collection void print (std::ostream& out) const; }; inline std::ostream& operator<< (std::ostream& out, const BidderCollection& b) { b.print(out); return out; } #endif
and
../opoverload-auctionOut/biddercollection.cpp#include <iostream> #include "arrayUtils.h" #include <fstream> #include "biddercollection.h" using namespace std; /** * Create a collection capable of holding the indicated number of items */ BidderCollection::BidderCollection (int MaxBidders) : MaxSize(MaxBidders), size(0) { elements = new Bidder [MaxSize]; } BidderCollection::~BidderCollection () { delete [] elements; } // Collection operations int BidderCollection::add (const Bidder& value) // Adds this bidder //Pre: getSize() < getMaxSize() { addToEnd (elements, size, value); return size - 1; } void BidderCollection::remove (int index) // Remove the bidder at the indicated position //Pre: 0 <= index < getSize() { removeElement (elements, size, index); } /** * Read all bidders from the indicated file */ void BidderCollection::readBidders (std::string fileName) { size = 0; ifstream in (fileName.c_str()); int nBidders; in >> nBidders; for (int i = 0; i < nBidders && i < MaxSize; ++i) { string nme; double bal; in >> nme >> bal; Bidder bidder (nme, bal);; add (bidder); } } /** * Find the index of the bidder with the given name. If no such bidder exists, * return nBidders. */ int BidderCollection::findBidder (std::string name) const { int found = size; for (int i = 0; i < size && found == size; ++i) { if (name == elements[i].getName()) found = i; } return found; } // Print the collection void BidderCollection::print (std::ostream& out) const { out << size << "/" << MaxSize << "{"; for (int i = 0; i < size; ++i) { out << " "; elements[i].print (out); out << "\n"; } out << "}"; }
-
The ItemCollection ADT:
../opoverload-auctionOut/itemcollection.h#ifndef ITEMCOLLECTION_H #define ITEMCOLLECTION_H #include "items.h" #include <iostream> class ItemCollection { int MaxSize; int size; Item* elements; // array of items public: /** * Create a collection capable of holding the indicated number of items */ ItemCollection (int MaxItems = 1000); ~ItemCollection (); // Access to attributes int getMaxSize() const {return MaxSize;} int getSize() const {return size;} // Access to individual elements const Item& get(int index) const {return elements[index];} // Collection operations void addInTimeOrder (const Item& value); // Adds this item into a position such that // all items are ordered by the time at which the auction for the // item ends. //Pre: getSize() < getMaxSize() void remove (int index); // Remove the item at the indicated position //Pre: 0 <= index < getSize() /** * Read all items from the indicated file */ void readItems (std::string fileName); // Print the collection void print (std::ostream& out) const; }; inline std::ostream& operator<< (std::ostream& out, const ItemCollection& b) { b.print(out); return out; } #endif
and
../opoverload-auctionOut/itemcollection.cpp#include <iostream> #include "arrayUtils.h" #include <fstream> #include "itemcollection.h" // // Items up for auction // using namespace std; /** * Create a collection capable of holding the indicated number of items */ ItemCollection::ItemCollection (int MaxItems) : MaxSize(MaxItems), size(0) { elements = new Item [MaxSize]; } ItemCollection::~ItemCollection () { delete [] elements; } // Collection operations void ItemCollection::addInTimeOrder (const Item& value) // Adds this item into a position such that // all items are ordered by the time the item' auction ends //Pre: getSize() < getMaxSize() { // Make room for the insertion int toBeMoved = size - 1; while (toBeMoved >= 0 && value.getAuctionEndTime().noLaterThan(elements[toBeMoved].getAuctionEndTime())) { elements[toBeMoved+1] = elements[toBeMoved]; --toBeMoved; } // Insert the new value elements[toBeMoved+1] = value; ++size; } void ItemCollection::remove (int index) // Remove the item at the indicated position //Pre: 0 <= index < getSize() { removeElement (elements, size, index); } /** * Read all items from the indicated file */ void ItemCollection::readItems (std::string fileName) { size = 0; ifstream in (fileName.c_str()); int nItems; in >> nItems; for (int i = 0; i < nItems; ++i) { Item item; item.read (in); addInTimeOrder (item); } } // Print the collection void ItemCollection::print (std::ostream& out) const { out << size << "/" << MaxSize << "{"; for (int i = 0; i < size; ++i) { out << " "; elements[i].print (out); out << "\n"; } out << "}"; }
Debugging with the Output Op
Note how much cleaner the debugging output statements look.
Instead of
if (bid.getAmount() <= bidder.getBalance())
{
highestBidSoFar = bid.getAmount();
winningBidderSoFar = bid.getBidder();
cerr << "Best bid so far is ";
bid.print (cerr);
cerr << " from ";
bidder.print (cerr);
cerr << endl;
}
we now can write
if (bid.getAmount() <= bidder.getBalance())
{
highestBidSoFar = bid.getAmount();
winningBidderSoFar = bid.getBidder();
cerr << "Best bid so far is "
<< bid << " from "
<< bidder << endl;
}
Return Values
The <<
operator must return the stream it is applied to.
-
That’s why we can write things like
cout << a << b;
-
The compiler treats this as
(cout << a) << b;
-
What is the
<< b
applied to? -
To the value returned by
(cout << a)
!
-
-
You can also view this as
operator<<(operator<<(cout, a), b)
Fine Points
-
These are not member functions, so they cannot access private data directly
-
You can get around this by using
friend
declarations described in the text.
-
-
Programming input operators is less common, but very similar to output operators.
3 Comparison Operators
Almost every class should provide ==
and <
operators.
-
Many data structures and functions in the standard library assume these are available.
-
So do some of the ones we have developed, e.g., in
#ifndef ARRAYUTILS_H
#define ARRAYUTILS_H
// Add to the end
// - Assumes that we have a separate integer (size) indicating how
// many elements are in the array
// - and that the "true" size of the array is at least one larger
// than the current value of that counter
template <typename T>
void addToEnd (T* array, int& size, T value)
{
array[size] = value;
++size;
}
// Add value into array[index], shifting all elements already in positions
// index..size-1 up one, to make room.
// - Assumes that we have a separate integer (size) indicating how
// many elements are in the array
// - and that the "true" size of the array is at least one larger
// than the current value of that counter
template <typename T>
void addElement (T* array, int& size, int index, T value)
{
// Make room for the insertion
int toBeMoved = size - 1;
while (toBeMoved >= index) {
array[toBeMoved+1] = array[toBeMoved];
--toBeMoved;
}
// Insert the new value
array[index] = value;
++size;
}
// Assume the elements of the array are already in order
// Find the position where value could be added to keep
// everything in order, and insert it there.
// Return the position where it was inserted
// - Assumes that we have a separate integer (size) indicating how
// many elements are in the array
// - and that the "true" size of the array is at least one larger
// than the current value of that counter
template <typename T>
int addInOrder (T* array, int& size, T value)
{
// Make room for the insertion
int toBeMoved = size - 1;
while (toBeMoved >= 0 && value < array[toBeMoved]) {
array[toBeMoved+1] = array[toBeMoved];
--toBeMoved;
}
// Insert the new value
array[toBeMoved+1] = value;
++size;
return toBeMoved+1;
}
// Search an array for a given value, returning the index where
// found or -1 if not found.
template <typename T>
int seqSearch(const T list[], int listLength, T searchItem)
{
int loc;
for (loc = 0; loc < listLength; loc++)
if (list[loc] == searchItem)
return loc;
return -1;
}
// Search an ordered array for a given value, returning the index where
// found or -1 if not found.
template <typename T>
int seqOrderedSearch(const T list[], int listLength, T searchItem)
{
int loc = 0;
while (loc < listLength && list[loc] < searchItem)
{
++loc;
}
if (loc < listLength && list[loc] == searchItem)
return loc;
else
return -1;
}
// Removes an element from the indicated position in the array, moving
// all elements in higher positions down one to fill in the gap.
template <typename T>
void removeElement (T* array, int& size, int index)
{
int toBeMoved = index + 1;
while (toBeMoved < size) {
array[toBeMoved] = array[toBeMoved+1];
++toBeMoved;
}
--size;
}
// Search an ordered array for a given value, returning the index where
// found or -1 if not found.
template <typename T>
int binarySearch(const T list[], int listLength, T searchItem)
{
int first = 0;
int last = listLength - 1;
int mid;
bool found = false;
while (first <= last && !found)
{
mid = (first + last) / 2;
if (list[mid] == searchItem)
found = true;
else
if (searchItem < list[mid])
last = mid - 1;
else
first = mid + 1;
}
if (found)
return mid;
else
return -1;
}
#endif
Comparison Operators for Time
-
We declare the comparison ops the same way we do other operator functions
-
Time
is a simple case, since it has only one data member
#ifndef TIMES_H
#define TIMES_H
#include <iostream>
/**
* Times in this program are represented by three integers: H, M, & S, representing
* the hours, minutes, and seconds, respecitvely.
*/
class Time {
public:
// Create time objects
Time(); // midnight
Time (int h, int m, int s);
// Access to attributes
int getHours() const;
int getMinutes() const;
int getSeconds() const;
// Calculations with time
void add (Time delta);
Time difference (Time fromTime);
/**
* Read a time (in the format HH:MM:SS) after skipping any
* prior whitepace.
*/
void read (std::istream& in);
/**
* Print a time in the format HH:MM:SS (two digits each)
*/
void print (std::ostream& out) const;
// Comparison operators
bool operator== (const Time&) const;
bool operator< (const Time&) const;
private:
// From here on is hidden
int secondsSinceMidnight;
};
inline
std::ostream& operator<< (std::ostream& out, const Time& t)
{
t.print(out);
return out;
}
inline
bool operator!= (const Time& t1, const Time& t2)
{
return !(t1 == t2);
}
inline
bool operator> (const Time& t1, const Time& t2)
{
return t2 < t1;
}
inline
bool operator<= (const Time& t1, const Time& t2)
{
return !(t1 > t2);
}
inline
bool operator>= (const Time& t1, const Time& t2)
{
return !(t1 < t2);
}
#endif // TIMES_H
#include "time.h"
#include <iomanip>
using namespace std;
/**
* Times in this program are represented by three integers: H, M, & S, representing
* the hours, minutes, and seconds, respecitvely.
*/
// Create time objects
Time::Time() // midnight
{
secondsSinceMidnight = 0;
}
Time::Time (int h, int m, int s)
{
secondsSinceMidnight = s + 60 * m + 3600*h;
}
// Access to attributes
int Time::getHours() const
{
return secondsSinceMidnight / 3600;
}
int Time::getMinutes() const
{
return (secondsSinceMidnight % 3600) / 60;
}
int Time::getSeconds() const
{
return secondsSinceMidnight % 60;
}
// Calculations with time
void Time::add (Time delta)
{
secondsSinceMidnight += delta.secondsSinceMidnight;
}
Time Time::difference (Time fromTime)
{
Time diff;
diff.secondsSinceMidnight =
secondsSinceMidnight - fromTime.secondsSinceMidnight;
}
/**
* Read a time (in the format HH:MM:SS) after skipping any
* prior whitepace.
*/
void Time::read (std::istream& in)
{
char c;
int hours, minutes, seconds;
in >> hours >> c >> minutes >> c >> seconds;
Time t (hours, minutes, seconds);
secondsSinceMidnight = t.secondsSinceMidnight;
}
/**
* Print a time in the format HH:MM:SS (two digits each)
*/
void Time::print (std::ostream& out) const
{
out << setfill('0') << setw(2) << getHours() << ':'
<< setfill('0') << setw(2) << getMinutes() << ':'
<< setfill('0') << setw(2) << getSeconds();
}
// Comparison operators
bool Time::operator< (const Time& time2) const
{
return secondsSinceMidnight < time2.secondsSinceMidnight;
}
bool Time::operator==(const Time& time2) const
{
return secondsSinceMidnight == time2.secondsSinceMidnight;
}
3.1 Equality Operators
Equality Operators for Compound Data
- You have to think about what you want
==
to mean for your ADT.-
May depend on the application
-
Common: Compare Every Member
For ADTs with multiple data members, we often expect every “significant” data member to match. E.g.,
class Bid {
std::string bidderName;
double amount;
std::string itemName;
Time bidPlacedAt;
⋮
can be compared like this
bool Bid::operator== (const Bid& b) const
{
return bidderName == b.bidderName
&& amount == b.amount
&& itemName == b.itemName
&& bidPlacedAt == b.bidPlacedAt;
}
Highly Variable Data
Fields that change frequently may or may not be significant:
bool Bidder::operator== (const Bidder& b) const
{
return name == b.name; // ignore balance
}
-
Two Bidder records describe the same person if the names match, even if their bank balances differ.
-
Some would say that the name is a key that identifies the Bidder,
Equality for ADTs with Arrays
bool BidCollection::operator== (const BidCollection& bc) const
{
if (size == bc.size) ➀
{
for (int i = 0; i < size; ++i) ➁
if (!(elements[i] == ➂bc.elements[i]))
return false;
return true;
}
else
return false;
}
-
➀ Do the quick & easy check first (
size
) -
➁ Then insist that each array element must match
- ➂ Note that this uses the Bid::operator== we developed above
-
I did not compare the
MaxSize
values because that is not, logically, part of the value of the collection. It’s more of an internal detail.
3.2 Less-Than Operators
<
is not always “less than”
-
What does
<
mean for compound data structures?-
Can a bid, for example, be said to be “less than” another bid? Bidders? Items?
-
-
<
is used mainly to put things into a sorted order -
So think of it as meaning “comes before”, not “is smaller than”
Less-than on Compound Data
-
For ADTs with multiple data members, we usually model
operator<
on lexicographic (alphabetic) order rules.-
When comparing two strings, we compare the first two characters
-
If they are different, we immediately decide that one string is
<
than the other. -
If they are equal, we move on to the next character and repeat steps 2-3
-
-
E.g.,
"abc" < "bbc"
,"cbc" > "bbc"
,"abc" < "abd"
,"abc" > "aba"
Extending Lexicographic Order to Non-Strings
Compare members until we find two that are not equal:
bool Bid::operator< (const Bid& b) const
{
if (bidPlacedAt < b.bidPlacedAt)
return true;
else if (b.bidPlacedAt < bidPlacedAt)
return false;
if (bidderName < b.bidderName)
return true;
else if (b.bidderName < bidderName)
return false;
if (itemName < b.itemName)
return true;
else if (b.itemName < itemName)
return false;
if (amount < b.amount)
return true;
else if (b.amount < amount)
return false;
return false;
}
Less-than for ADTs with Arrays
We extend the same idea to arrays of data:
bool BidCollection::operator< (const BidCollection& bc) const
{
if (size == bc.size) ➀
{
for (int i = 0; i < size; ++i) ➁
{
if (elements[i] < bc.elements[i]) ➂
return true;
else if (bc.elements[i] < elements[i]) ➂
return false;
// else keep going
}
// All of the elements are equal
return false; ➃
}
else
return size < bc.size; ➀
}
-
➀ Do the quick & easy check first (
size
) -
➁ Then run through the arrays until we find two elements that are not equal (➂ )
-
➃ If all the array elements are equal, then this is not less than the other (they are equal).
Adding Both == and <
We often have choices in how to implement these two.
-
They must be consistent.
For any two values
x
andy
, exactly one of the following conditions must be true:-
x == y
-
x < y
-
y > x
-
-
A good rule of thumb: every data member checked by
==
should be checked by<
, and vice-versa
Why Just Two?
- Why do we often just provide
==
and<
?
#ifndef TIMES_H
#define TIMES_H
#include <iostream>
/**
* Times in this program are represented by three integers: H, M, & S, representing
* the hours, minutes, and seconds, respecitvely.
*/
class Time {
public:
// Create time objects
Time(); // midnight
Time (int h, int m, int s);
// Access to attributes
int getHours() const;
int getMinutes() const;
int getSeconds() const;
// Calculations with time
void add (Time delta);
Time difference (Time fromTime);
/**
* Read a time (in the format HH:MM:SS) after skipping any
* prior whitepace.
*/
void read (std::istream& in);
/**
* Print a time in the format HH:MM:SS (two digits each)
*/
void print (std::ostream& out) const;
// Comparison operators
bool operator== (const Time&) const;
bool operator< (const Time&) const;
private:
// From here on is hidden
int secondsSinceMidnight;
};
inline
std::ostream& operator<< (std::ostream& out, const Time& t)
{
t.print(out);
return out;
}
inline
bool operator!= (const Time& t1, const Time& t2)
{
return !(t1 == t2);
}
inline
bool operator> (const Time& t1, const Time& t2)
{
return t2 < t1;
}
inline
bool operator<= (const Time& t1, const Time& t2)
{
return !(t1 > t2);
}
inline
bool operator>= (const Time& t1, const Time& t2)
{
return !(t1 < t2);
}
#endif // TIMES_H
-
It’s easy to define the others in terms of these two.
-
in fact, the C++ std library has function templates that already do this.
-
If your file
\#include
s the header<utility>
and has the statementusing namespace std::rel_ops;
then the operators
!=
,>
,<=
, and>=
will be provided based on your own==
and<
.
-
Taking Advantage of the Relational Ops
If we make a habit of providing these, we can often use “canned” versions of our algorithms instead of needing to write specialized versions.
For example, given:
#ifndef ARRAYUTILS_H
#define ARRAYUTILS_H
// Add to the end
// - Assumes that we have a separate integer (size) indicating how
// many elements are in the array
// - and that the "true" size of the array is at least one larger
// than the current value of that counter
template <typename T>
void addToEnd (T* array, int& size, T value)
{
array[size] = value;
++size;
}
// Add value into array[index], shifting all elements already in positions
// index..size-1 up one, to make room.
// - Assumes that we have a separate integer (size) indicating how
// many elements are in the array
// - and that the "true" size of the array is at least one larger
// than the current value of that counter
template <typename T>
void addElement (T* array, int& size, int index, T value)
{
// Make room for the insertion
int toBeMoved = size - 1;
while (toBeMoved >= index) {
array[toBeMoved+1] = array[toBeMoved];
--toBeMoved;
}
// Insert the new value
array[index] = value;
++size;
}
// Assume the elements of the array are already in order
// Find the position where value could be added to keep
// everything in order, and insert it there.
// Return the position where it was inserted
// - Assumes that we have a separate integer (size) indicating how
// many elements are in the array
// - and that the "true" size of the array is at least one larger
// than the current value of that counter
template <typename T>
int addInOrder (T* array, int& size, T value)
{
// Make room for the insertion
int toBeMoved = size - 1;
while (toBeMoved >= 0 && value < array[toBeMoved]) {
array[toBeMoved+1] = array[toBeMoved];
--toBeMoved;
}
// Insert the new value
array[toBeMoved+1] = value;
++size;
return toBeMoved+1;
}
// Search an array for a given value, returning the index where
// found or -1 if not found.
template <typename T>
int seqSearch(const T list[], int listLength, T searchItem)
{
int loc;
for (loc = 0; loc < listLength; loc++)
if (list[loc] == searchItem)
return loc;
return -1;
}
// Search an ordered array for a given value, returning the index where
// found or -1 if not found.
template <typename T>
int seqOrderedSearch(const T list[], int listLength, T searchItem)
{
int loc = 0;
while (loc < listLength && list[loc] < searchItem)
{
++loc;
}
if (loc < listLength && list[loc] == searchItem)
return loc;
else
return -1;
}
// Removes an element from the indicated position in the array, moving
// all elements in higher positions down one to fill in the gap.
template <typename T>
void removeElement (T* array, int& size, int index)
{
int toBeMoved = index + 1;
while (toBeMoved < size) {
array[toBeMoved] = array[toBeMoved+1];
++toBeMoved;
}
--size;
}
// Search an ordered array for a given value, returning the index where
// found or -1 if not found.
template <typename T>
int binarySearch(const T list[], int listLength, T searchItem)
{
int first = 0;
int last = listLength - 1;
int mid;
bool found = false;
while (first <= last && !found)
{
mid = (first + last) / 2;
if (list[mid] == searchItem)
found = true;
else
if (searchItem < list[mid])
last = mid - 1;
else
first = mid + 1;
}
if (found)
return mid;
else
return -1;
}
#endif
-
Compare our old code:
void ItemCollection::addInTimeOrder (const Item& value) // Adds this item into a position such that // all items are ordered by the time the item' auction ends //Pre: getSize() < getMaxSize() { // Make room for the insertion int toBeMoved = size - 1; while (toBeMoved >= 0 && value.getAuctionEndTime().noLaterThan(elements[toBeMoved].getAuctionEndTime())) { elements[toBeMoved+1] = elements[toBeMoved]; --toBeMoved; } // Insert the new value elements[toBeMoved+1] = value; ++size; }
-
to this more streamlined version
void ItemCollection::addInTimeOrder (const Item& value) // Adds this item into a position such that // all items are ordered by the time the item' auction ends //Pre: getSize() < getMaxSize() { addInOrder (elements, size, value); }
which is possible only because we now have an
operator<
onItem
s.