Complex numbers consist essentially of ordered pairs of real numbers. For example, the pair (2, 3) is shorthand for the complex number
2 + 3i
where i =
.
This definition implies the rules of arithmetic which apply to complex numbers. For example,
(2 + 3i ) + (1 + 2i) = (2 + 1) + (3 + 2)i = 3 + 5i (ADDITION)
(2 + 3i) - (1 + 2i) = (2 - 1) + (3 - 2)i = 1 + 1i = 1 + i (SUBTRACTION)
(2 + 3i) × (1 + 2i) = (2)(1) +
(3i)(1) +(2)(2i) + (3i)(2i) (MULTIPLICATION)
= 2 + 3i + 4i - 6 = -4 + 7i
(2 + 3i) ÷ (1 + 2i) = x + yi
==> (x + yi) × (1 + 2i) = 2 + 3i
==> x + yi + 2xi - 2y = 2 + 3i
==> x - 2y = 2
==> y = (x - 2)/2
2x + y = 3
==> 5x = 8 ==> x = 8/5 ==>
y = -2/10=-1/5
or, finally,
(2 + 3i) ÷ (1 + 2i) = 8/5 - 1/5 i = 1/5 (8 - i) (DIVISION)
All of these operations can be generalized as follows:
(x1 + y1 i) + (x2 + y2 i) = (x1 + x2) + (y1 + y2) i (ADDITION)
(x1 + y1 i) - (x2 + y2 i) = (x1 - x2) + (y1 - y2) i (SUBTRACTION)
(x1 + y1 i) × (x2 + y2 i) = [(x1)(x2) - (y1)(y2)] + [(x1)(y2) + (y1)(x2)] i (MULTIPLICATION)
(x1 + y1 i) ÷ (x2 + y2 i) = [(x1)(x2) + (y1)(y2)] + [(y1)(x2) -
(x1)(y2)] i (DIVISION)
(x2)2 + (y2)2
Sometimes, the two components of a complex number are referred to as the real and imaginary part. For example,
z = x + y i = (real part) + (imaginary part) i
So, Version 1.0 of our complex class might look like:
#include <iostream>
// Header File: complex.h
class complex {
public:
complex();
// Constructors
complex(double, double);
~complex();
// Destructor
double
getReal() const;
//
Accessors or Inspectors
double getImag()
const;
complex add(const complex)
const;
complex subtract(const complex)
const;
complex multiply(const complex)
const;
complex divide(const complex)
const;
void insert(ostream &)
const;
//I/O Facilitator
private:
double re;
double im;
};
#include "complex.h"
// Implementation File: complex.cpp
#include <iostream>
using namespace std;
complex::complex(){ //Constructor
re=im=0;
}
complex::complex(double x, double y){ //Constructor
re=x;
im=y;
}
complex::~complex(){}
double complex::getReal() const
{
return re;
}
double complex::getImag() const
{
return im;
}
complex complex::add(const complex z) const
{
return complex(re + z.getReal(),
im + z.getImag());
}
complex complex::subtract(const complex z) const
{
return complex(re - z.getReal(),
im - z.getImag());
}
complex complex::multiply(const complex z) const
{
return complex(re*z.getReal() - im*z.getImag(),
re*z.getImag() + im*z.getReal());
}
complex complex::divide(const complex z) const
{
double mod2 = z.re * z.re + z.im * z.im;
return complex((re * z.re + im * z.im)/mod2,
(im * z.re - re * z.im)/mod2);
}
void complex::insert(ostream &out) const
{
out << re << " + " << im << "
i" << endl;
return;
}
Version 1.5 of the complex class might incorporate overloaded insertion and extraction operators. Accomplishing this task mirrors that for the circle class. We must add the following auxiliary operators to the complex.h and complex.cpp files respectively:
ostream & operator<<(ostream &, const
complex &) ==> after declaration of class complex
in complex.h header file
ostream & operator<<(ostream & out, const
complex & z)==>at end of complex.cpp file
{
z.insert (out);
return out;
}
Finally, the class member functions add(), subtract(), multiply(), and divide() present awkward sytax for client programs. For example, to add two complex numbers, you would have to write something like
complex z, z1, z2
cin >> z1 >> z2;
z = z1.add(z2);
It would, of course, be much more natural if we could use the + operator so that
z = z1 + z2;
This can be accomplished in two ways. First, we could declare another class member function which overloads the + operator:
complex operator +(const complex z) const; // in complex.h
complex complex::operator +(const
complex z) const // in complex.cpp
{
return complex(re + z.re, im +
z.im);
}
Or, we could overload + as an auxiliary (non-member) operator as follows:
complex & operator +(const complex &, const complex &); // in complex.h
complex & operator +(const complex
& z1, const complex & z2) // in complex.cpp
{
return complex(z1.getReal() +
z2.getReal(),
z1.getImag() + z2.getImag());
}
It's worth playing around a little with the class as we have constructed it so far.
Lab Exercise. Continue the development of the fraction class.
Version 1.0 incorporated the four operations add(), subtract(), multiply(), and divide(). We also had accessor functions getNumerator() and getDenominator(). The testFraction driver program could be used to test your work up to this point.
Version 2.0. Add overloaded "friend" I/O operators << and >>. In order to avoid difficulties with Visual C++ 6.0, you could just make these auxiliary methods. The testFraction driver program becomes much more concise.
Version 3.0. Try your hand at overloading the "+", "-", "*", and "/" operators. Change the driver program to reflect these changes.
We took time for a brief discussion of the world of Hewlett-Packard calculators. These shun the typical infix (operator between the operands) notation in favor of what is usually referred to as RPN or Reverse Polish Notation. Here are a few expressions in each notation:
infix postfix (RPN)
3 + 5 3 5 +(3 + 5) * 2 3 5 + 2 *
(3 + 5 * 2)/(2 + 6) 3 5 2 * + 2 6 + /
Observe that parentheses are never necessary in RPN, explaining some of its appeal.
HW #6 (DUE: 4/16). Write a program which implements an RPN calculator. That is, input would consist of a string of characters consisting of numbers (0 to 9) and operators (+, -, *, /, =). The computer would then output the result.
Comments
It clearly will be necessary to "save" the numbers in the string until encountering an operator. At that point, the operator will be applied to the last two numbers, producing an intermediate result. Scanning of the input string will continue, "saving" numbers until another operator is seen, producing another intermediate result. When all the operators have been applied, the final result will be evident.
The process of "saving" numbers until operators are found and then retrieving them in Last In First Out (LIFO) order suggests that a stack might be the data structure of choice.
As you think about this algorithm, a serious question becomes apparent. The input string is scanned one character (char) at a time, but the operands are integers (int). How do you create a loop which examines characters and then recognizes and handles integers when required? HINT: The function putback() is a member function in the istream class. It places the previous character extracted from the input stream back on the stream. So, maybe you could detect a character in the range '0' to '9', then input it again as an integer?
In any event, this problem will require a stack of integers. So,
feel free to incorporate the following new data type, intStack.
//Header File: intStack.h
#ifndef INTSTACK_H
#define INTSTACK_H
class intStack
{
private:
int top;
int s[100];
public:
intStack();
void push(const int);
int pop();
bool empty() const;
bool full() const;
};
#endif
Lab Exercise. Write an implementation of the intStack class, call it intStack.cpp. Test your implementation with an appropriate driver. Try it with the driver, intStackDriver.cpp. Don't peek at my solution.
Lab Exercise. Write and implement a generic stack class.