Last modified: Jul 16, 2014
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 if x and y 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: < > <= >= = == !=
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 for
operator+(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 =
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:
#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
#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 << "]";
}
#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
#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 << "]";
}
#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
#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 << "]";
}
#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
#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;
}
#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
#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 << "}";
}
#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
#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 << "}";
}
#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
#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
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;
}
Equality Operators for Compound Data
You have to think about what you want == to mean for your ADT.
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)
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.
< is not always “less than”
What does < mean for compound data structures?
< 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
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 and y, 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?
#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 \#includes the header <utility> and has the statement
using 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< on Items.