Lab 10

advertisement
Laboratory 10
Abstract Data Types
Introduction
An abstract data type (ADT) is a data type whose implementation details are hidden from
the user. Only the user interface is visible. This ability is provided by the class construct
with its public and private parts. The public part is the user interface; the private part
contains the implementation details. This helps the user maintain a high-level (abstract)
view of the data type. He may concentrate on the functionality of the ADT without
concerning himself with its implementation.
In this lab we will work entirely with the Rational ADT, which is described in the text
in Chapter 8, Sections 8.2 - 8.4. The authors present only a portion of the ADT. It is
enough for us to see how things are done. Much of this lab will be devoted to adding
functionality to the class. For example, we will add subtraction and division operators, a
less-than operator, and various improvements of existing functions. Once we are satisfied
with our Rational ADT, we will make it into a library.
Key Concepts





Abstract data type
Rational class
Operators
Object integrity
Library
Before the Lab
Read Chapter 8 in Cohoon and Davidson.
Preliminary





Turn the PC on, if necessary.
Access the network.
Copy the source files from the Lab 10 Source Files folder.
Start up CodeWarrior.
Open the LabWork project.
The Rational Class
Observation
The program RationalTest.cpp will perform various calculations with Rational
objects to see if the results are correct. If everything checks out, then we will begin to
add new features to the class definition.


Open the file RationalTest.cpp.
Read the source code to see what the program is supposed to do.
It will construct three rational numbers, 0/1, 3/1, and 3/4. Then it will read a rational
number (an integer, followed by /, followed by another integer) and print it, to test the
extraction and insertion operators. It then tests the arithmetic operators of addition and
multiplication. Notice that it adds an int to a Rational and it multiplies an int by a
Rational. Finally, it adds 1/2 and 1/3 and prints the result.





Add the files rational.cpp and RationalTest.cpp to the LabWork project.
Run RationalTest.cpp.
Check the results. Are they correct?
Run RationalTest.cpp again, entering an unreduced fraction, such as 3/6.
Check the results. What do you notice?
We should pay special attention to the add() function, since it is representative of the
other Rational arithmetic functions.


Locate the function add() in the file rational.cpp.
Read the code in the function to see how it works.
Notice that add() gets the numerator and denominator of the “invoking object” and
names them a and b and then it gets the numerator and denominator of the parameter
Rational object and names them c and d. These short names are used strictly for
convenience. Now it must add a/b and c/d. Of course, we cannot write simply a/b +
c/d, for that would invoke integer division. We must compute the numerator as an
integer and the denominator as an integer using the rule
a/b + c/d = (ad + bc)/bd.
Furthermore, the computed numerator and denominator must be combined to form a
Rational object. All of this is accomplished by the expression
Rational(a * d + b * c, b * d)
This expression computes a * d + b * c and b * d and then passes them to the
Rational
constructor, which returns a Rational object.
Will the sum be reduced? That is, if the add() function adds 1/3 and 1/3, the above
formula will compute 6 and 9 for the numerator and denominator. Will add() return 6/9
or will it return 2/3? It will return 2/3. Why?

Read the code carefully and determine at what point the fraction is reduced.
Experimentation
Now we will add two new member functions to the Rational ADT:
subtract()
divide()



Open the file rational.h.
Add the subtract() and divide() prototypes to the Rational class definition in
the file rational.h.
Write the subtract() and divide() function definitions in the file
rational.cpp.
Use the addition and multiplication member functions as models. When dividing
Rationals, be sure to check that the divisor is not zero. If it is zero, write an appropriate
error message to cerr and return Rational(0).
Now we will add two operators, operator-() and operator/(), which will do
subtraction and division. These operators should call on the functions subtract() and
divide().


Add the operator-() and operator/() prototypes in the file rational.h.
Add the operator-() and operator/() function definitions in the file
rational.cpp.
Use the operators + and * as models.
We will test these changes once we have modified RationalTest.cpp.
Add code to RationalTest.cpp to do the tasks listed below. Add this code immediately
after the lines
r = 2 * r;
cout << "Multiply by 2:


" << r << endl;
Add lines that will subtract OneHalf from r and print the result.
Add lines that will divide r by Two and print the result.
Now test your changes.


Run RationalTest.cpp to verify that everything works.
Check the results carefully. Everything should look fine.
Observation
There is a potential problem, which we will not attempt to fix.


Change the lines that compute and print 1/2 + 1/3 so that they compute and
print 1/1000000 + 1/1000000 (one millionth plus one millionth).
Run the program again.
We would expect the result to be 2/1000000, or 1/500000.

What is the output? Why?
The problem is that the add() function computed a denominator of 1000000*1000000,
which is too large to store as an int. This is a problem of which the client programmer,
who uses the Rational ADT, must be aware (since we are not going to do anything about
it).
The Constructor and the Extraction Operator
Observation
The constructor and the extraction operator currently have a minor shortcoming, which
we will now demonstrate.


