diff --git a/lectures/13_operators/README.md b/lectures/13_operators/README.md deleted file mode 100644 index 8f08c88..0000000 --- a/lectures/13_operators/README.md +++ /dev/null @@ -1,190 +0,0 @@ -# Lecture 13 --- Operators & Friends - -- Operators as non-member functions, as member functions, and as friend functions. - -## 13.1 Complex Numbers — A Brief Review - -- Complex numbers take the form z = a + bi, where i = √−1 and a and b are real. a is called the real part, b is called the imaginary part. -- If w = c + di, then - – w + z = (a + c) + (b + d)i, - – w − z = (a − c) + (b − d)i, and - – w × z = (ac − bd) + (ad + bc)i -- The magnitude of a complex number is √a2 + b2; - -## 13.2 Complex Class declaration ([complex.h](complex.h)) - -```cpp -class Complex { -public: - Complex(double x=0, double y=0) : real_(x), imag_(y) {} // default constructor - Complex(Complex const& old) : real_(old.real_), imag_(old.imag_) {} // copy constructor - Complex& operator= (Complex const& rhs); // Assignment operator - double Real() const { return real_; } - void SetReal(double x) { real_ = x; } - double Imaginary() const { return imag_; } - void SetImaginary(double y) { imag_ = y; } - double Magnitude() const { return sqrt(real_*real_ + imag_*imag_); } - Complex operator+ (Complex const& rhs) const; - Complex operator- () const; // unary operator- negates a complex number - friend istream& operator>> (istream& istr, Complex& c); -private: - double real_, imag_; -}; -Complex operator- (Complex const& left, Complex const& right); // non-member function -ostream& operator<< (ostream& ostr, Complex const& c); // non-member function -``` - -## 13.3 Implementation of Complex Class ([complex.cpp](complex.cpp)) - -```cpp -// Assignment operator -Complex& Complex::operator= (Complex const& rhs) { - real_ = rhs.real_; - imag_ = rhs.imag_; - return *this; -} -// Addition operator as a member function. -Complex Complex::operator+ (Complex const& rhs) const { - double re = real_ + rhs.real_; - double im = imag_ + rhs.imag_; - return Complex(re, im); -} -// Subtraction operator as a non-member function. -Complex operator- (Complex const& lhs, Complex const& rhs) { - return Complex(lhs.Real()-rhs.Real(), lhs.Imaginary()-rhs.Imaginary()); -} -// Unary negation operator. Note that there are no arguments. -Complex Complex::operator- () const { - return Complex(-real_, -imag_); -} -// Input stream operator as a friend function -istream& operator>> (istream & istr, Complex & c) { - istr >> c.real_ >> c.imag_; - return istr; -} -// Output stream operator as an ordinary non-member function -ostream& operator<< (ostream & ostr, Complex const& c) { - if (c.Imaginary() < 0) ostr << c.Real() << " - " << -c.Imaginary() << " i "; - else ostr << c.Real() << " + " << c.Imaginary() << " i "; - return ostr; -} -``` - -## 13.4 Operators as Non-Member Functions and as Member Functions - -- We have already written our own operators, especially **operator<**, to sort objects stored in STL containers. -- We can write them as non-member functions (e.g., **operator-**). When implemented as a non-member function, -the expression: **z - w** is translated by the compiler into the function call: **operator- (z, w)** -- We can also write them as member functions (e.g., **operator+**). When implemented as a member function, the -expression: **z + w is translated into: z.operator+ (w)** -This shows that **operator+ is a member function of z**, since z appears on the left-hand side of the operator. -Observe that the function has **only on**e argument! -- There are several important properties of the implementation of an operator as a member function: - – It is within the scope of class **Complex**, so private member variables can be accessed directly. - – The member variables of **z**, whose member function is actually called, are referenced by directly by name. - – The member variables of **w** are accessed through the parameter **rhs**. - – The member function is **const**, which means that z will not (and can not) be changed by the function. -- Also, since w will not be changed since the argument is also marked const. -- Both **operator+ and operator-** return **Complex** objects, so both must call **Complex constructors** to create these -objects. Calling constructors for **Complex** objects inside functions, especially member functions that work on -**Complex** objects, seems somewhat counter-intuitive at first, but it is common practice! - -## 13.5 Assignment Operators - -- The assignment operator: **z1 = z2**; becomes a function call: **z1.operator=(z2)**; -And cascaded assignments like: **z1 = z2 = z3**; are really: **z1 = (z2 = z3)**; -which becomes: **z1.operator= (z2.operator= (z3))**; -Studying these helps to explain how to write the assignment operator, which is usually a member function. -- The argument (the right side of the operator) is passed by constant reference. Its values are used to change -the contents of the left side of the operator, which is the object whose member function is called. A reference -to this object is returned, allowing a subsequent call to **operator= (z1’s operator=** in the example above). -The identifier **this** is reserved as a pointer inside class scope to the object whose member function is called. -Therefore, ***this** is a a reference to this object. -- The fact that **operator=** returns a reference allows us to write code of the form: **(z1 = z2).real();** - -## 13.6 Exercise - -Write an operator+= as a member function of the Complex class. To do so, you must combine what you learned -about operator= and operator+. In particular, the new operator must return a reference, *this. - - - - -## 13.7 Returning Objects vs. Returning References to Objects - -- In the operator+ and operator- functions we create new Complex objects and simply return the new object. The return types of these operators are both Complex. -Technically, we don’t return the new object (which is stored only locally and will disappear once the scope of -the function is exited). Instead we create a copy of the object and return the copy. This automatic copying -happens outside of the scope of the function, so it is safe to access outside of the function. Note: It’s important -that the copy constructor is correctly implemented! Good compilers can minimize the amount of redundant -copying without introducing semantic errors. -- When you change an existing object inside an operator and need to return that object, you must return a -reference to that object. This is why the return types of operator= and operator+= are both Complex&. -This avoids creation of a new object. -- A common error made by beginners (and some non-beginners!) is attempting to return a reference to a locally -created object! This results in someone having a pointer to stale memory. The pointer may behave correctly -for a short while... until the memory under the pointer is allocated and used by someone else. - -## 13.8 Friend Classes vs. Friend Functions - -- In the example below, the Foo class has designated the Bar to be a friend. This must be done in the public -area of the declaration of Foo. -```cpp -class Foo { -public: - friend class Bar; - ... -}; -``` -This allows member functions in class Bar to access all of the private member functions and variables of a Foo -object as though they were public (but not vice versa). Note that Foo is giving friendship (access to its private -contents) rather than Bar claiming it. What could go wrong if we allowed friendships to be claimed? -- Alternatively, within the definition of the class, we can designate specific functions to be “friend”s, which -grants these functions access similar to that of a member function. The most common example of this is -operators, and especially stream operators. - -## 13.9 Stream Operators as Friend Functions - -- The operators >> and << are defined for the Complex class. These are binary operators. -The compiler translates: cout << z3 into: operator<< (cout, z3) -- Consecutive calls to the << operator, such as: cout << "z3 = " << z3 << endl; -are translated into: ((cout << "z3 = ") << z3) << endl; -- Each application of the operator returns an ostream object so that the next application can occur. -- If we wanted to make one of these stream operators a regular member function, it would have to be a member -function of the ostream class because this is the first argument (left operand). We cannot make it a member -function of the Complex class. This is why stream operators are never member functions. -- Stream operators are either ordinary non-member functions (if the operators can do their work through the -public class interface) or friend functions (if they need non public access). - -You can compile and run these three examples, in which the output stream operators are overloaded as a non-member function, a friend function, and a member function. - -- [Example 1 - overloading as a non member function](overloading_non_member.cpp) -- [Example 2 - overloading as a friend function](overloading_friend.cpp) -- [Example 3 - overloading as a member function](overloading_member.cpp) - pay attention to the main function, does it surprise you? - -## 13.10 Summary of Operator Overloading in C++ - -- Unary operators that can be overloaded: + - \* & ~ ! ++ -- -> ->\* -- Binary operators that can be overloaded: + - \* / % ^ & | << >> += -= \*= /= %= ^= &= |= <<= >>= < <= > >= == != && || , [] () new new[] delete delete[] -- There are only a few operators that can not be overloaded: - - . (the . operator) - - .\* (what is this?) - - ?: (the ternary operator) - - :: (the scope resolution operator) -- We can’t create new operators and we can’t change the number of arguments (except for the function call -operator, which has a variable number of arguments). -- There are three different ways to overload an operator. When there is a choice, we recommend trying to write -operators in this order: - – Non-member function - – Member function - – Friend function -- The most important rule for clean class design involving operators is to NEVER change the intuitive -meaning of an operator. The whole point of operators is lost if you do. One (bad) example would be -defining the increment operator on a Complex number. - -## 13.11 Extra Practice - -- Implement the following operators for the Complex class (or explain why they cannot or should not be -implemented). Think about whether they should be non-member, member, or friend. - -operator\* operator== operator!= operator< diff --git a/lectures/13_operators/complex.cpp b/lectures/13_operators/complex.cpp deleted file mode 100644 index b3a73da..0000000 --- a/lectures/13_operators/complex.cpp +++ /dev/null @@ -1,39 +0,0 @@ -#include -#include "complex.h" - -// Assignment operator -Complex& Complex::operator= (Complex const& rhs) { - real_ = rhs.real_; - imag_ = rhs.imag_; - return *this; -} - -// Addition operator as a member function. -Complex Complex::operator+ (Complex const& rhs) const { - double re = real_ + rhs.real_; - double im = imag_ + rhs.imag_; - return Complex(re, im); -} - -// Subtraction operator as a non-member function. -Complex operator- (Complex const& lhs, Complex const& rhs) { - return Complex(lhs.Real()-rhs.Real(), lhs.Imaginary()-rhs.Imaginary()); -} - -// Unary negation operator. Note that there are no arguments. -Complex Complex::operator- () const { - return Complex(-real_, -imag_); -} - -// Input stream operator as a friend function -istream& operator>> (istream & istr, Complex & c) { - istr >> c.real_ >> c.imag_; - return istr; -} - -// Output stream operator as an ordinary non-member function -ostream& operator<< (ostream & ostr, Complex const& c) { - if (c.Imaginary() < 0) ostr << c.Real() << " - " << -c.Imaginary() << " i "; - else ostr << c.Real() << " + " << c.Imaginary() << " i "; - return ostr; -} diff --git a/lectures/13_operators/complex.h b/lectures/13_operators/complex.h deleted file mode 100644 index ba6e92d..0000000 --- a/lectures/13_operators/complex.h +++ /dev/null @@ -1,29 +0,0 @@ -#include -using std::istream; -using std::ostream; - -class Complex { -public: - Complex(double x=0, double y=0) : real_(x), imag_(y) {} // default constructor - Complex(Complex const& old) : real_(old.real_), imag_(old.imag_) {} // copy constructor - Complex& operator= (Complex const& rhs); // Assignment operator - double Real() const { return real_; } - void SetReal(double x) { real_ = x; } - double Imaginary() const { return imag_; } - void SetImaginary(double y) { imag_ = y; } - double Magnitude() const { return sqrt(real_*real_ + imag_*imag_); } - Complex operator+ (Complex const& rhs) const; - Complex operator- () const; // unary operator- negates a complex number - - // Input and output stream operators can not be member functions. They can be friends, - // or they can be non-member functions if their work can be accomplished through the - // public interface to the class. Note that the complex object passed to the istream - // (>>) operator MUST be passed as a non-const reference. - friend istream& operator>> (istream& istr, Complex& c); -private: - double real_, imag_; -}; - -Complex operator- (Complex const& left, Complex const& right); // non-member function -ostream& operator<< (ostream& ostr, Complex const& c); // non-member function - diff --git a/lectures/13_operators/overloading_friend.cpp b/lectures/13_operators/overloading_friend.cpp deleted file mode 100644 index 4a37e27..0000000 --- a/lectures/13_operators/overloading_friend.cpp +++ /dev/null @@ -1,32 +0,0 @@ -// Overloading the output stream operator as a friend function. - -#include - -class MyClass { -private: - int value; - -public: - MyClass(int val) : value(val) {} - friend std::ostream& operator<<(std::ostream& os, const MyClass& obj); - - // getter function to access private member - int getValue() const { - return value; - } -}; - -// overload the output stream operator as a non-member function -std::ostream& operator<<(std::ostream& os, const MyClass& obj) { - os << "Value: " << obj.value; - return os; -} - -int main() { - MyClass obj(42); - - // output the object using the overloaded operator - std::cout << obj << std::endl; - - return 0; -} diff --git a/lectures/13_operators/overloading_member.cpp b/lectures/13_operators/overloading_member.cpp deleted file mode 100644 index d98abef..0000000 --- a/lectures/13_operators/overloading_member.cpp +++ /dev/null @@ -1,32 +0,0 @@ -// Overloading the output stream operator as a member function. - -#include - -class MyClass { -private: - int value; - -public: - MyClass(int val) : value(val) {} - - // getter function to access private member - int getValue() const { - return value; - } - - // overload the output stream operator as a non-member function - std::ostream& operator<<(std::ostream& os) { - os << "Value: " << value; - return os; - } - -}; - -int main() { - MyClass obj(42); - - // output the object using the overloaded operator - obj << std::cout << std::endl; - - return 0; -} diff --git a/lectures/13_operators/overloading_non_member.cpp b/lectures/13_operators/overloading_non_member.cpp deleted file mode 100644 index 97b7485..0000000 --- a/lectures/13_operators/overloading_non_member.cpp +++ /dev/null @@ -1,31 +0,0 @@ -// Overloading the output stream operator as a non-member function. - -#include - -class MyClass { -private: - int value; - -public: - MyClass(int val) : value(val) {} - - // getter function to access private member - int getValue() const { - return value; - } -}; - -// overload the output stream operator as a non-member function -std::ostream& operator<<(std::ostream& os, const MyClass& obj) { - os << "Value: " << obj.getValue(); - return os; -} - -int main() { - MyClass obj(42); - - // output the object using the overloaded operator - std::cout << obj << std::endl; - - return 0; -}