460 likes | 471 Views
Explore implementing a data structure for fractions in C++ with operator overloading, conversions, and object behavior. Learn constructors, destructors, getters, setters, and automatic type conversions.
E N D
Operator Overloading Back to Fractions...
Implementing an Object • We’ve talked at length about object-orientation. • 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 intValue() const; double doubleValue() const; string toString() const;
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; int compare(const Fraction &f) const; bool equals(const Fraction &f) const; int intValue() const; double doubleValue() const; string toString() const; Note the use of const here - This means that the method will NOT change the object on which it is called
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; int compare(const Fraction &f) const; bool equals(const Fraction &f) const; int intValue() const; double doubleValue() const; string toString() const; Note also that we are returning the object itself rather than a pointer to the object as before (no *) Use of string means we must #include <string>
Implementing Our Object • Step 2: implementing the methods (goes in Fraction.cpp file). Fraction::Fraction() : numerator(0), denominator(1) {} Fraction::Fraction(int n, int d) { numerator = n; denominator = d; } Wouldn't it be nice if the fraction were in reduced form? Solution: implement a private gcd() function in Fraction, use it to reduce form
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 n1 = abs(n); // want these positive int n2 = abs(d); int gcd = 1; for (int k = 1; k <= n1 && k <= n2; ++k) { if (n1 % k == 0 && n2 % k == 0) gcd = k; } return gcd; } Remember to put prototype in .h file as private, static! Oh yeah! And be sure to #include <cstdlib> in Fraction.cpp file! Note: this is a 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 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 Fraction::subtract(const Fraction &f) const { intnum = this->numerator * f.denominator; num-=f.numerator * this->denominator; intdnm = f.denominator * denominator; return Fraction(num, dnm); }
Implementing Our Object • Step 2: … multiplication, division, ... Fraction 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 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.getNum(); 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::intValue() const { return (numerator)/(denominator); } double Fraction::doubleValue() const { return ((double) numerator )/((double) denominator); }
Implementing Our Object • Step 2: … and conversion to string string Fraction::toString() 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 testFraction.cpp) #include <iostream> #include <string> #include "Fraction.h" using namespace std; int main() { int i; int j; Fraction g1; ...
Testing Our Class • Step 3: Writing test code ... Fraction g1; cout << "Enter two integers: " << flush; cin >> i >> j; g1.setNum(i); // test setters g1.setDenom(j); cout << "Enter two integers: " << flush; cin >> i >> j; Fraction g2(i,j); // test list constructor ...
Testing Our Class • Step 3: Writing test code cout << g1.toString() // test toString << " plus " << g2.toString() << " equals " << g1.add(g2).toString() // test add << endl;
Testing Our Class • Step 3: etc. etc. etc. for other 3 fcns, then do compare: cout << g1.toString() << " compare " << g2.toString() << " is " << g1.compare(g2) << endl;
Testing Our Class • Step 3: and equals: cout << g1.toString() << " equals " << g2.toString() << " is " << g1.equals(g2) << endl; Return 0; // done for now! }
Exercise 1: • Implement Fraction class (you should have most of this from before) – just constructors, setters, add(), toString(), 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(); 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 (f1 > f2) { … } • 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; static int gcd(int n, int d); public: ... bool operator<(const Fraction& f) const; bool operator==(const Fraction& f) const; ... operator keyword Operator symbol(s)
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 << g1.toString() << " < " << g2.toString() << " is " << (g1 < g2) << endl; // now test auto conversion from int cout << g1.toString() << " plus 1 equals " << g1.add(1).toString() << 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& f) const; Fraction operator-(const Fraction& f) const; Fraction operator*(const Fraction& f) const; Fraction operator/(const Fraction& f) const; ... operator keyword Operator symbol(s)
More Operators • Step 2: implementation Fraction Fraction::operator+(const Fraction& f) const { return (add(f)); } Fraction Fraction::operator-(const Fraction& f) const { return (subtract(f)); }
Augmented Assignment • What about code like f1 += f2; • 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& f) ; Fraction& operator-=(const Fraction& f) ; Fraction& operator*=(const Fraction& f) ; Fraction& operator/=(const Fraction& 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 << g1.toString() << " plus equal " << g2.toString() << " equals "; g1 += g2; cout << g1.toString() << 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)