Run RationalTest.cpp. When you enter a fraction, enter one with a negative
denominator, such as 13/-4.
What happens? Is this acceptable?
Of course, it is mathematically possible to divide 13 by -4, but it is customary to write the
fraction as -13/4, not 13/-4.
Experimentation



Modify the input() function so that if the user enters a negative denominator, the
function will make it positive and reverse the sign of the numerator. (Of course,
the user could have entered a negative numerator as well as a negative
denominator.)
Make a similar improvement in the Rational(int, int) constructor.
Modify the definition of t in RationalTest.cpp from
Rational t(3, 4);
to
Rational t(3, -4);


in RationalTest.cpp.
Run RationalTest.cpp. Enter the fraction 13/-4. Does it work better now?
Run RationalTest.cpp again. This time, enter the fraction -13/-4. Does it still
work?
Open the input.cpp file and notice in the code that if the user wants to enter a whole
number as a Rational, we should allow him to enter only the numerator. After reading
the numerator, how will the program determine that a denominator will not follow? We
must use in.get() to get the very next character following the numerator and then
determine whether it is a '/'. If it is, then we will also read the denominator; if not, then
we will set the denominator to 1 and then put the character back in the input stream using
in.putback().



Comment out all of the code for the input() member function in rational.cpp.
Copy and paste the input() function from input.cpp into rational.cpp. Now,
member function calls to input() by a Rational object will use the new version.
Run RationalTest.cpp to see if this improvement works. Enter the number 3.
Run RationalTest.cpp again, entering 3/2 to be sure the old method still works.
The Insertion Operator
Experimentation
As the output() function is currently written, it will always print the numerator and the
denominator, even when the denominator is 1. For example, it printed
The value of r is 0/1
The value of s is 3/1
when we ran RationalTest.cpp. We would like to modify output() so that it will
print only the numerator when the denominator is 1.


Modify the definition of output() so that when the denominator is 1, only the
numerator is printed.
Run RationalTest.cpp to test this improvement. Enter the fraction 3/1.
The Less-Than Operator
Experimentation
We want to provide one more operator, the less-than operator (<). To do this, we will
first write a lessThan() facilitator function. Then it will be a simple matter to write the
operator<().
Since we know that the denominators of our fractions are positive, the inequality
a/b < c/d
is equivalent to the inequality
ad < bc.
In other words, an inequality involving rationals may be rewritten as an equivalent
inequality involving integers. Thus, the lessThan() function should make its
determination by evaluating the expression a * d < b * c.




Write the prototype for the lessThan() member function in the Rational class
definition in the file rational.h. Be sure that lessThan() is a public member
function.
Write the prototype for operator<() in the file rational.h. Be sure that
operator<() is defined outside of the class definition, with the other operators.
Write the definition of lessThan() in the file rational.cpp.
Write the definition of operator<() in the file rational.cpp.
In a similar way we could define the operators >, <=, >=, ==, and !=, but we won’t. We
will test our implementation of less than.
In the file RationalTest.cpp,

Remove the comment delimiters /* and */ which enclose some code near the
bottom of the program RationalTest.cpp. Remove only the delimiters, not the
code.
This will admit the lines
cout << endl <<
r = Rational(2,
s = Rational(3,
if (r < s)
cout << r
else if (s < r)
cout << s
else
cout << s
"Test the less-than operator" << endl;
3);
5);
<< " is less than " << s << endl;
<< " is less than " << r << endl;
<< " equals " << r << endl;
into the program, so that we can test the less-than operator.

Run RationalTest.cpp. Did the less-than operator work?
Application
Open the MS Word document Lab 10 App.doc, found in the Lab 10 App subfolder, and
follow the instructions.
Summary
The user interface of an ADT should provide the user with access to the objects in a way
that feels natural. We saw that using + feels more natural than a function add() when
adding rational numbers. It requires considerably more work on the part of the
programmer. In fact, you may as well know that C++ is a language designed for large
projects (programs of 1,000,000+ lines of code). For such programs, a good user
interface will reward the programmer many times over. On the other hand, for relatively
small programs, it can seem like more trouble than it is worth.
We also touched on the topic of object integrity, i.e., guaranteeing that the object is stored
in a valid form (in this case, the denominator must be positive). By reducing our
fractions, we could further ensure that the fractions stored are valid. Of course, there is a
limit to everything. To improve our Rational class further, we could add lines of code
that would warn the user whenever a numerator or denominator exceeded INT_MAX, the
largest legal int.
We learned how to compile a library. Once the library is compiled, then it may be used in
place of the “.cpp” file, which contains the source code. In this way a programmer may
protect his proprietary rights to his source code, yet still share his programs with others.
Also, a smart linker will extract from the library and put into the application code only
those library functions that the application requires. This feature minimizes the size of
the application code.
Download