Operator Overloading Back to Fractions Implementing an Object
Operator Overloading Back to Fractions. . .
Implementing an Object • We’ve talked at length about objectorientation. – • We’ve looked heavily at encapsulation and related concerns. We’ve started to implement a data structure for fractions. – Data structures are special classes/objects designed to organize data within a program.
Fractions • As noted earlier, C++ does not have a fraction or rational data type or object class – This does not mean that we can’t create a class that provides this functionality! – Let's do it! – Get out laptops and bring up the code we started a couple weeks ago
Object Requirements • Our core idea: we want a class of objects that behave like fractions – What are some of the implications of this? – What must we store/track? – What operations should be possible? – What conversions? – What about operators?
Object Requirements • Our core idea: we want a class of objects that behave like fractions – Fractions have a numerator and a denominator – we must store these – We can assign, compare, add, subtract, multiply, and divide fractions – It would be nice to convert a fraction to an int or a double. How about the reverse directions? – What about strings?
Object Requirements • Our core idea: we want a class of objects that behave like fractions – Converting to int or double is easy. – Converting from int is easy – Converting from double. . . … not so much! – Converting to a string is easy. . . … from a string a bit harder
Object Requirements • Our core idea: we want a class of objects that behave like fractions – What about binary operators like +, -? – We can overload these just like functions! – How about +=, -=, etc. ? Yep! – How about =, <, >, etc. ? Yep, those too!
Object Requirements • • • We need constructors and destructors We may also need getters and setters for numerator and denominator We can also provide for “automatic” type conversion!
Implementing Our Object • Step 1: determining the declarations – These go in the header (Fraction. h) file. class Fraction { private: int numerator; int denominator; public: Fraction(int, int); Fraction(); // more later
Implementing Our Object • Step 1 (con't): more declarations Fraction add(const Fraction &f) const; Fraction subtract(const Fraction &f) const; Fraction multiply(const Fraction &f) const; Fraction divide(const Fraction &f) const; // comparison methods int compare(const Fraction &f) const; bool equals(const Fraction &f) const; // conversion methods int. Value() const; double. Value() const; string to. String() const;
Implementing Our Object Step 1(con't): more declarations Fraction add(const Fraction &f) const; Note thesubtract(const use of const here. Fraction multiply(const Fraction &f) const; This means that the method will Fraction divide(const Fraction &f) const; NOT change the object on which int compare(const Fraction &f) const; it is called. Fraction &f) const; bool equals(const int. Value() const; double. Value() const; string to. String() const;
Implementing Our Object Step 1(con't): more declarations Fraction add(const &f)are const; Note. Fraction also that we returning Fraction subtract(const Fraction &f)than const; the object itself rather Fraction multiply(const; a pointer to. Fraction the object&f) as before Fraction divide(const Fraction (no *) &f) const; int compare(const Fraction &f) const; bool equals(const Fraction &f) const; int. Value() const; Use ofconst; string means we must double. Value() string to. String() const; #include <string>
Implementing Our Object • Step 2: implementing the methods (goes in Fraction. cpp file). Fraction: : Fraction() : it be nice if{}the fraction numerator(0), Wouldn't denominator(1) were in reduced form? Solution: n, implement Fraction: : Fraction(int d) a private { gcd() function in Fraction, numerator = n; use it to reduce form denominator = d; }
Implementing Our Object • Revised version of constructor: Fraction: : Fraction() : numerator(0), denominator(1) {} Fraction: : Fraction(int n, int d) { int g = gcd(n, d); if (g > 1) { n /= g; d /= g; } numerator = n; denominator = d; }
Implementing Our Object • What does gcd look like? int Fraction: : gcd(int n, int d) { int n 1 = abs(n); // want these positive Remember to put int n 2 = abs(d); int gcd = 1; prototype in. h file as private, static! Oh for yeah! Andkbe= sure (int 1; k <= n 1 && k <= n 2; ++k) { to #include if (n 1<cstdlib> % k == 0 && n 2 % k == 0) in Fraction. cpp gcd = k; file! } Note: this is a return gcd; cheesy implementation } of the gcd function!
Implementing Our Object • Step 2: OK – constructors done, now for addition, etc. (we did this one before, 'member? ) Fraction: : add(const Fraction &f) const { int num = this->numerator * f. denominator; num += f. numerator * this->denominator; int dnm = f. denominator * denominator; return Fraction(num, dnm); }
Implementing Our Object • Step 2: … subtraction, multiplication, . . . Pretty straight-forward… Fraction: : subtract(const Fraction &f) con { int num = this->numerator * f. denominator; num -= f. numerator * this->denominator; int dnm = f. denominator * denominator; return Fraction(num, dnm); }
Implementing Our Object • Step 2: … multiplication, division, . . . Fraction: : multiply(const Fraction &f) const { int num = this->numerator * f. numerator; int dnm = this->denominator * f. denominator; return Fraction(num, dnm); }
Implementing Our Object • Step 2: … division, . . . Fraction: : divide(const Fraction &f) const { // divide is multiply by reciprocal int num = this->numerator * f. denominator; int dnm = this->denominator * f. numerator; return Fraction(num, dnm); }
Implementing Our Object • Step 2: Now for comparison. . . int Fraction: : compare(const Fraction &f) const { Fraction temp = subtract(f); // difference int num = temp. get. Num(); if (num < 0) return -1; // neg => smaller return (num > 0 ? 1 : 0); // pos => bigger } bool Fraction: : equals(const Fraction &f) const { return(0 == compare(f)); }
Implementing Our Object • Step 2: Conversion to built-ins. . . Easy peasy int Fraction: : int. Value() const { return (numerator)/(denominator); } double Fraction: : double. Value() const { return ((double) numerator )/((double) denominator); }
Implementing Our Object • Step 2: … and conversion to string Fraction: : to. String() const { stringstream ss; ss << numerator; if (denominator > 1 && numerator != 0) ss << "/" << denominator; return (ss. str()); } What is the if for? ? ? Prevent output like 3/1 and 0/4
Testing Our Class • Step 3: Writing some test code (This goes in test. Fraction. cpp) #include <iostream> #include <string> #include "Fraction. h" using namespace std; int main() { int i; int j; Fraction g 1; . . .
Testing Our Class • Step 3: Writing test code. . . Fraction g 1; cout << "Enter two integers: " << flush; cin >> i >> j; g 1. set. Num(i); // test setters g 1. set. Denom(j); cout << "Enter two integers: " << flush; cin >> i >> j; Fraction g 2(i, j); // test list constructor. . .
Testing Our Class • Step 3: Writing test code cout << << << g 1. to. String() // test to. String " plus " g 2. to. String() " equals " g 1. add(g 2). to. String() // test add endl;
Testing Our Class • Step 3: etc. for other 3 fcns, then do compare: cout << << << g 1. to. String() " compare " g 2. to. String() " is " g 1. compare(g 2) endl;
Testing Our Class • Step 3: and equals: cout << g 1. to. String() << " equals " << g 2. to. String() << " is " << g 1. equals(g 2) << endl; Return 0; } // done for now!
Exercise 1: • • Implement Fraction class (you should have most of this from before) – just constructors, setters, add(), to. String(), and compare() for now. . . Use direct form rather than pointer version Compile – no need to have these as 3 files for now. . . check #includes!!! Run test and see that it works
Review Requirements • We have met all the basic requirements • Arithmetic operations, comparison, conversion to float, int, and string Missing conversion from int, float, or string • What about operators? • And automatic conversion?
Conversion from int • • • Automatic (compiler) conversion from other types can occur when a function is called (such as add) that needs another Fraction, but an int or float is the actual parameter Compiler will first search for overloaded function with matching signature – not there! Then it looks for … constructor!
Conversion from int • Step 1: More declarations class Fraction { private: int numerator; int denominator; static int gcd(int n, int d); public: Fraction(int, int); Fraction(int n); // conversion from int. . .
Conversion from int • Step 2: implementation Fraction: : Fraction(int n) { numerator = n; denominator = 1; } So what about floats? Hmmmmmm. .
Overloading Operators • Wouldn't it be nice to be able to use code like if (f 1 > f 2) { … } • • Well, C++ allows this! In fact, C++ allows lots of operators to be overloaded Use the special operator function Named with operator keyword followed by the actual operator
Overloading Operators • Step 1: More declarations class Fraction { private: int numerator; int denominator; operator static int keyword gcd(int n, Operator int d); symbol(s) public: . . . bool operator<(const Fraction& f) const; bool operator==(const Fraction& f) const; . . .
Overloading Operators • Step 2: implementation bool Fraction: : operator<(const Fraction& f) const { return (compare(f) < 0); } bool Fraction: : operator==(const Fraction& f) const { return (compare(f) == 0); }
Testing Operator Overload • Step 3: Writing some test code . . . // test overloading < operator cout << g 1. to. String() << " << g 2. to. String() << " is " << (g 1 < g 2) << endl; // now test auto conversion from int cout << g 1. to. String() << " plus 1 equals " << g 1. add(1). to. String() << endl; . . .
Exercise 2: • Add declarations and implementation for integer conversion and overloading < operator • Add a little code to test these • Compile • Run test and see that it works
More Operators • Step 1: More declarations class Fraction {. . . public: . . . Fraction operator+(const Fraction operator-(const Fraction operator*(const Fraction operator/(const. . . operator keyword Fraction& f) f) const; Operator symbol(s)
More Operators • Step 2: implementation Fraction: : operator+(const Fraction& f) const { return (add(f)); } Fraction: : operator-(const Fraction& f) const { return (subtract(f)); }
Augmented Assignment • What about code like f 1 += f 2; • • Well, C++ also allows this! Issue here is that assignment also returns a Lvalue. . . how to solve? So declare as reference Fraction& and return object Can't use const any more!!! Why not?
AA Operators • Step 1: More declarations class Fraction {. . . public: . . . Fraction& operator+=(const Fraction& operator-=(const Fraction& operator*=(const Fraction& operator/=(const Fraction& f) f) ; ; . . . reference type return value No more const!
More Operators • Step 2: implementation Fraction& Fraction: : operator+=(const Fraction& f) { *this = this->add(f); return *this; Modify object } Return modified object reference return type so it can be Lvalue
More Testing Overload • Step 3: Writing some test code . . . cout << g 1. to. String() << " plus equal " << g 2. to. String() << " equals "; g 1 += g 2; cout << g 1. to. String() << endl; . . .
Exercise 3: • Add declarations and implementation for overloading + and += operators • Add a little code to test these • Compile • Run test and see that it works
Operator Overloading • • Can also overload [] indexing operator – see lab Only operators that can't be overloaded in C++ are: ? : • . . * : : Only overload operators when the overloaded function fulfills the logical function of the original operator!
Questions? • • Project 4: Implement overload operators for set and multiset: + (union), - (subtraction), +=, -=, * (intersect), ^ (difference), *=, ^=, == (equality), < (proper subset), <= (subset)
- Slides: 46