operator overloading back to fractions.... implementing an object we’ve talked at length about...
TRANSCRIPT
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 declarationsFraction add(const Fraction &f) const;Fraction subtract(const Fraction &f) const;Fraction multiply(const Fraction &f) const;Fraction divide(const Fraction &f) const; // comparison methodsint compare(const Fraction &f) const;bool equals(const Fraction &f) const; // conversion methodsint 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 willNOT 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 returningthe 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 fractionwere in reduced form?
Solution: implement a privategcd() 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;}
Note: this is a cheesy implementation
of the gcd function!
Remember to putprototype in .h fileas private, static!
Oh yeah! And be sureto #include <cstdlib>in Fraction.cpp file!
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{ 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 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 stringstring 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 declarationsclass 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 declarationsclass 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 declarationsclass 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 declarationsclass 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;}
reference return type so it can be Lvalue
Modify object
Return modified object
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)