First, you must understand a little more detail about abstract data types. That information is fairly general, so see these other notes.
struct
ureLooking at how to define a complex number in the computer, we get something like this:
double imag, real; char symbol;
Unfortunately, we'd have to have duplicates of each of these
three variables for any complex numbers we wanted to define.
So if we had two complex numbers x
and y
,
we'd need to declare:
double x_imag, y_imag, x_real, y_real; char x_symbol, y_symbol;
Not a happy situation! Think if you had 10 or 20 or more! The best we could hope for would be parallel arrays or something.
To alleviate this, C added a new way to conglomerate many values
which could be of different types (arrays, recall, would need to
all have the same exact type for all elements). This mechanism
is known as the struct
ure (struct
for
short). To define a complex number as a struct
type, we'd have to do this in C:
typedef struct { double imag, real; char symbol; } Complex;
But C++ shortened this to:
struct Complex { double imag, real; char symbol; };
Note that you still need the ;
after the close brace
-- even though it is no longer necessary to use typedef
on the struct
ure. Also note that the type name
(Complex
) has moved from the end of the definition to
right after the keyword struct
.
In either case, we can now declare variables of the type
Complex
as:
Complex a, b, c;
Each of these variables has a distinct copy of the three values
making up a complex number (imag
, real
,
and symbol
). Each of these values is known as a field
or member of the struct
ure (or, when looking at a
particular variable's copies, as a field or member of the
struct
ured variable).
So, how do we get to these values to manipulate them? Well, do you
recall that cin
owns a function named getline
(as well as others named get
, peek
, and
ignore
)? Recall that to access this function you had
to ask cin
's permission using the dot (.
)
operator: cin.getline(buff,MAX);
. Access to
struct
ure members is similar. To access the fields
of a
, for instance, you might type:
cin >> a.real; a.imag = -4.2; if (a.symbol == 'j') { cout << "I'm a double-E!!" << endl; }
Access to the members of the other variables would be similar --
simply replace the variable name a
with that of the
variable whose fields you wish to access.
Let's look at how you might implement an operation for a complex
number. Obviously we'll have to define how all operations occur
to a complex number since the computer didn't even know what one
looked like till a couple of minutes ago when we defined our
struct
ure. So, we'll need to define a function for
each of the operations in our list. Let's start with an easy one:
addition. A prototype for this function might look something like:
// add x to y and store in z --- z = x + y void Add(Complex x, Complex y, Complex & z);
Notice that it isn't required to do anything but list the new type like any other type. Two value arguments and a reference argument to change (it isn't a return value because it is actually many values -- not a single one as required by the return mechanism).
So, what does the body look like? Well, if we were adding two complex numbers in math, it might look like:
x_a + x_b i y_a + y_b j ------------------------- (x_a+y_a) + (x_b+y_b) i \___/ \___/ z_a z_b
So we should do the same for our C++ code:
// add x to y and store in z --- z = x + y void Add(Complex x, Complex y, Complex & z) { z.imag = x.imag + y.imag; z.real = x.real + y.real; z.symbol = x.symbol; return; }
Note how the imaginary part of the x
argument is
added to the imaginary part of the y
argument and
this result is stored in the imaginary part of the z
argument. Since the imag
field of a Complex
struct
ure is of type double
, the computer
already knows how to add and store these values. What we are
defining is how to add an entire complex number to another and store
the result. To do this, we have to add each of the imaginary parts
and the real parts and store them. Then we must pick a symbol
(I've chosen x
's symbol rather arbitrarily here) and
store that. Until all of these parts is done, the complex number
result is still incomplete.
Now we can call this function from the main
(or
elsewhere) like:
Add(a,b,c); // adds a and b and stores the result in c --- c = a + b
Cool!
struct
's and const &
However, while the language designers made sure that simple variables
and arrays would transfer to functions in a (relatively) fast and
efficient manner, they completely ignored struct
ures!
So, we often see programmers take action to alleviate this inefficiency.
Since we know that references are fast and efficient, we could pass
all struct
ure arguments as references. But this might
allow for accidental changes to arguments we meant to leave alone.
Protecting arguments from accidental change, though, is what
const
is all about! So, we can make any
struct
ure arguments to functions that should be value
arguments into const
ant references to make them
transfer to the function quickly and efficiently while maintaining
data security. For the Add
function above this might
look like:
// add x to y and store in z --- z = x + y void Add(const Complex & x, const Complex & y, Complex & z);
A call to this function would look the same as before except that,
since the arguments are a constant reference, we can't have literal
or expression actual arguments. Not a big loss since you can't
have literal or expression struct
ures anyway. *smiel*
But addition isn't all we have to define! The computer makes NO assumptions, you'll recall. That means that not only does it not know how to add two complex numbers, it doesn't even know how to assign values to complex numbers or input/output complex numbers! So, let's look at the implementation of these tedious details here.
There are two (basic) ways that assignment can take place. We might want to assign a variable a particular literal value, for instance:
// given a numeric variable named x... x = 5;
Or, we might want to assign one variable the value stored in another:
// given two like-typed variables named p & q... p = q;
So, we should define how both of these occur for complex number variables. The first is a little less clear, so we'll begin with the second. We might have a prototype like:
// assign x's value to y --- y = x void Assign(Complex x, Complex & y);
(Note that the x
argument could be made into a constant
reference as shown above. I'll not do that further to save a little
space and typing, but know that it can be done.)
And how to implement this? Simply:
// assign x's value to y --- y = x void Assign(Complex x, Complex & y) { y.imag = x.imag; y.real = x.real; y.symbol = x.symbol; return; }
Copying each member value from x
to the like-named
field of y
. We might call this function as:
Assign(b,a); // assign b's value to a --- a = b
Now on to the literal assignment. I mentioned before that you cannot
have a literal struct
ure, right? Well, you can, though,
have literals of the fields within the struct
ure. So,
we simply assign literal values for each member:
// assign x's value to y --- y = x void Assign(double imag, double real, Complex & x, char symbol = 'i');
Note here that I've made the symbol a default valued argument (since
most of the world uses i
for the symbol; in fact, the
only ones I know who use something different is the electrical
engineers -- EEs -- who use a j
; it's a long and sordid
story...maybe later I'll elaborate). The definition follows
fairly easily from here:
// assign x's value to y --- y = x void Assign(double imag, double real, Complex & x, char symbol = 'i') { x.imag = imag; x.real = real; x.symbol = symbol; return; }
And we could call this function as:
Assign(9,12,b); // assign b to be 9+12i
or:
Assign(9,12,b,'j'); // assign b to be 9+12j
(Note that the 9
and 12
are automatically
upgraded to type double
to match the argument types.
If this doesn't happen, there is something wrong with your compiler!)
Note that we've overloaded Assign
so that the name is
not difficult to guess. The number and types of arguments will allow
the compiler to figure out which version to call.
So we now might have in our program somewhere:
Complex a, b, c; Assign(9,12,b); // or Assign(9,12,b,'j'); Assign(b,a); Add(a,b,c);
Not bad...but what about:
We still have to teach the computer how to print a complex number on the screen! Our first guess might be something like:
void Output(Complex x) { cout << x.real << x.imag << x.symbol; return; }
Let's look at a few cases here:
Complex Number Output on Screen ------------------------------------------------ 9-4i 9-4i 9+4i 94i 9+0i 90i
Not too glorious. Let's try this:
void Output(Complex x) { cout << x.real << '+' << x.imag << x.symbol; return; } // Complex Number Output on Screen // ------------------------------------------------ // 9-4i 9+-4i // 9+4i 9+4i // 9+0i 9+0i
*ich* Not a whole lot better. Let's think about this some
more. We have a value that may be positive, zero, or negative.
Didn't I see something like that in the book...printing plus
signs on numbers? Yeah...it was in the iomanip
library: ios::showpos
the input/output flag. If
set (raised), it tells the computer to print a sign even for
positive numbers! Let's try that:
void Output(Complex x) { cout << x.real << setiosflags(ios::showpos) << x.imag << resetiosflags(ios::showpos) << x.symbol; return; } // Complex Number Output on Screen // ------------------------------------------------ // 9-4i 9-4i // 9+4i 9+4i // 9+0i 9+0i or maybe 90i
Well, that's much better, but it doesn't work on all compilers. Hmm...maybe we could look at the value and print a plus sign only when we needed it...yeah!
void Output(Complex x) { cout << x.real; if (x.imag >= 0) { cout << '+'; } cout << x.imag << x.symbol; return; } // Complex Number Output on Screen // ------------------------------------------------ // 9-4i 9-4i // 9+4i 9+4i // 9+0i 9+0i
Cool! Now we have it working for any value on any compiler! Yippee! Oh, sorry...got carried away in the moment.
BTW, note that this output function doesn't output anything
but the complex number. It doesn't print spacing before, after,
or in between the values. It doesn't print any labels. It
doesn't even print a newline (endl
) or anything!
Why is that? Well, look at the way the standard data types
print. They print their value and nothing more or less. Somewhere
in the iostream
library, there are functions defining
that this is how it should be. So, when we define our own data
types, we should follow suit: print the value and nothing more
or less.
Now we can add to our program:
Complex a, b, c; Assign(9,12,b); // or Assign(9,12,b,'j'); Assign(b,a); Add(a,b,c); Output(c);
So they can see the answer! But...what if they want to:
How do we read a complex number from the keyboard? Well, how might the user like to type a complex number? Several ways are:
9+4j 9 +4j 9+ 4j 9+4 j 9 + 4j 9+ 4 j 9 +4 j 9 + 4 j
Hmm...so a complex number might actually have spaces inside it. Okay. We need to read the stuff, possibly space-separated possibly not, into our fields. Let's try this:
void Input(Complex & x) { cin >> x.real >> x.imag >> x.symbol; return; }
How will this react to the above inputs? Recall that spaces
between data are ignored and characters after data are ignored
but characters IN data cause cin
to lock
up and die -- never reading anything else again. Let's see:
Input real imag symbol left ------------------------------------------------------------------- 9+4j 9 4 j 9 +4j 9 4 j 9+ 4j 9 --- --- + 4j (cin is dead) 9+4 j 9 4 j 9 + 4j 9 --- --- + 4j (cin is dead) 9+ 4 j 9 --- --- + 4 j (cin is dead) 9 +4 j 9 4 j 9 + 4 j 9 --- --- + 4 j (cin is dead)
Not great, but not bad. We got half of them working right off
the bat! It's that plus sign that causes a problem (it might
also be a minus sign: 9 - 4j
). So, let's read
that in separately:
void Input(Complex & x) { char plusminus; cin >> x.real >> plusminus >> x.imag >> x.symbol; return; }
How's that do? Well:
Input real plusminus imag symbol left ------------------------------------------------------------------- 9+4j 9 + 4 j 9 +4j 9 + 4 j 9+ 4j 9 + 4 j 9+4 j 9 + 4 j 9 + 4j 9 + 4 j 9+ 4 j 9 + 4 j 9 +4 j 9 + 4 j 9 + 4 j 9 + 4 j
WooHoo!! But:
Input real plusminus imag symbol left ------------------------------------------------------------------- 9-4i 9 - 4 i 9 -4i 9 - 4 i 9- 4i 9 - 4 i 9-4 i 9 - 4 i 9 - 4i 9 - 4 i 9- 4 i 9 - 4 i 9 -4 i 9 - 4 i 9 - 4 i 9 - 4 i
And shouldn't our imaginary part be negative here? After all,
our math format is: a + bj
. So the imaginary
part should be negative since we have: 9 + -4i
.
(It just looks icky to do it that way and so we type 9 - 4j
,
instead. BTW, are you getting that the symbol is pretty irrelevant
here. It stands for the square root of -1 -- which doesn't truly
exist. It is only a placeholder.)
So, we need to fix this a bit:
void Input(Complex & x) { char plusminus; cin >> x.real >> plusminus >> x.imag >> x.symbol; if (plusminus == '-') { x.imag = -x.imag; } return; }
Thus, if plusminus
is a minus sign, we'll negate
that positive value stored in our imaginary part to reflect
it.
Note again that this code reads a complex number and nothing
more. It doesn't print a newline afterwards. It doens't prompt
for the value. It merely reads the value and stores it. This
reflects how cin
handles the standard data types.
Now we can make our program more useful looking:
Complex a, b, c; cout << "Enter a complex number: "; Input(a); Assign(9,12,b); // or Assign(9,12,b,'j'); Add(a,b,c); Output(a); cout << " + "; Output(b); cout << " = "; Output(c);
Now we can input, assign, output, and add complex numbers! Pretty snazzy!
This tedium applies to not just complex numbers, but to any data type you might choose to define. You'll have to teach the computer to do everything to each one separately. For instance, if you were going to implement the rational numbers described above as an ADT, you'd have to define addition, input, output, and assignment (among other operations) all over again -- for rational numbers.
The main difficulty here is representing these various data types on the screen. For instance, rational numbers are normally written as:
a --- b
A format fine for pencil and paper, but not easily done with character output on a screen. So we sometimes make compensations to the medium of display. Rational numbers, for instance, are most often displayed (and input) as:
a / b (or a/b or a /b or a/ b)
So watch yourself as you implement ADTs for various things. Remember to always include input, output, and assignment for any ADT you define. Watch the I/O format to make sure it's not too difficult to type or understand. Then add any other operations needed for the type.
Since a data type is generally used in many programs (we use
short
and char
all the time, right?),
we'd like to place every ADT in a library as well. So, we'll
put the type definitions and operation function prototypes in
the header and the operation function definitions in the
implementation and just re-use it in different applications.
Here is a
library and testing application
for the complex ADT begun above.
enum
's
struct
ure doesn't an ADT make...
Complex
Point
& Circle
Brick