This assignment introduces the concept of operator overloading, the use of the friend
keyword, and the concept of "const
correctness".
A number of the form a + ib, where i2 = -1 and a and b are real numbers, is called a complex number. We call a the real part and b the imaginary part of a + ib. Complex numbers can also be represented as ordered pairs (a, b). The addition and multiplication of complex numbers is defined by the following rules:
(a + ib) + (c + id) = (a + c) + i(b + d)
(a + ib) * (c + id) = (ac - bd) + i(ad + bc)
Using the ordered pair notation, these rules are written as:
(a, b) + (c, d) = ((a + c), (b + d))
(a, b) * (c, d) = ((ac - bd), (ad + bc))
C++ has a standard library class that represents complex numbers defined in the header file <complex>
. However, in this assignment we will create our own new data type, complex
, to process complex numbers. We will overload the stream insertion and stream extraction operators for easy input and output. We will also overload the operators + and * to perform addition and multiplication of complex numbers. If x and y are complex numbers, we can evaluate expressions such as x + y and x * y.
More information on complex numbers can be found here, but the description given above should be sufficient to complete this assignment.
Log in to Unix.
Run the setup
script for Assignment 6 by typing:
setup 6
You will need to write one class for this assignment. A main program to test your class will be provided.
complex
classThe complex
class represents a complex number as an ordered pair. Like the other classes we've written this semester, this class should be implemented as two separate files.
The class definition should be placed in a header file called complex.h
. Include header guards to prevent it from accidentally being #included more
than once in the same source code file.
The complex
class should contain the following private
data members:
double
to store the real part of the complex number.double
to store the imaginary part of the complex number.In addition to the data members described above, the class definition should also contain prototypes for the member functions described below.
The implementations of the class member functions should be placed in a separate source code file called complex.cpp
. Make sure to #include "complex.h"
at the top of this file.
The complex
class should have the following member functions (most of which are quite small):
complex
constructor
Parameters: The constructor should take two double
variables as arguments, representing the real and imaginary parts of a complex number. Give the two parameters the default value 0 in the prototype for the constructor (which will allow this constructor to double as a "default" constructor) See the page on Default Function Arguments.
Logic: Assign the function parameters to the corresponding data members.
set_complex()
Parameters: This member function takes two double
variables as arguments, representing the real and imaginary parts of a complex number.
Returns: Nothing.
Logic: Assign the function parameters to the corresponding data members.
tuple<double, double> complex::get_complex() const
Parameters: None.
Returns: This function returns a special C++ compound data type called a tuple. This will allow us to return more than one value from a function (two double
variables in this case).
Logic: Return the result of calling the standard library function make_tuple()
, passing it the real part and the imaginary part of the complex number. This can all be done in a single return
statement.
This member function does not alter any data members of the object that called the member function, so it is declared to be const
.
To use the tuple
data type, you must #include <tuple>
. Both tuple
and make_tuple
are part of the namespace std
. You can read more about the tuple
data type on this page.
set_real()
Parameters: A double
, representing the real part of a complex number.
Returns: Nothing.
Logic: Assign the function parameter to the real part data member of the object that called the member function.
get_real()
Parameters: None.
Returns: The real part of the complex number (a double
).
Logic: Return the data member representing the real part of the complex number.
This member function does not alter any data members of the object that called the member function, so it should be declared const
.
set_imaginary()
Parameters: A double
, representing the imaginary part of a complex number.
Returns: Nothing.
Logic: Assign the function parameter to the imaginary part data member of the object that called the member function.
get_imaginary()
Parameters: None.
Returns: The imaginary part of the complex number (a double
).
Logic: Return the data member representing the imaginary part of the complex number.
This member function does not alter any data members of the object that called the member function, so it should be declared const
.
operator+()
This member function will be called when the operator + is used to add two complex
objects. For example:
// Assume c1, c2, and c3 are complex objects c3 = c1 + c2 // Generates this member function call: c1.operator+(c2);
Parameters: This member function takes one parameter, a reference to a constant complex
object, representing the right operand of the arithmetic expression. The left operand of the expression is represented by this
, which points to the complex
object that called the member function.
Returns: A complex
object that holds the result of the arithmetic.
Logic: Declare a complex
object to hold the result of the arithmetic. You will need to set the real part and the imaginary part of this result object to the correct values, as outlined in the ordered pair version of the addition rule shown at the beginning of the assignment sheet. For example, the real part of the result object should be set to the sum of the real part of the left operand (which corresponds to the variable a in the rule) and the real part of the right operand (which corresponds to the variable c in the rule). Once you've done the arithmetic, return the result object.
This member function does not alter any data members of the object that called the member function, so it should be declared const
.
operator*()
This member function will be called when the operator * is used to multiply two complex
objects. For example:
// Assume c1, c2, and c3 are complex objects c3 = c1 * c2 // Generates this member function call: c1.operator*(c2);
Parameters: This member function takes one parameter, a reference to a constant complex
object, representing the right operand of the arithmetic expression. The left operand of the expression is represented by this
, which points to the complex
object that called the member function.
Returns: A complex
object that holds the result of the arithmetic.
Logic: Declare a complex
object to hold the result of the arithmetic. You will need to set the real part and the imaginary part of this result object to the correct values, as outlined in the ordered pair version of the multiplication rule shown at the beginning of the assignment sheet. Once you've done the arithmetic, return the result object.
This member function does not alter any data members of the object that called the member function, so it should be declared const
.
operator==()
This member function will be called when the operator == is used to compare two complex
objects. For example:
// Assume c1 and c2 are complex objects if (c1 == c2) // Generates this member function call: c1.operator==(c2);
Parameters: This member function takes one parameter, a reference to a constant complex
object, representing the right operand of the relational expression. The left operand of the expression is represented by this
, which points to the complex
object that called the member function.
Returns: A boolean value.
Logic: The member function should return true
if the real part of the left operand equals the real part of the right operand AND the imaginary part of the left operand equals the imaginary part of the right operand. Otherwise, the member function should return false
.
This member function does not alter any data members of the object that called the member function, so it should be declared const
.
In addition to the member functions described above, you will need to write two standalone functions. These functions are not (and can not be) member functions. You should
friend
declaration for each of these functions in the complex
class definition.complex.cpp
.operator<<()
This function will be called when the stream insertion operator << is used to print a complex
object. For example:
complex c(3, 5); // A complex number cout << c; // Generates this function call: operator<<(cout, c);
In the example code above, the ostream
object cout
(the left operand in the stream insertion expression) will be passed to the function as the first parameter, while the complex
object c
(the right operand) will be passed to the function as the second parameter.
Parameters: This function takes two parameters. The first is a reference to an ostream
object, representing the left operand of the stream insertion expression. The second is a reference to a constant complex
object, representing the right operand of the expression.
Returns: A reference to an ostream
object (i.e., the first parameter).
Logic: This function should print a complex
object as an ordered pair of the form:
(real part, imaginary part)
So printing the object c
from the example above should produce the output:
(3, 5)
You can use the left operand parameter to print a left parenthesis:
os << '('; // "os" is the reference to an ostream object // passed into the member function as the 1st parameter
In similar fashion, you can use the left operand and the << operator to print the real part of the right operand, followed by a comma, a space, the imaginary part of the right operand, and a right parenthesis. At the end of the function, return the left operand (to allow cascading of the << operator to work correctly).
operator>>()
This function will be called when the stream extraction operator >> is used to read a complex
object. For example:
complex c; // A complex number cin >> c; // Compiler generated function call: operator>>(cin, c);
In the example code above, the istream
object cin
(the left operand in the stream extraction expression) will be passed to the function as the first parameter, while the complex
object c
(the right operand) will be passed to the function as the second parameter.
Parameters: This function takes two parameters. The first is a reference to an istream
object, representing the left operand of the stream extraction expression. The second is a reference to a complex
object, representing the right operand of the expression.
Returns: A reference to an istream
object (i.e., the first parameter).
Logic: This function should read input entered by the user as an ordered pair of the form:
(real part, imaginary part)
You can use the left operand parameter to read the left parenthesis entered by the user into a char
variable:
char ch; is >> ch; // "is" is the reference to an istream object // passed into the member function as the 1st parameter
(You don't need to do anything with parenthesis once it's been read. We just need to get it out of the way so we can read the stuff that's important.)
Once the left parenthesis has been read, you can then read the real part of the input entered by the user into the real part of the right operand object. Then read the comma into your char
variable. Read the imaginary part of the input entered by the user into the imaginary part of the right operand object. Then read the right parenthesis entered by the user into the char
variable. Finally, return the left operand (to allow cascading of the >> operator to work correctly).
The setup script will create the directory Assign6
under your csci241
directory. It will copy a makefile named makefile
to the assignment directory.
You will also receive a driver program named main.cpp
that contains a main()
function that will test your complex
class.
The correct output for this assignment is shown below (text in red represents input entered by the user):
Testing constructor... done Testing default constructor... done Testing stream insertion operator and constructors... c1 = (23, 34) c2 = (0, 0) Testing get methods... Real part of c4 = 3 Imaginary part of c4 = 4 Real part of c4 = 3 Imaginary part of c4 = 4 Testing set methods... New value of c2 = (3.7, 2.5) New value of c2 = (-1.4, 2.5) New value of c2 = (-1.4, 83) Testing stream extraction operator... Enter a complex number in the form (a, b) (2, 7) New value of c2 = (2, 7) Testing addition operator... c3 = (26, 38) (3, 4) + (23, 34) = (26, 38) (3, 4) + (3, 4) = (6, 8) Testing multiplication operator... c3 = (-67, 194) (3, 4) * (23, 34) = (-67, 194) (3, 4) * (3, 4) = (-7, 24) Testing equality operator... (23, 34) and (-67, 194) are not equal (3, 4) and (3, 4) are equal
If you would like a copy of this output to compare against your own program's output using the diff
command, it is available on Unix at the pathname /home/turing/t90kjm1/CS241/Output/Spring2021/Assign6/output6.txt
.
The driver program is designed to make this assignment easy to develop incrementally. Simply comment out all of the lines of the driver program that call functions that you haven't written yet. You should be able to write, test, and debug function at a time. I would suggest writing the functions in the following order:
operator<<()
get_real()
, get_imaginary()
, get_complex()
set_real()
, set_imaginary()
, set_complex()
operator>>()
operator+()
operator*()
operator==()
Remember that member functions that do not change any data members of the object that called them should be made const
so that they may be called by const
objects. If not, you will get the syntax error message "error: passing 'const complex' as 'this' argument discards qualifiers"
when the member function is called by a const
object.