720 likes | 940 Views
Functions overloading Operator overloading. Overloading. Function overloading. Function Overloading. Overloaded functions have Same name Different sets of parameters Compiler selects proper function to execute based on number, types and order of arguments in the function call
E N D
Functions overloading • Operator overloading Overloading
Function Overloading • Overloaded functions have • Same name • Different sets of parameters • Compiler selects proper function to execute based on number, types and order of arguments in the function call • Commonly used to create several functions of the same name that perform similar tasks, but on different data types and numbers of parameters
overload.cpp (1/2) // function square for int values int square(int x) { cout << "square of integer " << x << " is "; return x * x; } // end function square with int argument // function square for double values double square(double y) { cout << "square of double " << y << " is "; return y * y; } // end function square with double argument Defining a square function for ints and doubles
overload.cpp (2/2) int main() { cout << square( 7 ); // calls int version cout << endl; cout << square( 7.5 ); // calls double version cout << endl; return 0; // indicates successful termination } // end main square of integer 7 is 49 square of double 7.5 is 56.25 • Sample Output • Output confirms that the proper function was called in each case
More examples ... #include <stdio.h> int max(int a, int b) { if (a > b) return a; return b; } char* max(char* a, char* b) { if (strcmp(a, b) > 0) return a; return b; } int main() { cout << “max(19, 69) = “ << max(19, 69) << endl; cout << “max(“abc”, “def”) = “ << max(“abc”, “def”) << endl; }
Function Overloading • How the compiler differentiates overloaded functions: • Overloaded functions are distinguished by their signatures • Compiler encodes each function identifier with the number and types of its parameters to enable type-safe linkage • Type-safe linkage ensures that • Proper overloaded function is called • Types of the arguments conform to types of the parameters • Creating overloaded functions with identical parameter lists and different return types is a compilation error • It is ambiguous on which function to call
Operator overloading: part I (good for users, hard for developpers )
Motivation Class Rational { public: Rational(int, int) const; … Rational add(const Rational&) { … return (Rational(…)); }; … private: int numerator; int denumerator; } Rational add(const Rational& c1, const Rational& c2) { … return Rational(…,…); } main() { Rational a,b,c; c=a.add(b); // member function c=add(a,b); // non-member (global) function … } Or Rational add(Rational) {…}; Ideally c = a + b;
Operator Overloading: a syntax sugar! ‘+’ is a function (operator function), is called ‘operator+’, and can be overloaded! 2+3 is operator+(2,3) ‘a+b’ is ‘operator+(a,b)’ or ‘a.operator+(b)’ a member function a (global) non-member function
‘addition operator’ of two Rational numbers obj1.add(obj2) obj1 + obj2 Class Rational { public: … Rational operator+(const Rational&) { … return Rational(…); }; … } main() { Rational a,b,c; c=a+b; … } The form of a+b is translated into a.operator+(b)
But, • ‘a+b+c’ is fine: a.operator+(b.operator+(c)) • ‘a+10’ is fine (if we modify it ): a.operator+(10) Rational operator+(const int i) { …} • How about ‘10+a’ ? 10.operator+(a)?
Use a non-member function A call of a+b is then converted to operator+(a,b) 10+a operator+(10,a) (a normal function call, not a ‘method’ of an object) If the first operand (or the left operand) is not an object of the same class, we cannot use a ‘member function’, so have to use a normal overloaded function. Class Rational { public: … int numerator(); int denumerator(); … Rational operator+(const Rational&); Rational operator+(const int); … } Rational operator+(const int num1, const Rational& r2) { … return Rational(numerator(…),denumerator(…)); } main() { Rational a,b,c; c=10+a; c=a+10; … }
‘Friend’ is coming to help … We can define (global) functions or classes to be friends of a class to allow them direct access to its private data members Class Rational { ... public: ... friend Rational operator+(const Rational&, const Rational&); }; Rational operator+(const Rational& op1, const Rational& op2) { int numerator = op1.numerator*op2.denumerator + op2.numerator*op1.denumerator; int denumerator = op1.denumerator*op2.denumerator; return(Rational(numerator,denumberator)); } Access to private data thanks to the ‘friendship’! No ‘friend’, we need to do: op1.numerator();
Assignment Operator ‘=‘ A a,b; a = b; • Assignment operator (=) can be used to assign an object to another object of the same type • Member-wise assignment: each data member of the right object is assigned to the same data member in the left object
class T { ... public: ... void operator=(const T&); ... }; void T::operator=(const T& right) { … } main() { T a,b; … a = b; // a.operator=(b) … } it can NOT be a const function, as ‘=‘ modifies the object OK for a = b, But not for the chaining of assignments: a = b = c = … a.operator=(b.operator=(c)) It should be an object here …
A function returning an object X f() { X x; return x; } ‘return x’ returns a temporay object ‘temp’ of class X by constructor (because x is a local object, and to be lost!)
A function returning a constant object ? const X f() { X x; return x; } It’s the same, but enforces that it cannot be a left value
Put ‘const’ everywhere, three different places Class Rational { public: … … const Rational operator+(const Rational& r2) const; const Rational operator+(const int num2) const; … } main() { Rational a,b,c; c=a+10; … }
Excercises: class X { public: X() {cout << "constructor\n";} X(const X& x){cout << "copy constructor\n"; } }; // return an object of X X f() { X x; return x; } // must be rvalue const X cf() { X x; return x; } constructor (for X x in main) constructor (for X y in main) constructor (for X x in f) copy constructor (Unix has that, but not Linux, for return x in f) constructor (for X x in cf) copy constructor (Unix has that, but not Linux, for return x in cf) int main() { X x,y; f() = x; // No compilation error but does nothing f = cf(); // cfx() = x is error }
A constructor is called when: • declare/define objects • pass by value • return by value X f(X x) { X a; return a; }
Object references We have pointers to class objects (low-level manipulations) ap->getx(); (*ap).getx(); delete ap; class A { public: getx(); private: int x; } Access and deletion We also have references to objects int i; int& j = i; A a; A& b=a;
A function returning an object returning a reference class A { public: A f(A a); private: int x; } A A::f(A a) { A b; … return b; } main() { A a,b,c; c = a.f(b); } class A { public: A& f(A a); private: int x; } A& A::f(A a) { A b; … return b; } A& A::f(A a) { … return a; } A& A::f(A& a) { A b; … return a; } No! b disappears No! a disappears OK! but the referenced object should exisit!
class A { public: A& f(A a); private: int x; } const A& A::f(const A& a) { … return a; } ‘return by value’ is safe. Avoid ‘return by reference’. If we want to avoid the copy, we ‘return by constant reference’! ‘references’ to local variables are not possible! The object have to exist after the function! ‘return by constant reference’ means that the object being returned cannot itself be modified later on!
class X { public: X() {cout << "constructor\n";} X(const X& x){cout << "copy constructor\n"; } }; // return an object of X X f() { X x; return x; } // must be rvalue const X cf() { X x; return x; } // must be rvalue const X& crf(X& x){ return x; } //can be rvalue or lvalue X& rf(X& x){ return x; } int main() { X x,y; f() = x; // No compilation error but does nothing f = cf(); // cfx() = x is error f = rf( y ); f = crf( y ); // crfx( y ) = x is error return 0; } constructor (for X x in main) constructor (for X y in main) constructor (for X x in f) copy constructor (Unix has that, but not Linux, for return x in f) constructor (for X x in cf) copy constructor (Unix has that, but not Linux, for return x in cf)
Object self-reference: the ‘this’ pointer Every class has a keyword, ‘this’, which is a pointer to the object itself. Thus, *this is the object itself! Class Object Function members A& F::f(){ // … return *this; } *this Data members this Can return the modified object. int i; int& j = i;
Returning a reference or a const reference? a = b = c always means a = (b = c) as ‘=‘ is right associative, it is a r-value. We can write (a=b)=c, depending on what a=b returns. int i=5, j=4; (i=j)=3; // lvalue, return the new i cout << i<< j ; // get 3 4 (i=3)=4; // lvalue, return the new i cout << i<< j ; // get 4 4 i = j = 3; // get 3 3
It can be both a l-value and r-value! We cannot write (a = b) = c if we return a const ref: const T& operator=(const T&); We can do (a=b)=c if we return a non-constant ref: T& operator=(const T&);
class T { ... public: ... T& operator=(const T&); ... }; T& T::operator=(const T& right) { … return *this; } main() { T a,b; … b = a; // b.operator=(a) … } it can NOT be a const function, as ‘=‘ modifies the object *this refers to the calling object Returning a reference allows the chaining of operators a = b = c = d; a = (b = (c = d)) a.operator=(b.operator=(c.operator=(d)))
class T { ... public: ... T& operator=(const T& right); ... }; T& T::operator=(const T& right) { if (this == &right) return *this; … return *this; } main() { T a,b; … b = a; // b.operator=(a) … } Summary on ‘=‘ overloading 1. Const reference for the argument (a r-value) 2. Return a reference to the left-hand side (a l-value), and *this is converted into a reference 3. Check for self-assignment
Other operators such as +=, -=, *=, … can be overloaded in a similar manner.
Input/output class T { ... }; ostream& operator<<(ostream& out, const T t) { … return out; } istream& operator>>(istream& in, T& t) { … return in; } main() { T a; … cin >> a; cout << a; … } Before, we wrote a ‘display’ or ‘print’ function.
‘Rational’ example void Rational::Display() const { cout << Numerator << '/' << Denominator; } Example t.Display(); ostream& operator<<(ostream& out, const Rational t) { out << Numerator << '/' << Denominator; return out; } Example cout << t; ofstream fout(“toto”); fout.open(…); fcout << t;
void Rational::Get() { char slash; cin >> Numerator >> slash >> Denominator; if(Denominator == 0){ cout << "Illegal denominator of zero, " << "using 1 instead" << endl; Denominator = 1; } } Example t.Get(); istream& operator>>(istream& in, T& t) { char slash; in >> Numerator >> slash >> Denominator; if(Denominator == 0){ cout << "Illegal denominator of zero, " << "using 1 instead" << endl; Denominator = 1; } return in; } Example cin >> t;
Which operators to overload? • Only those for which it makes sense … • … and for which there is a genuine need … • … and which the users will expect • Typically these are usually appropriate: • = • << • >> • If the class involves ‘arithmetic type’ (e.g. complex, rational, vector, matrix, …), arithmetic operations should be provided.
Essential operators class X { X(); X(const X&); ~X(); X& operator=(const X&); … } ostream& operator<<(ostream& out, const X x) { … }
How to ‘hide’ or ‘disable’ an operator? class X { private: void operator=(const X&); void operator&(); void operator,(const X&); … } Void f(X a, X b) { a=b; // error: operator= private &a; // error: operator& private a,b; // error: operator, private }
Use a private function to overload others class Rational { public: const Rational operator+(Rational r) { return add(…); }; const Rational operator+(int i) { return add(…); }; const Rational operator+(…) { return add(…); } private: const Rational add(a,b,c,d) { return Rational(a*d+b*c,b*d); } } const Rational operator+(int i, Rational) { return add(…); } This also gives one example of defin ring a ‘private’ member function.
Restrictions on Operator Overloading • Cannot change • Precedence of operator (order of evaluation) • Use parentheses to force order of operators • Associativity (left-to-right or right-to-left) • 2*3*4 (=6*4) vs. 2^3^2 (=2^9) • Number of operands • e.g., !, & or * is unary, i.e., can only act on one operand as in &i or *ptr • How operators act on built-in/primitive data types (i.e., cannot change integer addition) • Cannot create new operators • Operators must be overloaded explicitly • Overloading + and = does not overload +=
Member functions declaration: bool operator!() const; bool operator==(const T&) const; bool operator<(const T&) const; bool operator!=(const T& right) const; bool operator>( const T& right) const; bool operator<=(const T& right) const; bool operator>=(const T& right) const;
Operators as Class Members • Leftmost object must be of the same class as operator function • Use this keyword to explicitly get left operand argument • Operators (), [], -> or some other assignment operator must be overloaded as a class member function • Called when • Left operand of binary operator is of this class • Single operand of unary operator is of this class
Operators as Global Functions • Need parameters for both operands • Can have object of different class • Can be a friend to access private (or protected) data • Both << and >> must be global functions • Cannot be class members • Overloaded << operator • Left operand is of type ostream& • Such as cout object in cout << classObject • Similarly, overloaded >> has left operand of istream& • Such as cin object in cin >> classObject
Commutative Operators for global functions HugeInt operator+( long, HugeInt ); // function overloading HugeInt operator+( HugeInt, long ); HugeInt operator+( HugeInt, HugeInt ); • May want + to be commutative • So both “a + b” and “b + a” work • Suppose we have two different classes • If the overloaded operator is a member function, then its class is on left • HugeIntClass + long int • Can be member function for HugeIntClass • HugeIntClass + HugeIntClass • Can be member function as well • long int + HugeIntClass • For this to work, + needs to be a global overloaded function
Overloading Unary Operators • Can overload as member function with no arguments • Can overload as global function with one argument • Argument must be class object or reference to class object • If member function, needs no arguments • bool operator!() const; • If global function, needs one argument • bool operator!( const T& ), i.e., !fbecomes operator!(f)
Overloading Binary Operators int i=2,j=4; (j +=i) += 2; // return the new j cout << i << j; // output 2 8 • Member function: one argument • const T& operator+=(const T&); • s1 += s2; // a string • s1 += s2 += s3; // same as s1 += ( s2 += s3 ); • (s1 += s2) += s3; // compiler yells • Global function: two arguments • One of the arguments must be class object or reference • const T& operator+=(T&, const T&); // no const for the first argument • y += z becomes operator+=( y, z ) • Note that int type provides a variant of lvalue:
Subscription Operator ‘[]‘ A a,b; a[3]; For example, we can enable this operator for a list (whether it is array-based or linked): List l; L[10] Or we can re-number from 1 instead of 0 …