18-1 Computing Fundamentals with C++ Object-Oriented Programming and Design, 2nd Edition Rick Mercer Franklin, Beedle & Associates, 1999 ISBN Presentation Copyright 1999, Franklin, Beedle & Associates Students who purchase and instructors who adopt Computing Fundamentals with C++, Object-Oriented Programming and Design by Rick Mercer are welcome to use this presentation as long as this copyright notice remains intact.
18-2 Chapter 18 Operator Overloading Chapter Objectives overload != and < for any state object you want contained in a standard container vector list perform complex arithmetic overload > so you can output or input any new class of objects that you develop
18-3 C++ Operator Overloading Built-in types ( int double ) define operators = + - / Operator overloading: operators have different meanings for different operands: Expression "2" + "5" "2" + "5" 2 / 5 2 / / 5.0 Evaluates to 7 "25" 0 0.4
18-4 Syntactic sugar Operator overloading provides easier to read code: Employee e1, e2; Employee e1, e2; if(e1.lessThan(e2)) if(e1.lessThan(e2)) // no operator overloading needed to do this // no operator overloading needed to do this or if(e1 < e2) if(e1 < e2) // operator overloading for < must have // operator overloading for < must have // been done in class Employee // been done in class Employee
18-5 Some standard classes do Overloaded operators allow your own classes to "look like" the built-in types Some standard C++ classes overload some operators string and complex for example string s1, s2; cin >> s1 >> s2; if(s1 < s2) s1 += s2; s1 += s2;else s2 = s1 + s2; // perform s2 += s1; s2 = s1 + s2; // perform s2 += s1; cout << s2;
18-6 Our own classes could But what about programmer defined classes such as CD, bankAccount, and Date ? Wouldn't it be nice if you could overload? see Date Do you need to? Yes, if you want to use the STL
18-7 Date overloads - and + Consider adding or subtracting Date objects const Date today; // Default is today's date const Date today; // Default is today's date Date graduationDay(5, 14, 1999); Date graduationDay(5, 14, 1999); int collegeDaysLeft = graduationDay - today; int collegeDaysLeft = graduationDay - today; Date warrantyExpires; Date warrantyExpires; warrantyExpires = today + 90; warrantyExpires = today + 90; nice Now consider using the STL list container
18-8 Review: The STL list class A list object a container class like vector manages a collection of homogenous objects is sequenced--there is a first, second,...,last can add objects at the beginning or at the end of the list with push_front and push_back always knows how big it is is generic because it may be declared to store a collection of any class of objects has useful operations: find, remove, and sort
18-9 Construct, push_back, size Construct three list objects: list tests; // size is 0 list tests; // size is 0 list GPAs; list GPAs; list names; list names; Copy objects into the list with push_back: tests.push_back(95); tests.push_back(95); GPAs.push_back(3.67); GPAs.push_back(3.67); names.push_back("Chris"); names.push_back("Chris"); Access the number of objects with size: cout << tests.size() << endl; cout << tests.size() << endl; cout << GPAs.size() << endl; cout << GPAs.size() << endl; cout << names.size() << endl; cout << names.size() << endl;
18-10 Can we store objects we designed? The STL list contains int, double, and string What about our own objects? bankAccount one("Hal", ); bankAccount one("Hal", ); bankAccount two("Sue", ); bankAccount two("Sue", ); bankAccount tre("Deb", ); bankAccount tre("Deb", ); list acctList; list acctList; acctList.push_back(one); acctList.push_back(one); acctList.push_back(two); acctList.push_back(two); acctList.push_back(tre); acctList.push_back(tre); acctList.push_back(bankAccount("Four", 4.44)); acctList.push_back(bankAccount("Four", 4.44));
18-11 STL element requirements The STL containers (list, vector, map) require the contained elements to have a way to copy elements the container elements are copies of the objects inserted could use the default = operator a way to compare elements could use the relational operators != = != =
18-12 STL needs all relational operators defined Any STL container can be sorted when the elements understand less than < acctList.sort(); // overload < to sort acctList.sort(); // overload < to sort Any STL container can be searched when the elements understand equal == list ::iterator i; list ::iterator i; i = find(acctList.begin(), i = find(acctList.begin(), acctList.end(), acctList.end(), bankAccount("Sue", 0.00)); bankAccount("Sue", 0.00)); Other operations require other operators, so also overload !=, =, and >
18-13 Operator functions Operators functions have a different syntax Some are free functions today < tomorrow // operator < (Date,Date) today < tomorrow // operator < (Date,Date) Some are member functions d += 30; // d.operator += (int) d += 30; // d.operator += (int) Both need access to private data either directly or through an accessing function Here is a < free function for bankAccount it will use accessing functions to get data
18-14 The header file bankAccount.h class bankAccount { public: // constructors, modifiers, and this accessor // constructors, modifiers, and this accessor string name(); string name();private: // data members and functions // data members and functions}; //--auxiliary free functions bool operator < ( const bankAccount& left, const bankAccount& right ); const bankAccount& right ); bool operator == ( const bankAccount& left, const bankAccount& right ); const bankAccount& right );
18-15 What does < mean? Answer: anything you want The < and == operators are overloaded for bankAccount using this general form: bool operator an-operator bool operator an-operator ( const class & left, const class & right ); ( const class & left, const class & right ); We can give any meaning to less than < decision: a < b is true when left's name alphabetically precedes right's name
18-16 Visualize < between the parameters // from the file baccount.cpp bool operator < (const bankAccount& left, const bankAccount& right) const bankAccount& right){ return left.name() < right.name(); return left.name() < right.name();} // From a test driver int main() { bankAccount b1("Bob", ); bankAccount b1("Bob", ); bankAccount b2("Carla", ); bankAccount b2("Carla", ); if(b1 < b2) if(b1 < b2) //... //...
18-17 Completing the relational operators // from the file baccount.cpp bool operator == (const bankAccount& left, const bankAccount& right) const bankAccount& right){ return left.name() == right.name(); return left.name() == right.name();} bool operator!= (const bankAccount& lhs, const bankAccount& rhs) const bankAccount& rhs){ return lhs.name() != rhs.name(); return lhs.name() != rhs.name();}
18-18 Complete all 6 for each class // from the file baccount.cpp bool operator > (const bankAccount& lhs, const bankAccount& rhs) const bankAccount& rhs){ return lhs.name() > rhs.name(); return lhs.name() > rhs.name();} bool operator <= (const bankAccount& lhs, const bankAccount& rhs) const bankAccount& rhs){ return lhs.name() <= rhs.name(); return lhs.name() <= rhs.name();} bool operator >= (const bankAccount& lhs, const bankAccount& rhs) const bankAccount& rhs){ return lhs.name() >= rhs.name(); return lhs.name() >= rhs.name();}
18-19 Summing Up Operator overloading allows new classes to look more like built-in types don't go crazy C++ operator overloading is sometimes nice, sometimes necessary Operator functions have a different syntax The STL requires all six relational operators There is a shortcut described in the standard If you define < and == the compiler will define the rest for you
18-20 Coming Up Overloading input and output operators Friend functions for efficiency Member operator functions +=
Using Friend Functions The previous member functions call accessor methods to get the names these two function calls can slow down programs consider sorting a large list of objects, for example each comparison requires 2 function calls it would be better to access the data members directly but the data members are private C++ workaround: friend functions
18-22 friend functions functions can be declared inside a class with the friend tag this gives the function access to private data members Consider this modification
18-23 Not a member function, but rather a friend function class bankAccount { public: //... //... double balance() const; double balance() const; string name() const; string name() const; //--friend operator functions friend bool operator == (const bankAccount& left, friend bool operator == (const bankAccount& left, const bankAccount& right); const bankAccount& right); friend bool operator < (const bankAccount& left, friend bool operator < (const bankAccount& left, const bankAccount& right); const bankAccount& right); //... //...};
18-24 Implementing friend functions Friend functions are free functions They have access to the private data fields of the class where they were declared bool operator == (const bankAccount& left, bool operator == (const bankAccount& left, const bankAccount& right) const bankAccount& right) { // Reference data members directly { // Reference data members directly return left.my_name == right.my_name; return left.my_name == right.my_name; } bool operator < (const bankAccount& left, bool operator < (const bankAccount& left, const bankAccount& right) const bankAccount& right) { // Avoid function-call overhead { // Avoid function-call overhead return left.my_name < right.my_name; return left.my_name < right.my_name; }
Overloading = for Classes with Pointer Data Members C++ overrides the = operator for vectors So if a collection class like bag is assigned to another, all vector elements are actually copied from one to the other // The bag class with a vector data member // The bag class with a vector data member // obtained with a typedef and #include "bag" // obtained with a typedef and #include "bag" bag b1; bag b1; bag b2; bag b2; for(int j = 0; j < 6; j++) for(int j = 0; j < 6; j++) b1.add(j); b1.add(j); b2 = b1; b2 = b1; // b2 is exactly like b1 because vector overloads = // b2 is exactly like b1 because vector overloads =
18-26 = with pointers Since the generic class has a pointer, the same assignment does not work. So if a collection class like bag is assigned to another, only the pointer is copied shallow copy // The bag class with a pointer data member // The bag class with a pointer data member // obtained with #include "genbag" // obtained with #include "genbag" bag b1; bag b1; bag b2; bag b2; for(int j = 0; j < 6; j++) for(int j = 0; j < 6; j++) b1.add(j); b1.add(j); b2 = b1; b2 = b1; // Only the pointer gets copied and the program // Only the pointer gets copied and the program // could terminate prematurely later on. // could terminate prematurely later on.
18-27 Overloading = for vector The bag class should overload = The operator = function must copy the individual objects from one bag to the other use a for loop For example, the vector class that comes with this book does that see next slide
18-28 vector operator = // operator = is defined inside the vector class definition vector& operator = (const vector & right) { if( this != &right ) // Skip deleting when aVec == aVec if( this != &right ) // Skip deleting when aVec == aVec { // Deallocate unneeded memory (x is a pointer) { // Deallocate unneeded memory (x is a pointer) delete [] x; delete [] x; // Copy the capacity data member // Copy the capacity data member this->my_capacity = right.my_capacity; this->my_capacity = right.my_capacity; // Allocate precisely the correct amount of memory // Allocate precisely the correct amount of memory this->x = new VectorElementType [this->my_capacity]; this->x = new VectorElementType [this->my_capacity]; // Copy the individual items from the right to the left // Copy the individual items from the right to the left for(int j = 0; j my_capacity; j++) for(int j = 0; j my_capacity; j++) { this->x[j] = right.x[j]; this->x[j] = right.x[j]; } } return *this; return *this;}
18-29 How it works First check to make sure you are not assigning the vector to itself this expression is true if the address of the object to the left of = (this) is the same address as the object to the right &right if( this != &right ) // Skip deleting when aVec == aVec if( this != &right ) // Skip deleting when aVec == aVec Don't do it. Otherwise the object gets deleted with delete [] x; delete [] x; Then copy the value of capacity Then use a for loop to copy every element
18-30 String operator = The vector = function finally returns the address of new vector object vector& The string operator = function uses the same algorithm instead of a for loop, string uses the strcpy function. see next slide
18-31 String operator = string& string::operator = (const string& right) { if (this != (&right) ) if (this != (&right) ) { delete [] my_chars; delete [] my_chars; my_len = right.my_len; my_len = right.my_len; my_chars = new char[my_len + 1]; my_chars = new char[my_len + 1]; strcpy(my_chars, right.my_chars); strcpy(my_chars, right.my_chars); } return *this; return *this;}
Developing the complex Class This complex number class acts as a review for operator overloading Think of a complex number as one point on the Cartesian (complex) plane. The point is usually written as (1, 2) where 1 is the number on the real axis (x axis) and 2 is a value on the imaginary axis (y axis).
18-33 Imaginary Axis ¦ 3i + 3i + ¦ (3.0, 2.0) ¦ (3.0, 2.0) 2i + 2i + (-2.0, 1.0) ¦ (-2.0, 1.0) ¦ i + i + ¦ (3.5, 0.0) ¦ (3.5, 0.0) Real Axis Real Axis ¦ ¦ i + -i + ¦(1.5, -2.0) ¦(1.5, -2.0) -2i + -2i + (-3.0, -3.0) ¦ (-3.0, -3.0) ¦ -3i + -3i + ¦
18-34 Complex numbers Complex numbers are used a lot in certain fields electrical engineering for example Standard C++ now has a complex class we'll develop our own We'll consider a simple app: find the center of a triangle defined as the average of the three points sum three complex numbers and divide by three see next slide
18-35 complex overloads +, /, << #include #include using namespace std; #include "ltcomplx" // author-supplied complex class int main() { // construct three complex numbers complex a(0, 0); complex a(0, 0); complex b(1, 1); complex b(1, 1); complex c(0.75, 2.15); complex c(0.75, 2.15); complex triangleCenter = (a + b + c) / 3.0; complex triangleCenter = (a + b + c) / 3.0; cout << "\nCenter of triangle (" << a << " + " cout << "\nCenter of triangle (" << a << " + " << b << " + " << c << ") / 3 is " << endl; << b << " + " << c << ") / 3 is " << endl; cout << triangleCenter << endl; cout << triangleCenter << endl;}Output Center of triangle ((0, 0) + (1, 1) + (0.75, 2.15)) / 3 is ( , 1.05)
Design Want the complex class to handle complex arithmetic as easily as real arithmetic with int and double like to have + - / * like to allow input and output there are a host of other complex function such as abs and sqrt don't want programmers to have to remember and perform complex multiplication and division
Implementation Each complex number has a real part and an imaginary part two doubles called my_real and my_imag could store the state of a complex object We could have several constructors default, one argument and 2 arguments to allow complex a; complex a; complex b( -1.0 ); // the real number -1.0 complex b( -1.0 ); // the real number -1.0 complex b( 12.57, 1.99 ); complex b( 12.57, 1.99 );
18-38 Also overload some operators as friend functions class complex { public://--constructors complex(); // complex a; complex(); // complex a; complex(double initReal) // complex b(1.0); complex(double initReal) // complex b(1.0); complex(double initReal, // complex c( ); complex(double initReal, // complex c( ); double initImag); double initImag);//--accessors double real(); double real(); double imag(); double imag(); //--operator functions friend complex operator + (complex left, complex right); friend complex operator + (complex left, complex right); friend complex operator / (complex left, complex right); friend complex operator / (complex left, complex right); friend ostream& operator << (ostream& os, complex right); friend ostream& operator << (ostream& os, complex right); friend istream& operator >> (istream& is, complex& right); friend istream& operator >> (istream& is, complex& right);private: double my_real; double my_real; double my_imag; double my_imag;};
18-39 Overloading + complex operator + (complex left, complex right) { // Complex addition // (a + bi) + (c + di) = (a + c) + (b + d)i // (a + bi) + (c + di) = (a + c) + (b + d)i // a + c, the real part // a + c, the real part double tempReal = left.my_real + right.my_real; double tempReal = left.my_real + right.my_real; // b + d, imaginary part // b + d, imaginary part double tempImag = left.my_imag + right.my_imag; double tempImag = left.my_imag + right.my_imag; // Construct a complex object and return it // Construct a complex object and return it complex temp(tempReal, tempImag); complex temp(tempReal, tempImag); return temp; return temp; }
18-40 Overloading << The input and output operators can be overloaded for any new class in fact, the C++ system does this for all the standard types What has to be done: Make operator << it a friend for efficiency take an ostream& parameter in addition to the complex argument return a reference to the changed ostream
18-41 operator << The left operator is an ostream object, cout for instance, the complex object is the right cout << complex( 1.2, 3.4 ) << endl; cout << complex( 1.2, 3.4 ) << endl; The return type is needed to allow chaining output together like this complex a, b, c; complex a, b, c; cout << a << b << c; cout << a << b << c; cout << a returns the ostream you are outputting to so cout << b can occur next
18-42 output it as you please use (, and ) Here is the function heading as from ltcomplex.h //--operator function //--operator function friend ostream& operator << (ostream& os, friend ostream& operator << (ostream& os, complex right); complex right); Its implementation ostream& operator << (ostream& os, complex right) ostream& operator << (ostream& os, complex right) { left << "(" << right.my_real << ", " left << "(" << right.my_real << ", " << right.my_imag << ")"; << right.my_imag << ")"; return os; return os; }
18-43 Complex number output Output complex a(0.75, 2.15); complex a(0.75, 2.15); complex b(-1.5, 0.5); complex b(-1.5, 0.5); cout << a << " " << b << endl; cout << a << " " << b << endl;Output (0.75, 2.15) (-1.5, 0.5) (0.75, 2.15) (-1.5, 0.5)
Overloading >> for complex input You can also input a complex number as two numbers in a row must overload >> make it a friend function for efficiency need the left argument to be an istream object, cin for example need a complex reference parameter it must change return a reference to an ostream object to allow chaining of input like this complex a, b, c; complex a, b, c; cin >> a >> b >> c; cin >> a >> b >> c;
18-45 input as you please this just reads two numbers Here is the function heading as from ltcomplex.h //--operator function //--operator function friend istream& operator >> (istream& is, friend istream& operator >> (istream& is, complex right); complex right); Its implementation istream& operator >> (istream& left, complex& right) { double x, y; double x, y; // Get two numbers from the input stream is == cin // Get two numbers from the input stream is == cin is >> x >> y; // is is the same as cin is >> x >> y; // is is the same as cin // Now modify the right argument (c1) // Now modify the right argument (c1) right = complex(x, y); right = complex(x, y); return is; return is; };
18-46 Complex number output Output complex a(0.75, 2.15); complex a(0.75, 2.15); complex b(-1.5, 0.5); complex b(-1.5, 0.5); cout << a << " " << b << endl; cout << a << " " << b << endl;Output (0.75, 2.15) (-1.5, 0.5) (0.75, 2.15) (-1.5, 0.5)