#include "Complex.h" #include // for sqrt() using namespace std; // Constructor // // This also functions as the zero-argument (default) ctor due to the // default arguments specified in Complex.h. Complex::Complex(double real, double imag) { re = real; // shame on me im = imag; // should use member init invalidate_absolute_value(); count++; } // Copy constructor // // As a ctor, it has no return type. // The argument must be a const reference. // • const, because we have to be able to initialize from a read-only object // • reference, because what’s the alternative? Calling by value? // That would require COPYING the value. But THIS is the copy ctor! // // Always use member initialization, unless you can’t. Complex::Complex(const Complex &rhs) : re(rhs.re), im(rhs.im), absolute_value(rhs.absolute_value) { count++; } // Destructor // // Since there is now one fewer Complex object, decrement the object count. // Don’t bother clearing the member variables--they’re doomed to die // in just a moment. It would be like painting a condemned building. Complex::~Complex() { count--; } // Assignment operator // // All assignment operators return a reference to the assigned object, // so that we can chain assignments: a=b=c=d; which is equivalent to // a=(b=(c=d)); each assignment returns a ref to its left-hand-side // so that the next assignment can use that as a right-hand-side. // The argument must be a const reference. // • const, because we have to be able to copy a read-only object // • reference, because passing by value is slow. Complex &Complex::operator=(const Complex &rhs) { re = rhs.re; im = rhs.im; absolute_value = rhs.absolute_value; return *this; } // A simple accessor (alias a “getter”). // // It’s declared const so that we can call it on a const object. double Complex::real() const { return re; } double Complex::imag() const { return im; } // The complex conjugate of a+bi is a-bi. // This is a const method. It does NOT change the current object. // It creates a new object and returns it by value. Don’t return by // reference, because the object retval vanishes when this function // finishes. Complex Complex::conj() const { Complex retval(re, -im); return retval; } // Return the absolute value of a complex number. // The absolute value of a+bi is defined as the square root of a²+b². double Complex::abs() const { // For educational purposes, assume that computing a square root is // very expensive, so remember the value once we compute it. // If the absolute value needs computing, compute it now. // Hey, this method is const--how can we change absolute_value? if (absolute_value < 0) // First time? absolute_value = sqrt(re*re + im*im); // Yes; do the hard work. return absolute_value; } // This private method is used to invalidate our cached absolute value // when the values in this object change. // // Sure, it seems silly to have a whole method to just assign -1 to a // variable. However, which is easier to understand: calling a method // named “invalidate_absolute_value”, or assigning -1 to a data member? // // If you’re concerned about execution speed, then measure it. // Reasoning and guesswork are fine, but data is what counts. // A good compiler will inline the call to invalidate_absolute_value(), // which means that calling it has NO runtime overhead. Win! // // Of course, we could help the inlining by putting this method // into the .h file. void Complex::invalidate_absolute_value() { absolute_value = -1; } // This is a static method. That means that it doesn’t access any // non-static data members, and so may be called without an object. // It doesn’t have be declared const, because it can’t access // non-static data members. int Complex::get_count() { return count; } // Unlike operator+, this += operator indeed changes the current object. // Therefore, it’s not const. // // Like the plain assignment operator: // All assignment operators return a reference to the assigned object, // so that we can chain assignments. Granted, one rarely chains +=, // but it’s best to treat all assignment-like operators similarly. // // The argument must be a const reference. // • const, because we have to be able to copy a read-only object // • reference, because passing by value is time-consuming Complex &Complex::operator+=(const Complex &rhs) { re += rhs.re; im += rhs.im; invalidate_absolute_value(); return *this; } Complex &Complex::operator-=(const Complex &rhs) { re -= rhs.re; im -= rhs.im; invalidate_absolute_value(); return *this; } // (x + yi)*(u+vi) = (xu-yv) + (xv+yu)i Complex &Complex::operator*=(const Complex &rhs) { // Self-assignment would fail miserably, because the real // part would get changed, and then the changed number would // get used in the computation of the imaginary part. // // Therefore, put the new values in separate variables, // and copy them both to the object after the computations. const auto r = re*rhs.re - im*rhs.im; const auto i = re*rhs.im + im*rhs.re; re = r; im = i; invalidate_absolute_value(); return *this; } // a / b = a * (1/b) // 1/b = conj(b) / abs(b)² Complex &Complex::operator/=(const Complex &rhs) { auto factor = rhs.re*rhs.re + rhs.im*rhs.im; // abs(b)² const Complex inv(rhs.re/factor, -rhs.im/factor); // 1/b return *this *= inv; } // This is the pre-increment operator, because it has no argument. // It invokes += to do the real work. Complex &Complex::operator++() { *this += 1.0; // Let += do the work return *this; } // This is the post-increment operator, because it has a dummy int argument. // Crazy, huh? // // It must return the value as it was before incrementing. // Therefore, save the value, increment the object using pre-increment, // and then return the saved value. // Since it uses pre-increment to do the actual work of incrementing, // then we have no duplication of code. Happy happy joy joy! Complex Complex::operator++(int) { // Post-increment const auto save = *this; // Save old value ++*this; // Invoke pre-increment return save; // Return old value } Complex &Complex::operator--() { // Pre-decrement *this -= 1.0; // Let -= do the work return *this; } Complex Complex::operator--(int) { // Post-decrement const auto save = *this; // Save old value --*this; // Invoke pre-decrement return save; // Return old value } //////// Non-methods (free functions) start here ////////////////// // Implement the addition operator. This is a free function, // not a member function, so that the compiler can convert numbers // on the left side, e.g., Complex c(1.0, 2.0); Complex d = 3.4 + c; // // Arguments are const because this operator does NOT change the current // object. It creates a new one. // // By convention, the arguments are called lhs/rhs for Left/Right-Hand Side. // The arguments should be a const reference, for the same reasons // given in assignment operator. // // It returns a new object, by value. It can’t return by reference, // because any local object will go away when the function finishes. // // Note how operator+ cleverly allows operator+= to do the real work. // Sure, + is trivial, but would you want to do complex division // in two places?? Complex operator+(const Complex &lhs, const Complex &rhs) { Complex retval = lhs; return retval += rhs; } Complex operator-(const Complex &lhs, const Complex &rhs) { Complex retval = lhs; return retval -= rhs; } Complex operator*(const Complex &lhs, const Complex &rhs) { Complex retval = lhs; return retval *= rhs; } Complex operator/(const Complex &lhs, const Complex &rhs) { Complex retval = lhs; return retval /= rhs; } bool operator==(const Complex &lhs, const Complex &rhs) { return lhs.real()==rhs.real() && lhs.imag()==rhs.imag(); } bool operator!=(const Complex &lhs, const Complex &rhs) { // Let == do the real work. return !(lhs == rhs); } // This is the insertion (output) operator, a non-member function. // It must be a non-member function. If it were a member function, // it would have to be a member of the ostream class, because that’s // the left-hand argument. You can’t modify the ostream class, because // you didn’t write it, so that’s not possible. // // The first argument is a non-const ostream reference: // • non-const because we’re changing the stream by writing to it // • a reference because you can’t copy an I/O stream. // // The second argument is a const reference. // • const, because we have to be able to insert a read-only object // • reference, because passing by value is time-consuming // // Why don’t we insert a space or a newline after the 1+2i? // Because that’s not our job. Imagine how annoying it would be // if the statement cout << 5; inserted "5 " or "5\n". NO!!! // It should just insert the value, AND THAT IS ALL. ostream& operator<<(ostream &os, const Complex &val) { // If the number’s negative, it’ll emit its own minus sign. // It would be better to use the showpos manipulator here. const char *sign = val.imag() >= 0 ? "+" : ""; return os << val.real() << sign << val.imag() << 'i'; } // The extraction (input) operator, which reads something // like 1.2±3.4i. istream& operator>>(std::istream &is, Complex &val) { double re, im; if (is >> re && (is.peek()=='-' || is.peek()=='+') && is >> im && is.get()=='i') val = Complex(re, im); else // Needed because failure might have been due to a failed char // comparison (consider 3+2X), which wouldn’t set the fail bit. is.setstate(ios::failbit); return is; }