Andy Wang Object Oriented Programming in C++ COP 3330 Operator Overloading Andy Wang Object Oriented Programming in C++ COP 3330
Fundamentals Many existing operators that work on built-in types (e.g., int, double) Operator overloading allows programmers to define new versions of these operators A C++ operator is just a function called with special notation Overloading a function involves writing a function C++ already has operator overloading + operator works on ints, floats, doubles, and chars Different versions of + exist for each type
Some Rules Regarding Operator Overloading Overloading an operator Cannot change a number of properties Precedence a + b*c Associativity (a + b) + c = a + (b + c) Number of operands Cannot create new operators Can only overload the existing ones Operator meaning on the built-in types cannot be changed
Some Rules Regarding Operator Overloading In other words You can change the meaning, not the grammar That is “sick” –Big Hero 6 Yes, you can define ‘+’ as subtraction
Friend vs. Member Functions Some operators can be written as member functions Some operators can be written as stand-alone friend functions A binary operator has two operands As a stand-alone function, it will take two parameters As a member function, the first operand is the calling object Taking the second operand as a parameter A unary operator has one operand As a stand-alone function, it will take a parameter As a member function, one calling object with no parameters How about “-”?
Format An operator is just a function It requires a name, a return type, and a parameter list Name of an operator is always a conjunction of the keyword operator and the operator symbol operator+ operator++ operator<< operator== Format of operator overload declaration return_type operator<operator_symbol> (parameter_list);
Motivation Example 1 Arithmetic operators int x = 3, y = 6, z; float a = 3.4, b = 2.1, c; z = x + y; c = a / b + 1 / 3; Can we use arithmetic operators on our Fraction class (a user-defined type)? Fraction n1, n2, n3; n3 = n1 + n2;
Example 1 The answer is NO Since Fraction is a user-defined type Operator overloading makes this possible
Example 2 The following screen output is legal int x = 5, y = 0; cout << x << y; How about this? Fraction f(3, 4); cout << “The fraction f = “ << f << ‘\n’; No, since the iostream library does not know about Fraction objects Again, operator overloading can help here
Overloading the Arithmetic Operators Can be overloaded either as stand-alone functions or as member functions Originally, we have the following friend Fraction Add(Fraction f1, Fraction f2); To add, perform the following Fraction n1, n2, n3; n3 = Add(n1, n2);
Note: const is only allowed for member functions The + Operator With operator overloading, we can have the following friend Fraction operator+(Fraction f1, Fraction f2); Or the following friend Fraction operator+(const Fraction &f1, const Fraction &f2); Thus, we can invoke the function this way n3 = operator+(n1, n2); Or, we can use the infix notation n3 = n1 + n2; // cascading also works (n1 + n2 + n3 + n4…) Note: const is only allowed for member functions
Definition of the + Operator Fraction operator+(Fraction f1, Fraction f2) { Fraction r; r.numerator = (f1.numerator*f2.denominator) + (f2.numerator*f1.denominator); r.denominator = f1.denominator*f2.denominator; return r; }
Code Example http://www.cs.fsu.edu/~myers/cop3330/examples/oo/frac1 /
frac.h class Fraction { friend Fraction operator+(const Fraction &f1, const Fraction &f2); public: … private: };
frac.cpp … Fraction operator+(const Fraction &f1, const Fraction &f2) { Fraction r; r.numerator = (f1.numerator*f2.denominator) + (f2.numerator*f1.denominator); r.denominator = f1.denominator*f2.denominator; return r; }
main.cpp #include <iostream> #include “frac.h” using namespace std; Int main() { Fraction f1, f2, f3(3,4), f4(6); … cout << “\n Now enter first fraction: “; f1.Input(); cout << “\nYou entered “; f1.Show(); cout << “\n Now enter first fraction: “; f2.Input(); cout << “\nYou entered “; f2.Show();
main.cpp cout << “\nThe sum of the first and second fraction is “; Fraction result; result = f1 + f2; result.Show(); cout << ‘\n’; cout << “Attempting arithmetic calls\n”; f2 = f1 + 5; cout << “Second arithmetic call\n”; f4 = 2 + f3; cout << “\n The fraction f1 is “; f1.Show(); … }
Overloading an Operator as a Member Function Member function version of Add Fraction Add(const Fraction &f) const; To call this function Fraction n1, n2, n3; n3 = n1.Add(n2); To overload +, we can change the name Add to operator+ Fraction operator+(const Fraction &f) const; To call, either way will work n3 = n1.operator+(n2); // hardly anyone will do this n3 = n1 + n2; // n1 is the calling object, n2 is the // parameter
Definition of operator+ Fraction Fraction::operator+(const Fraction &f) const { Fraction r; r.numerator = (numerator*f.denominator) + (f.numerator*denominator); r.denominator = (denominator*f.denominator); return r; }
Example Code http://www.cs.fsu.edu/~myers/cop3330/examples/oo/frac2 /
frac.h class Fraction { public: Fraction operator+(const Fraction &f) const; … private: };
frac.cpp … Fraction Fraction::operator+(const Fraction &f) const { Fraction r; r.numerator = (numerator*f.denominator) + (f.numerator*denominator); r.denominator = denominator*f.denominator; return r; }
main.cpp #include <iostream> #include “frac.h” using namespace std; Int main() { Fraction f1, f2, f3(3,4), f4(6); … cout << “\n Now enter first fraction: “; f1.Input(); cout << “\nYou entered “; f1.Show(); cout << “\n Now enter first fraction: “; f2.Input(); cout << “\nYou entered “; f2.Show();
main.cpp cout << “\nThe sum of the first and second fraction is “; Fraction result; result = f1 + f2; result.Show(); cout << ‘\n’; cout << “Attempting arithmetic calls\n”; f2 = f1 + 5; cout << “Second arithmetic call\n”; f4 = 2 + f3; // won’t work with member function cout << “\n The fraction f1 is “; f1.Show(); … }
Other Arithmetic Operators Multiplication overload friend Fraction operator*(Fraction f1, Fraction f2); Operator to add a Fraction and an integer friend Fraction operator+(Fraction f, int n); // not needed Operator to add an integer to a Fraction friend Fraction operator+(int n, Fraction f); // not needed The last two operators are not needed, since we have conversion constructors
Friend vs. Member Operator Overloading Will both cases work for friend version and the member function version? Fraction n1, n2, n3; n3 = n1 + 5; // case 1 n3 = 10 + n2; // case 2 What is the type of the calling object? Does it have the necessary conversion constructors?
Overloading Comparison Operators Can be overloaded either as stand-alone or member functions Here is the original Equals function friend bool Equals(const Fraction &f1, const Fraction &f2); We can write this as an operator overload friend bool operator==(const Fraction &f1, const Fraction &f2); Here are the corresponding calls Fraction n1, n2; if (Equals(n1, n2)) cout << “n1 and n2 are equal”; If (n1 == n2) cout << “n1 and n2 are equal”; Hmm… Can apple == orange? Well, if we see them as fruits…
Can Overload All Six operator== operator!= operator< operator>
Overloading the Insertion << and Extraction >> Operators << and >> are defined for basic types Thus, the following code should not work Fraction f; cout << f; You need to teach the computer to work user-defined types
Overloading the Insertion << and Extraction >> Operators << and >> are binary operators The first operator is always an io stream object (e.g., cout) Thus, << cannot be defined as a member function Should be defined as outside (usually friend) functions The second parameter is the user-defined type Here is the overloading function friend ostream& operator<<(ostream &s, Fraction f); Or a better version friend ostream& operator<<(ostream &s, const Fraction &f);
Overloading the Insertion << and Extraction >> Operators Here is the corresponding declaration for >> friend istream& operator>>(istream &s, Fraction &f); Notice that f is passed by reference Need to modify the original No const
Defining the Insertion << and Extraction >> Operators Recall the Show() member function void Fraction::Show() { cout << numerator << ‘/’ << denominator; } Here is the << operator friend function for Fraction ostream& operator<<(ostream &s, const Fraction &f) { s << f.numerator << ‘/’ << f.denominator; return s; Need a return type Use s, not cout
Using the Insertion << and Extraction >> Operators Member function Overloaded operator Fraction f1; cout << “f1 is “; f1.Show(); cout << ‘\n’; Fraction f1; cout << “f1 is “ << f1 << ‘\n’; // same as (((cout << “f1 is “) << f1) << ‘\n’);
Example with << Overload http://www.cs.fsu.edu/~myers/cop3330/examples/oo/frac3 / How about >> overload for Fraction? Try it!
frac.h class Fraction { … friend ostream &operator<<(ostream &s, const Fraction &f); public: private: };
frac.cpp … ostream& operator<<(ostream &s, const Fraction &f) { s << f.numerator << ‘/’ << f.denominator; return s; }
main.cpp #include <iostream> #include “frac.h” using namespace std; int main () { Fraction f1, f2, f3(3,4), f4(6); cout << “\n The fraction f1 is “ << f1 << ‘\n’; cout << “\n The fraction f2 is “ << f2; cout << “\n The fraction f3 is “ << f3; cout << “\n The fraction f4 is “ << f4;
main.cpp cout << “\n Now enter first fraction: “; f1.Input(); cout << “\nYou entered “ << f1; cout << “\n Now enter second fraction: “; f2.Input(); cout << “\nYou entered “ << f2; Fraction result; result = f1 + f2; cout << “\nThe sum of the first and second fraction is “ << result << ‘\n’;
main.cpp cout << “\n The value of fraction 1 is “ << f1.Evaluate() << ‘\n’; cout << “\n The value of fraction 2 is “ << f2.Evaluate() << ‘\n’; cout << “Goodbyte!\n”; return 0; }
Another Example http://www.cs.fsu.edu/~myers/cop3330/examples/complex / Complex numbers i2 = -1 Arithmetic operators: +, -, *, / Insertion and extraction operators: <<, >> Increment and decrement: ++, --
Prefix vs. Postfix Notation int j = 0; while (j < 10) { cout << ++j << endl; } // same as j = j + 1; cout << j << endl; Int j = 0; while (j < 10) { cout << j++ << endl; } // same as cout << j << endl; j = j + 1;
Review of Complex Numbers (a + bi) + (c + di) = (a + c) + (b + d)i (a + bi) – (c + di) = (a – c) + (b – d)i (a + bi)(c + di) = ac + adi + bci – bd = (ac – bd) + (ad + bc)i (a + bi)/(c + di) = [(a + bi)/(c + di)][(c – di)/c – di)] = (ac – adi + bci + bd)/(c2 + d2) = (ac + bd)/(c2 + d2) + (bc – ad)i/(c2 + d2) conjugate
complex.h #include <iostream> using namespace std; class Complex { friend Complex operator+(const Complex &, const Complex &); friend Complex operator-(const Complex &, friend Complex operator*(const Complex &, friend Complex operator/(const Complex &,
Notice the return types complex.h Notice the return types friend ostream &operator<<(ostream &, const Complex &); friend istream &operator>>(istream &, Complex &); public: Complex(double r = 0.0, double im = 0.0) // default constructor (sets to 0 + 0i) // conversion constructor (double to complex // constructor with 2 parameters (r + im*i) ~Complex(); double getReal() const; double getImaginary() const; void set(double r, double im = 0.0);
Just a note to the compiler to indicate that it is a postfix operator complex.h Complex& operator++(); // prefix increment Complex operator++(int); // postfix increment Complex& operator--(); // prefix decrement Complex operator--(int); // postfix decrement private: double real, imag; }; Just a note to the compiler to indicate that it is a postfix operator
complex.cpp #include “complex.h” // iostream is already in complex.h Complex operator+(const Complex &c1, const Complex & c2) { return Complex(c1.real + c2.real, c1.imag + c2.imag); } Complex operator-(const Complex &c1, const Complex &c2) { return Complex(c1.real – c2.real, c1.imag – c2.image);
complex.cpp Complex operator*(const Complex &c1, const Complex &c2) { double realPart = c1.real*c2.real – c1.imag*c2.imag; double imagPart = c1.imag*c2.real + c1.real*c2.imag; return Complex(realPart, imagPart); } Complex operator/(const Complex &c1, const Complex &c2) { double realPart = (c1.real*c2.real + c1.imag*c2.imag) / (c2.real*c2.real + c2.imag*c2.imag); double imagPart = (c1.imag*c2.real – c1.real*c2.imag)
complex.cpp ostream &operator<<(ostream& os, const Complex &c) { if (c.imag = 0) return (os << c.real); if (c.real = 0) return (os << c.imag << ‘i’); os << c.real; if (c.imag > 0) os << “ + “ << c.imag << ‘i’; else os << “ – “ << -c.imag << ‘i’; return os; } // (cout << a << b << c) is the same as // (((cout << a) << b) << c) or // (((cout.operator<<(a)).operator<<(b)).operator<<(c)) // cout.operator<<(a) must return an object
complex.cpp // input format: <real> + <imag>i istream &operator>>(istream &is, Complex &c) { char symbol, iMarker; is >> c.real >> symbol >> c.imag >> iMarker; if (symbol == ‘ – ‘) c.imag = -c.imag; return is; } Complex::Complex(double r, double im) { real = r; imag = im; } Complex::~Complex(); double Complex::getReal() const { return real; } double Complex::getImaginary() const { return imag; } void Complex::set(double r, double im) { real = r; imag = im; }
complex.cpp // prefix increment Complex &Complex::operator++() { real++; return *this; } // postfix increment Complex Complex::operator++(int) { Complex temp = *this; real++; return temp; // return OLD value } // prefix decrement Complex &Complex::operator--() { real--; return *this; } // postfix decrement Complex Complex::operator--(int) { Complex temp = *this; real--;
samplemain.cpp #include “complex.h” using namespace std; void PrintComplex(const char* label, Complex c) { cout << label << “: “ << c << ‘\n’; } int main() { Complex c1, c2(7.5), c3(3.6, 2.1), c4(5, -8), c5(0, 8.4), c6(0, -9.3); PrintComplex(“c1”, c1); … PrintComplex(“c6, c6);
samplemain.cpp cout << “Enter new value for c1: “; cin >> c1; cout << “Enter new value for c2: “; cin >> c2; cout << “\nYou entered: \n”; PrintComplex(“c1”, c1); PrintComplex(“c2”, c2); }