220 likes | 356 Views
Recitation Week 6. Object Oriented Programming COP3330 / CGS5409. Today’s Recitation. Comparison Operator Overloading Insertion (<<)/Extraction (>>) Operator Overloading Dynamic Memory Allocation. Overloading Comparison Operators. The comparison operators can also be overloaded
E N D
Recitation Week 6 Object Oriented Programming COP3330 / CGS5409
Today’s Recitation • Comparison Operator Overloading • Insertion (<<)/Extraction (>>) Operator Overloading • Dynamic Memory Allocation
Overloading Comparison Operators • The comparison operators can also be overloaded • stand-alone functions or • member functions • Consider the Equals function example: friend bool Equals(const Fraction& f1, const Fraction& f2); • We can easily write this as an operator overload: friend bool operator== (const Fraction& f1, constFraction& f2);
Overloading Comparison Operators • Here are corresponding sample calls: Fraction n1, n2; if (Equals(n1, n2)) cout<< "n1 and n2 are equal"; • Contrast with this: Fraction n1, n2; if (n1 == n2) cout<< "n1 and n2 are equal";
Overloading the Insertion << and extraction >> Operators • As with other operators, the << and >> operators are defined for the basic types. • If you build your own class, don't expect << to automatically work with your new types of objects! • If you want it to work, you have to teach the computer how to do such output. Consider the following: Fraction f; cout<< f; // how would the machine // know how to do this?
Overloading the Insertion << and extraction >> Operators • The insertion operator << is only pre-defined for built-in types. • The iostream.h library doesn't know about the Fraction type. • The << operator is a binary operator (2 parameters, left side and right side). • The first parameter is always an ostream object (we've mostly used cout, so far). Because of this, it cannot be defined as a member function (it would have to be a member of the ostream class, which we cannot change). • The << and >> operators should always be defined as outside functions (usually friend functions). • The second parameter is whatever new type it is being overloaded to print: friend ostream& operator << (ostream& s, Fraction f);
Overloading the Insertion << and extraction >> Operators friend ostream& operator << (ostream& s, Fraction f); • This declaration has all of the usual parts for defining a function. • The name is operator<< (the keyword operator and the operator symbol). • The return type is ostream&. • The parameters are (ostream& s, Fraction f). When defining overloads of << and >> , always pass the stream parameters by reference.
Overloading the Insertion << and extraction >> Operators • A better way to write this operator is: friend ostream& operator << (ostream& s, const Fraction& f); • Notice that the first one passes the Fraction by value (and makes a copy). • The second passes by reference (avoiding the overhead of a copy). • It is declared as a const because the Fraction does not need to change if we are just doing output.
Overloading the Insertion << and extraction >> Operators • Here is the corresponding prototype for extraction >> friend istream& operator >> (istream& s, Fraction& f); • Notice that the Fraction parameter for >> is also a reference parameter. • This is because we are getting input into the object, so we need to work on the original, not a copy.
Overloading the Insertion << and extraction >> Operators • Remember the Show() function of the Fraction class: void Fraction::Show() { cout<< numerator << '/' << denominator; } • Here is how the << operator might be defined for Fraction. Notice how similar it is to the Show() function. ostream& operator << (ostream& s, const Fraction& f) { s << f.numerator << '/' << f.denominator; return s; }
Overloading the Insertion << and extraction >> Operators • Note the differences between this and the Show() function. • In the new function, we must have a return statement, because we have a return type (as opposed to "void" in the original Show function). We return the ostream itself. • Note also that we must use "s" (not "cout") in the function body. This is the formal parameter, and a nickname for whatever was passed in (which could be cout, but also could be a different ostream). • Last, notice that this is not a member function of the Fraction class, but rather, a friend function. So, we can access the private data, but we must do it through the object
Overloading the Insertion << and extraction >> Operators • Once this is defined, we can use a Fraction object in a cout statement: Fraction f1; • So now, instead of: cout<< "Fraction f1 is "; f1.Show(); cout<< '\n'; • We can write: cout<< "Fraction f1 is " << f1 << '\n';
Memory Allocation Types • Remember that memory allocation comes in two varieties: • Static (compile time): Sizes and types of memory (including arrays) must be known at compile time, allocated space given variable names, etc. • Dynamic (run-time): Memory allocated at run time. Exact sizes (like the size of an array) can be variable. Dynamic memory doesn't have a name (names known by compiler), so pointers used to link to this memory
Dynamic Allocation & Cleanup • Allocate dynamic space with operator new, which returns address of the allocated item. Store in a pointer: int* ptr = new int; // one dynamic integer double * nums = new double[size]; // array of doubles, called "nums" • Clean up memory with operator delete. Apply to the pointer. Use delete [] form for arrays: delete ptr; // deallocates the integer above delete [] nums; // deallocatesdouble array above
Accessing Dynamic Items • Remember that to access a single dynamic item, dereference is needed: cout<< ptr; // prints pointer contents cout<< *ptr; // prints the target • For a dynamically created array, the pointer attaches to the starting position of the array, so can act as the array name: nums[5] = 10.6; cout<< nums[3];
Dynamic Allocation of Objects • Just like basic types, objects can be allocated dynamically. • When an object is created, the constructor runs. Default constructor is invoked unless parameters are added: Fraction * fp1, * fp2, * flist; fp1 = new Fraction; // uses default // constructor fp2 = new Fraction(3,5); // uses constructor with // two parameters flist= new Fraction[20]; // dynamic array of 20 // Fraction objects • Default constructor used on each
Dynamic Object De-allocation • Just like basic types, dynamically created objects must also be de-allocated. • De-allocation with delete works the same as for basic types: delete fp1; delete fp2; delete [] flist;
Dot-Operator vs. Arrow-Operator • Dot-operator requires an object name (or effective name) on the left side objectName.memberName // member can be data or function • The arrow operator works similarly as with structures. pointerToObject->memberName
Dot-Operator vs. Arrow-Operator • Remember that if you have a pointer to an object, the pointer name would have to be dereferenced first, to use the dot-operator: (*fp1).Show(); • Arrow operator is a nice shortcut, avoiding the use or parentheses to force order of operations: fp1->Show(); //equivalent to (*fp1).Show();
Dynamically Allocated Objects • When using dynamic allocation of objects, we use pointers, both to single object and to arrays of objects. Here's a good rule of thumb: • For pointers to single objects, arrow operator is easiest: fp1->Show(); fp2->GetNumerator(); fp2->Input();
Dynamically Allocated Arrays • For dynamically allocated arrays of objects, the pointer acts as the array name, but the object "names" can be reached with the bracket operator. Arrow operator usually not needed: flist[3].Show(); flist[5].GetNumerator(); • Note that this would be INCORRECT, flist[2] is an object, not a pointer! flist[2]->Show();