250 likes | 359 Views
Overloading. Overloading allows a function or operator to have a different meaning depending on the type of objects it is used on. Examples: operator+ can be applied to both integers and strings. When applied to integers, it adds them. When applied to strings it concatenates them.
E N D
Overloading • Overloading allows a function or operator to have a different meaning depending on the type of objects it is used on. • Examples: • operator+ can be applied to both integers and strings. When applied to integers, it adds them. When applied to strings it concatenates them. • We may wish to have more than one constructor for a class, depending on what inputs are specified by the user
Overloading • A function with the same name as an existing one but a different number and/or types of parameters is an overloaded instance of the existing function. • If two functions have the same name and same number and types of parameters, they cannot have different return types. This would be an error.
Overloading • Example 1: The maximum() function can be used to find the largest of three or the largest of two integers. int maximum (int x, int y) { return (x>y)? x : y; } int maximum (int x, int y, int z) { if (x < y) return (y < z)? z : y; else return (x < z)? z : x; } int main () { cout << maximum(3,4) << endl; cout << maximum(3,8,-1) << endl; return 0; } screen output: 4 8
Overloading • Example 2: Consider a Fraction class. There are several places where we can use overloading: • An additional constructor that takes as arguments initial numerator and denominator values • A plus (+) operator to add two fractions. • The output stream operator (<<) to be able to use cout to print a Fraction.
Overloading • Example 2: Overloading constructors int main () { Fraction f1; Fraction f2(3,4); f1.print(); cout << endl; f2.print(); return 0; } class Fraction { private: int numerator; int denominator; public: Fraction() { numerator = denominator = 1; } Fraction(int num, int den) { numerator = num; denominator = den; } void print() { cout << numerator << "\n--\n" << denominator << endl; } ... }; 1--1 3--4 screen output:
Overloading • Example 2: Overloading constructors • We may also "merge" the two constructors into one by using default arguments. • Careful! The default arguments must be the last (rightmost ones) class Fraction { private: int numerator; int denominator; public: Fraction(int num=1, int den=1) { numerator = num; denominator = den; } void print() { cout << numerator << "\n--\n" << denominator << endl; } ... }; int main () { Fraction f1; Fraction f2(3,4); f1.print(); cout << endl; f2.print(); return 0; } 1--1 3--4 screen output:
Overloading • Example 3: Overloading the + operator int main () { Fraction f1(1,4); Fraction f2(3,4); Fraction sum; sum = f1+f2; sum.print(); return 0; } class Fraction { private: int numerator; int denominator; public: Fraction(int num=1, int den=1) { numerator = num; denominator = den; } Fraction operator+ (const Fraction& addend) { Fraction result; result.numerator = numerator * addend.denominator \ + denominator * addend.numerator; result.denominator = denominator * addend.denominator; return result; } void print() { cout << numerator << "\n--\n" << denominator << endl; } ... }; 16--16 screen output:
Overloading • Example 3: Dissecting the overloaded operator+ Fractionoperator+ (const Fraction& addend); sum = f1+f2; is equivalent to sum =f1.operator+(f2); Apply operator+ to the current object (f1). Pass the addend, f2, as an argument to the operator. Get back the sum and assign it to a variable.
Overloading • Example 4: Overloading the << operator • We want to be able to type cout << f1;instead of f1.print(); • But this is different from the + operator: • cout << f1; is equivalent to operator<<(cout, f1) • This means that operator<< cannot be a member of the Fraction class (as it is not applied on a Fraction object). • The overloaded operator<< will be defined globally, outside the class. • Furthermore, it should return a reference to an ostream object in order to allow chained output (e.g. cout << f1 < f2;) • Why is this safe?
Overloading • Example 4: Overloading the << operator class Fraction { ... }; ostream& operator<< ( ostream& out, Fraction frac) { out << frac.getNumerator() << "\n---\n" << frac.getDenominator() << endl; return out; } int main () { Fraction f1(3,4); cout << f1; return 0; } 3--4 screen output:
operator= • Consider the following chunk of code: • The assignment is translated to f1.operator=(f2) • A copy of f2 is placed and is assigned to f1 • The compiler will perform a member by member assignment • This can cause problems when dynamic memory is involved. • We must overload the assignment operator, so that it performs a deep assignment. int main () { Fraction f1(3,4); Fraction f2; f2 = f1; return 0; }
operator= • Example: class ClassInfo { private: int *student_ids; int class_size; public: ClassInfo (int size=0) { if (size == 0) student_ids = NULL; else student_ids = new int[size]; class_size = size; } void read_ids() { for (int i=0; i<class_size; i++) { cout << "Enter next id: "; cin >> student_ids[i]; } } }; int main () { ClassInfo eecs231(5); eecs231.read_ids(); ClassInfo eecs211(3); eecs211.read_ids(); eecs211 = eecs231; ...
operator= and pointers 1:ClassInfo eecs231(16); eecs231.read_ids(); eecs231: 10 9 13 2 7 8 5 2: ClassInfo eecs211; eecs211: 1 16 3 3 3: eecs211 = eecs231; eecs231: 10 9 13 2 7 8 5 eecs211: 1 16 3 5
operator= and pointers • Not only do both objects point to the same memory, even though they are difference objects, but we also have a memory leak because we lost the original eecs211.student_idspointer. • We must perform a deep assignment!
operator= and pointers think of obj1 = obj2; as being equivalent to obj1.operator=(obj2); returning a reference to the current object allows chain assignments: obj1 = obj2 = obj3; pass the right-hand-side as a reference but do not allow it to be modified. ClassInfo& operator= (const ClassInfo& rhs) { if ( this == &rhs ) return *this; if ( student_ids != NULL) delete [ ] student_ids; student_ids = new int[rhs.class_size]; class_size = rhs.class_size; return *this; } the this pointer provides access to an object's own address. this is a C++ keyword.
operator= and pointers ClassInfo& operator= (const ClassInfo& rhs) { if ( this == &rhs ) return *this; if ( student_ids != NULL) delete [ ] student_ids; student_ids = new int[rhs.class_size]; class_size = rhs.class_size; return *this; } if the right-hand side and the left-hand side are identical, (e.g. obj1 = obj1) just return a reference to the current object. (What will happen if we don't check this?)
operator= and pointers ClassInfo& operator= (const ClassInfo& rhs) { if ( this == &rhs ) return *this; if ( student_ids != NULL) delete [ ] student_ids; student_ids = new int[rhs.class_size]; class_size = rhs.class_size; return *this; } Since the current object will be assigned a new value, make certain that any memory already allocated for it is properly deallocated.
operator= and pointers ClassInfo& operator= (const ClassInfo& rhs) { if ( this == &rhs ) return *this; if ( student_ids != NULL) delete [ ] student_ids; student_ids = new int[rhs.class_size]; class_size = rhs.class_size; return *this; } Finally, perform the actual assignment and return a reference to the current object.
pointer data members • Whenever a class has at least one data member that is a pointer, you MUST write • a destructor • a copy contructor • an overloaded assignment operator THE BIG THREE! If you need one of them, then you also need the other two!
friend functions • In many cases, we would like a function to have access to private data members of the class, without the function being a member of the class. • Examples: • operator<< needs to print data member values but is not a class member itself. • a function that needs to operate on two or more objects of the same class • e.g. a function that takes as arguments two Points and computes the distance between them • a function that needs to operate on objects of different classes. • e.g. a function HaveCollided() that takes as arguments a Ship and a Torpedo object. • Possible Solution: • Use a get method (a.k.a. accessor) that returns the values of the class data members • Use friends!
friend functions • A class may allow a function to access its private data members by declaring it as a friend function. • Example: class Torpedo; class Ship { private: ShipT type; char *name; Coords position; public: ... friend bool HaveCollided(Torpedo& , Ship& ); }; ... bool HaveCollided(Torpedo& t, Ship& s) { ... }
friend classes • We may also declare a class A to be a friend of class B. This will give A access to the private members of B. • IMPORTANT: This does not mean that B has access to the private data members of A. In other words, if A is a friend of B, B is not automatically a friend of A. class City { private: Coords latitude; Coords longitude; public: ... friend class CityNetwork; }; class CityNetwork { private: City *citylist; Road *highways; public: ... }; Now, CityNetwork can access latitude and longitude
evil friends? • Friendship may only be granted, not taken. • Keep in mind that a friend function is dependent on the implementation of the class that declared it as a friend. • If the implementation changes, the function may need to be modified and will certainly need to be recompiled. • You must always have very good reasons for using friends. • See also:http://www.parashift.com/c++-faq-lite/friends.html#faq-14.2
operator<< as a friend classinfo.h class ClassInfo { private: int *student_ids; int class_size; public: ClassInfo (int size=0); .... friend ostream& operator<< (ostream&, const ClassInfo&) }; classinfo.cpp ClassInfo::ClassInfo (int size) { .... } ostream& operator<< (ostream&, const ClassInfo& obj) { ... }