Religious & Philosophical

Recall that you overload a function by specifying a different signature for the parameters:

Description
Operator Overloading Recall that you overload a function by specifying a different signature for the parameters: int abs(int); double abs(double); When you invoke abs, the compiler determines at compile-time
Published
of 7
All materials on our website are shared by users. If you have any questions about copyright issues, please report us to resolve them. We are always happy to assist you.
Related Documents
Share
Transcript
Operator Overloading Recall that you overload a function by specifying a different signature for the parameters: int abs(int); double abs(double); When you invoke abs, the compiler determines at compile-time the best match for the parameters, and will call the appropriate function. int x = abs(2); double y = abs(2.0); // calls abs(int) // calls abs(double); You can also overload operators. If you're familiar with the string class, then you have already seen an example of operator overlaoding. For example, string a, b, c; a = abc ; b = def ; c = a + b; In this case the strings are concatonated so that variable c contains abcdef after execution. For this statement the compiler generates two references: c = a + b; c = a.operator+(b) c = operator+(a, b) // original statement // 1st attempt // 2nd attempt If an operator+ member function can be found in the string class, then it will call this function on behalf of a, passing b as an argument. If no such function is found, then the compiler attempts to find a prototype describing a standalone function called operator+ that accepts two strings as arguments. Complex Class Let's examine a class that handles complex numbers. Recall that complex numbers consist of two parts: a real part and an imaginary part. Our class will consist of two private data members: re and im, for the real and imaginary parts respectively. class Complex { public: Complex::Complex(double real = 0.0, double imag = 0.0) { re = real; im = imag; Complex operator+(const Complex &x) const { Complex tmp(re, im); tmp.re += x.re; tmp.im += x.im; return tmp; private: double re; // real part double im; // imaginary part ; We have a constructor for creating complex numbers, and a + operator for adding two operands. At compile-time, the compiler will rewrite statements to invoke the + operator as follows: Complex a, b, c; c = a + b; c = a + 2.0; // rewritten: c = a.operator+(b) // rewritten: c = a.operator+(complex(2.0)) Specifying default arguments for the constructor enabled the compiler to generate a Complex type from a floating point constant so that it could call the operator+ function that expects a Complex type. Consider the following: c = 2 + a; // rewritten: c = 2.operator+(a); There's one problem with this statement. There is no Complex class called 2 ! To solve this problem we could implement a standalone function as follows: Complex operator+(const Complex& x, const Complex& y) { Complex tmp(x.re, x.im); tmp.re += y.re; tmp.im += y.im; return tmp; We'll assume the function has been given friend status in the Complex class so we can access private data members such as re and im. Using this technique the above statement is translated as follows: c = 2 + a; // rewritten: c = operator+(complex(2), a) The compiler will first try to match an operator member function. If this is not possible, then it will try to match a standalone function. Only one argument is required for the member function, but two arguments are required for the standalone function. This is because the member function assumes the current class for the left-hand side. For binary operators that don't modify the class, use standalone functions for a flexible interface. For binary operators that do modify the class, such as += , the left-hand side must be the class in question, so implement these operators as class member functions. It's best to keep functions within the class unless there's a good reason to do otherwise. It's interesting to trace calls to constructors and destructors for this case. Variable tmp is instantiated on entry to the function. Since it's a local variable it must be destroyed on exit from the function. Another instantiation of the Complex class is created on the stack, the contents of tmp are copied to the stack, tmp is destroyed, and control is returned to the caller. Immediately after return, the copy on the stack is used and destroyed. To preclude the extra constructor/destructor calls, many compiler implement Named Value Return Optimization (NVRO). In this case tmp could be constructed on the stack in the correct place for a return value, and the extra constructor/destructor call can be avoided. Increment/Decrement Operators The increment and decrement operators require special handling. Since these operators modify the class, they should be class member functions. Here is the syntax for pre-increment and postincrement operators in the Complex class. class Complex { public: Complex::Complex(double real = 0.0, double imag = 0.0) { re = real; im = imag; Complex operator++() { re = re + 1; // pre-increment, ++x Complex operator++(int) { // post-increment, x++ Complex tmp(re, im); re = re + 1; return tmp; private: double re; // real part double im; // imaginary part ; Specifying (int) indicates the operation is post-increment. In this case we must return the original object since it gets used before incrementing. From this example it's easy to see that preincrementing, at least for class operators, is more efficient. I/O Operators To display the contents of a complex number you could write: Complex t; cout t; However, the compiler has no rule for displaying values from the Complex class. The standard library does contain several operators for builtin types. For example, here is the output operator for integers: std::ostream& operator (std::ostream& s, int x); s x; Again, this is a fictitious example as our solution would indefinitely recurse since we're trying to output an integer within the function. However, it does illustrate several points. When you have a statement such as int i; cout i; // rewritten: operator (cout, i) the operator has two parameters: the left-hand side (cout) and the right-hand side(i). Note that the operator is left associative. If we chain outputs as follows: cout i1 i2 i3; the left-most operator that outputs i1 will execute first. In order to process i2, we must return the output stream. This is why we return the stream in the above example. Note that the stream is passed in by reference, and not const reference, as we change the state of the stream when we output a value. Similarly, a reference to the stream is returned for the next operation in the chain. Here are examples of input and output operators for the Complex class: std::ostream& operator (std::ostream& s, const Complex& x) { s ( x.re , x.im ) ; std::istream& operator (std::istream& s, Complex& x) { s x.re x.im; Summary All operators, with the exception of the following, may be overloaded: :: scope resolution operator :? ternary operator. dot operator * dereferencing operator Overloaded operators have the same precedence and associativity as the original operator. Complex Class, Final Version /////////////// // complex.h // /////////////// #include iostream #include cmath class Complex { public: // i/o operators friend std::ostream& operator (std::ostream& s, const Complex& x); friend std::istream& operator (std::istream& i, Complex& x); // binary operators that do not modify class friend Complex operator+(const Complex& a, const Complex& b); friend Complex operator-(const Complex& a, const Complex& b); // standalone function friend double abs(const Complex &x); // constructor // define complex number (real, imag) // default arguments allow the following forms: // Complex a; (0,0) // Complex a(1); (1,0) // Complex a(1,2); (1,2) Complex(double real = 0.0, double imag = 0.0); // binary operators that modify the class Complex& operator+=(const Complex& x); Complex& operator-=(const Complex& x); // unary operators Complex operator-() const; Complex operator+() const; private: double re; double im; // real part // imaginary part ; // standalone functions inline Complex operator+(const Complex& x, const Complex& y) { Complex t(x); return t += y; inline Complex operator-(const Complex& x, const Complex& y) { Complex t(x); return t -= y; inline double abs(const Complex& x) { return sqrt(x.re*x.re + x.im*x.im); ///////////////// // complex.cpp // ///////////////// #include cmath #include Complex.h Complex::Complex(double real, double imag) { re = real; im = imag; Complex& Complex::operator+=(const Complex &x) { double a, b, c, d; a = re; b = im; c = x.re; d = x.im; re = a + c; im = b + d; Complex& Complex::operator-=(const Complex &x) { double a, b, c, d; a = re; b = im; c = x.re; d = x.im; re = a - c; im = b - d; Complex Complex::operator-() const { Complex t; t.re = -re; t.im = -im; return t; Complex Complex::operator+() const { // output operator std::ostream& operator (std::ostream& s, const Complex& x) { s ( x.re , x.im ) ; // input operator std::istream& operator (std::istream& s, Complex& x) { s x.re x.im; ////////////// // main.cpp // ////////////// #include iostream #include Complex.h using std::cout; using std::endl; using std::cin; int main() { Complex a; // (0,0) Complex b(5); // (5,0) Complex c(3,4); // (3,4) // use constructor to create complex number on-the-fly a = Complex(10,3); // member functions a += b; // a.operator+=(b) a -= b; // a.operator-=(b) // binary + operator implemented as standalone function a = c + 2; // operator+(complex,int) == operator+(complex,complex) a = 2 + c; // operator+(int,complex) == operator+(complex,complex) // putting it all together... c += abs(a - (5 - b) + Complex(2,2)); // input/output cin a; cout value is a endl; return 0;
Search
Similar documents
View more...
Related Search
We Need Your Support
Thank you for visiting our website and your interest in our free products and services. We are nonprofit website to share and download documents. To the running of this website, we need your help to support us.

Thanks to everyone for your continued support.

No, Thanks