22
Object Oriented Programming COP3330 / CGS5409

Recitation Week 6

Embed Size (px)

DESCRIPTION

Recitation Week 6. Object Oriented Programming COP3330 / CGS5409. Today’s Recitation. Comparison Operator Overloading Insertion () Operator Overloading Dynamic Memory Allocation. Overloading Comparison Operators. The comparison operators can also be overloaded - PowerPoint PPT Presentation

Citation preview

Page 1: Recitation Week  6

Object Oriented ProgrammingCOP3330 / CGS5409

Page 2: Recitation Week  6

Comparison Operator Overloading Insertion (<<)/Extraction (>>) Operator

Overloading Dynamic Memory Allocation

Page 3: Recitation Week  6

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, const Fraction& f2);

Page 4: Recitation Week  6

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";

Page 5: Recitation Week  6

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?

Page 6: Recitation Week  6

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);

Page 7: Recitation Week  6

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.

Page 8: Recitation Week  6

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.

Page 9: Recitation Week  6

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.

Page 10: Recitation Week  6

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;

}

Page 11: Recitation Week  6

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

Page 12: Recitation Week  6

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';

Page 13: Recitation Week  6

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

Page 14: Recitation Week  6

Allocate dynamic space with operator new, which returns address of the allocated item. Store in a pointer:

int * ptr = new int; // one dynamic integerdouble * 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 abovedelete [] nums; // deallocates double array above

Page 15: Recitation Week  6

Remember that to access a single dynamic item, dereference is needed:

cout << ptr; // prints pointer contentscout << *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];

Page 16: Recitation Week  6

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

// constructorfp2 = new Fraction(3,5); // uses constructor with

// two parameters

flist = new Fraction[20]; // dynamic array of 20 // Fraction objects

Default constructor used on each

Page 17: Recitation Week  6

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;

Page 18: Recitation Week  6

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

Page 19: Recitation Week  6

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();

Page 20: Recitation Week  6

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();

Page 21: Recitation Week  6

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();

Page 22: Recitation Week  6