// Things to Remember: // 1. Syntax of overloading operators: // -- for any operator @, x @ y can be thought of as // x.operator@( y ) for a member function called "operator@( . )", OR // operator@( x, y ) for a friend function "operator@( ., . )" // // 2. Semantics of overloading: // -- take parameter (the second operand) by REFERENCE (to avoid copying) // -- return the result by VALUE (which usually results in creation of an // unnamed temporary), because the result of an operation is a new // object of the same class // // 3. Assignment (operator=) and C.C.: // -- are always generated even if not defined in your code. If default, // they only do member-wise copy // -- operator= should check agains "assignment to self" ("this" helps out) // -- how are C.C. and operator= different? // // 4. "this" (defined inside any memeber function of a class X) is a pointer // to the current instance, i.e. the current variable of type X) // Class declarations normally go into a separate header file #ifndef MY_FRAC #define MY_FRAC class myFrac { private: int num; // numerator int denom; // denominator public: myFrac() { // needed for arrays etc. cout << "myFrac default constructor called\n"; } myFrac( int n, int d = 1) : num(n), denom(d) { // works for cout << "myFrac other constructor called\n"; // myFrac a(1,2); } // myFrac b( 3 ); // (recall: 3 == 3/1 ) myFrac( const myFrac& y ) : num(y.num), denom(y.denom) { cout << "myFrac copy constructor called\n"; // works for } // myFrac c = a; // myFrac c(a); // NOTE: myFrac a=c; and myFrac a(c); are the SAME, and both call C.C. ~myFrac() { cout << "myFrac destructor called\n"; } // overloaded assignment. This kind of code will be generated automatically myFrac& operator= ( const myFrac& y ){ // returns a reference to allow cout << "operator= for myFrac\n"; // a = b = c; if( this != &y ) { // check against x = x; num = y.num; denom = y.denom; } return *this; // returns a reference to the current myFrac } // Basic Operations myFrac operator+ ( const myFrac& y ){ return myFrac( num * y.denom + denom * y.num, denom * y.denom ); } myFrac operator* ( const myFrac& y ){ return myFrac( num * y.num, denom * y.denom ); } // implement -, / . Guard again division by 0 // we want to allow 3*myFrac(1,2) (== 3/2) and 2 + myFrac(2,5) (== 12/5 ) // friend functions are NOT members. Only their prototypes may appear here // Notice the overloading! friend myFrac operator+( int x, const myFrac& y ); // definition deferred friend myFrac operator*( int x, const myFrac& y ); // definition deferred friend myFrac operator+( const myFrac& y, int x ); // definition deferred friend myFrac operator*( const myFrac& y, int x ); // definition deferred //NOTE: operator+, operator* for myFracs themselves can also be defined as // friends rather than members. In fact, defining them as friends is // preferred, because it makes code more symmetric (see below) friend bool operator== ( const myFrac& x, const myFrac& y ); // friend bool operator== ( int x, const myFrac& y ); -- implement these // friend bool operator== ( const myFrac& x, int y ); // implement operator<, operator> etc. as friends // we want myFracs to be anble to print themselves: cout << a; // So we overload operator<< , which is just like any other operator. // Recall: cout << a is actually operator<< ( cout, a ) !!! // Q: What type is the object "cout"? // A: ostream (defined in iostream.h) friend ostream& operator<<( ostream& , const myFrac& ); }; // end class myFrac // Definitions of friends and others myFrac operator+( int x, const myFrac& y ){ // x/1 + a/b return myFrac( x*y.denom + y.num , y.denom ); } myFrac operator*( int x, const myFrac& y ){ return myFrac( x*y.num , y.denom ); } myFrac operator+( const myFrac& x, int y ){ return myFrac( x.num + y*x.denom , x.denom ); } myFrac operator*( const myFrac& x, int y ){ return myFrac( x.num * y , x.denom ); } // a/b == c/d if and only if a*d == b*c . Why not just a==c, b==d ? bool operator== ( const myFrac& x, const myFrac& y ){ return ( x.num * y.denom == y.num * x.denom ); } ostream& operator<<( ostream& os , const myFrac& x ){ os << x.num << "/" << x.denom ; return os; // must return ostream& to allow smth like // cout << a << " " << b << endl; } #endif // MY_FRAC