As you may have experienced when purchasing textbooks, books can be quite expensive. One way to save some money would be to share books with someone else. In fact, the more people you can share your books (or CDs, DVDs, etc.) with, the more you can save.
A library (sometimes called a library module or simply a module) is a generalization of this idea of sharing. When a group of people pool their resources, they can buy and share a centralized collection of books. By cooperating this way, everyone has access to a greater set of books than they could afford individually.
In the digital world, sharing libraries is even easier. While a physical book can be actively used by only one person at a time, a digital file can be shared by many people all at the same time. Similarly, in our situation, the code can be written once and shared by many other programs.
In fact, we have already been sharing code written by others when we've
#include
d libraries. For example, in all of the programs
thus far, we've used the iostream
library. This library defines
cout
and cin
as well as the output
(<<
) and input (>>
) operators. There's
actually a lot more in that library (such as the formatting manipulators
described in Chapter 4), and we certainly don't
want to write all of that code ourselves for every program we write.
By reusing the library, we save themselves enormous amounts of time.
As we'll see in this lab, there's nothing magical about a library, In fact, you will create your own library. The main difference between the libraries you write and the standard libraries of C++ is their size. The libraries provided in C++ are much larger than the ones you will write.
We'll begin writing our own library in this lab — an English-to-metric conversion library. It will contain only two basic conversion functions, but there are several others that could be added. For example, see the project described here. Your instructor may have you extend this library to include some of them.
We begin with a brief review of what we know about function libraries.
We've seen that C++ provides a variety of function libraries, including
iostream
that provides input and ouput, cmath
that
provides various mathematical functions, and cctype
that
provides character-processing functions.
To use a library function, a program must use the #include
directive to make the contents of that program available to the
program; for example,
#include <cmath>inserts basic contents of the library into your program so they can be used in your program.
In most programs, we are interested in functions such as
sqrt()
and pow()
and operators such as
<<
and >>
.
(Note: To indicate that an identifier such as
pow
is the name of a function as opposed to naming a
variable, constant, file, etc., it is common to attach parentheses to
its name.) To call a function
you must specify its name and a list of arguments —
values to be sent to the function (or as we'll see later,
possibly returned by it) — in parentheses. For example, if we have
numeric variables x
and y
, we can calculate
xy
— that is, x
to the
y
th power — by calling the function pow()
in
the cmath
library as follows:
pow(x, y)Question #5.1: In the function call above, what is the name of the function being called? What are the arguments in the function call?
The code associated with the function is executed, and the result
xy
is returned.
Since this is an expression,
you will undoubtedly put it into a context; for example, assign it to a
variable
or output it:double result = pow(x, y);
cout << "The answer is " << pow(x, y)
<<".\n";
The library we will create in this lab exercise will provide a set of
functions to convert English-system measurements to their metric-system
counterparts. We will name our library metric
.
The first thing we must decide is what measurement conversions we wish our library to provide. Here are some candidates:
English Unit Metric Unit Conversion Equation Inches Centimeters 1 inch = 2.54 cm Feet Centimeters 1 foot = 30.48 cm Feet Meters 1 foot = 0.3048 m Yards Meters 1 yard = 0.9144 m Miles Kilometers 1 mile = 1.609344 km Ounces Grams 1 ounce = 28.349523 g Pounds Kilograms 1 pound = 0.453592 kg Tons Kilograms 1 ton = 907.18474 kg Pints Liters 1 pint = 0.473163 l Quarts Liters 1 quart = 0.946326 l Gallons Liters 1 gallon = 3.785306 l
In this lab exercise, you will write two functions,
one to convert feet into meters and the other to convert meters into feet.
(You instructor may assign more of the conversions above to add to the
library.)
We will put hese functions in our metric
library so they can be
shared by any and all programs that need to convert between feet and
meters and thereby avoid having to "reinvent the wheel."
When you begin work with the the four files described in the Files section below, you will see that you've already been provided with the feet-to-meters conversion. We'll walk through this example, and then it will be your task to add the meters-to-feet conversion.
A library consists of three separate files:
The header file is necessary, and its role is to provide an interface to the library, by providing just enough information for a program to use the functions in the library, without specifying their details. In contrast, the role of the implementation file is to provide complete definitions of the library's functions. You'll notice some redundancy in these two files, but this is unavoidable.
Helpful hint: Remember to check both your header file and implementation file when something goes wrong with your library.
Why all of these files? The reason has to do with program maintenance. If we are writing a library, then we expect that many programs will make use of it. It is often the case that even a well-designed library may need to be updated if a better way is discovered to perform one of its functions or even if a function needs to be fixed. If we have designed our functions carefully, then updating a library function should simply involve altering its definition which is in the implementation file. We may be able to leave the header file alone and thereby save users of the library from having to recompile their programs.
In this lab you will be using four files:
metric.h
,
metric.cpp
, and
metric.txt
implement a metric
conversion library.driver.cpp
implements a driver for the metric
conversion library..cpp
files to its Source Files folder.
.h
file to its Header Files folder.
.txt
file to its Resource Files folder.
If you are using some other C++ environment, you will probably create a directory to store these four files.
Edit metric.h
, the header file of our library. For
convenience, we base the name of this file on the name of our library.
It is common practice to attach the extension .h
to the end
of the filename to specify that this is the header file.
Personalize the opening documentation of this file as your instructor has specified.
A simplified general pattern for a library header file is as follows:
Opening Documentation
List of Items Provided
The opening documentation is a comment describing the purpose of the library, who wrote it (and other information your instructor specifies). For the list of items provided by the library, for simplicity, we will use a list of the prototypes of the functions that the library provides.
The functions in the library should be designed in the same way as all functions. Thus, we will use OCD to design our library functions.
First, we describe the behavior of our feet-to-meters function:
Our function should receive from its caller the number of feet to be converted, and should check that this value is non-negative. If it is, it should convert that quantity to meters by multiplying it by 0.3048, and then return the resulting value to the caller.
Note that a function typically receives values from whoever
called the function whereas a program typically inputs values
from the keyboard. Similarly, a function typically
returns a value to whoever called the function whereas a
program typically outputs values to the screen.
This distinction is important because most functions do
not use cin
or cout
. We won't
need them in these library functions.
We should also try to anticipate what could go wrong. The function will only produce a correct result if the value it receives is non-negative; this is a precondition since it is a condition that must be true before the function can be executed.
Now let's consider our other function, converting meters to feet. This function won't be much different than the feet-to-meters function. So, the basic structure for the behavior should be the same. The values it receives and returns will be different and the computation is different (division instead of multiplication), but they are still quite similar.
Question #5.2: Write the behavior paragraph for the meters-to-feet function.
We list the objects of a function in the same way that we do for a program, except we also want to describe their movement. For each object, does it move into the function from outside, does it move from the function out to the outside, or is it purely local to the function?
Description Type Kind Movement Name the number of feet double
varying in feet
the conversion factor double
constant local 0.3048 the corresponding number of meters double
varying out meters
The movement column of this chart summarizes what data is coming in, what is going out, and what is simply being used inside the function. This allows us to write our specification, which also includes our precondition:
Specification:receive:feet
, the number of feet to be converted.
precondition:feet
is not negative.
return:meters
, the equivalent of feet in meters.
Keep in mind that formulating this information for a function is done in basically the same manner as for a program. The behavior paragraph is used to create the object chart. The nouns in the behavior are our objects, and the behavior explains the type, kind, and movement of the objects. The behavior even suggests the names of our objects.
Once we have the object chart, we can formulate the specification, looking for the objects moving into and out of the function. The behavior paragraph establishes our preconditions.
Question #5.3: Using your behavior description, make an object chart for the meters-to-feet function.
Question #5.4: Using your behavior description and object chart, formulate a specification for the meters-to-feet function.
The specification of a function provides us all we need to write a prototype, which is how we translate the specification for our function into C++ code. It specifies what the function does, not how is does it. We will need a prototype for every function we write.
The general pattern for a function prototype is
wherereturnType
functionName
(parameterList
);
returnType
is the type of value the function
returns. It can be any data type such as int
or
double
.functionName
is the name we decide
to give to the function. parameterList
is a list of declarations of
parameters.Each value that our function receives must be declared as a parameter. A parameter is basically just a variable like the ones we've declared and used up to now. The differences are subtle:
Because parameters are like variables, we will use the same naming convention as we've been using for variables: their names begin with a lowercase letter, with every word after the first capitalized. The same convention is used for naming functions. Athough these are are not universal conventions, they are commonly used and are the ones that we will use.
Using our specification and with some imagination, we can come up with the components of our prototype:
double
.feetToMeters()
.double
that we'll call feet
.double feetToMeters(double feet);
You will see this prototype in both the header file (metric.h
) and the documentation file (metric.txt
). C++
requires the prototype in the header file so that our program (and
others) can see it. We document it in the documentation file so that
users of the library know how to use our function; that's why you'll see the
specification just above it.
Question #5.5: Write a prototype for the meters-to-feet function. Add your prototype to both the header file and the documentation file. Also, add a comment containing the specification for the prototype in the documentation file.
As this illustrates, the stages of software design are fluid, not firm. We can build a function's prototype (a part of coding) during our design stage, as soon as we have enough information.
Continuing with our design, our list of needed operations is quite short:
Description Predefined? Name Library get a value from the caller yes feet
built-in check that the value is not negative yes >= 0
built-in halt the program if the value is negative yes assert()
cassert
multiply two real values yes *
built-in return a value to the caller yes return
built-in
We've already seen the parameter mechanism that allows us
to get the feet
value. We'll see more when we write our
function definition. We'll also see the return mechanism in action. The key
now is that all of these operations are built into C++. The
relational operators and the assert()
function
can be used to handle the preconditions.
Question #5.6: Construct a similar operation chart for the meters-to-feet function.
We can then organize these operations and objects into the following algorithm:
feet
from the caller.feet
is non-negative;
halt the program if it isn't.meters
= feet
* 0.3048.meters
.Question #5.7: Write a similar algorithm for the meters-to-feet function.
We store declarations in the header file. That's all a function prototype is: a declaration. It declares the function's name, it's parameters, and return type. But we haven't written any code for the function to execute!
That's the purpose of the implementation file. It contains the
definitions of our library functions; that is, it contains
the code for these functions. Implementation files are usually named the same as our header file,
except that they end in .cpp
.
The general pattern for an implementation file is as follows:
OpeningDocumentation
Include directives
DefinitionList
If you haven't already done so, personalize the opening documentation in
metric.cpp
.
A library's header file should normally be
#include
d in the implementation file.
For this, we use a variation of the #include
directive.
We've used the form
for system libraries. A personal library that we have in our working directory uses different punctuation:#include <iostream>
#include "metric.h"
→ Add this line to your implementation file.
The general pattern for a function definition is
returnType
functionName(parameterList)
{
statementList
}
where
returnType
,
functionName
, and
parameterList
are exactly the same
as in a function prototype;statementList
is a sequence of valid C++ statements.A function stub is a
minimal function definition. For example, this is the function stub
for feetToMeters()
:
Why bother with a function stub? Because we can compile the program with a function stub so we can check our program to make sure that at least what we've written so far contains no syntax errors.double feetToMeters(double feet) { }
Of course, we eventually fill the stub with code so that the function
does something. That's already been done for feetToMeters()
.
Now it's up to you to do this for the meters-to-feet function.
Start by writing a function stub for the meters-to-feet function, and
add it to the implementation file. Compile the program. You should
be able to compile the program without errors. You may get a warning
that your function does not return anything, but we'll let that slide
for now .
Also, you only have to compile the metric.cpp
file itself;
you do not have to link it with the driver yet. You can, but
you may get errors that you should ignore.
Now we can translate our algorithm into code, as we'll illustrate using out feet-to-meters conversion.
feet
from the caller.This step is taken care of for us by the C++ function-call mechanism. We've already done it! Declaring a parameter in our function heading
double feetToMeters(double feet)
informs C++ that we want to receive this value. That's the whole purpose of parameters!
Nevertheless, it's a good idea to put this step in our algorithm to remind us where the data comes from; but once we have the parameters declared, the function has already received the values from whatever called it.
feet
is non-negative and
halt the program if it isn't.This is our precondition. We decided that we'd do this with the
assert()
statement:
assert(feet >= 0);
That's exactly what's been done in our definition of
feetToMeters()
.
meters
= feet
* 0.3048.This step has been implemented in our declaration of meters
:
double meters = feet * 0.3048;
Remember that we have to declare all of our variables. The
feet
variable has already been declared as a
parameter---that counts! However, this is the first time we've used
meters
, so we declare it and initialize it with our computation.
The third operation can be performed using the C++ return statement. Its general pattern is:
wherereturnexpression
;
expression
is any valid C++ expression. The result
of the expression is sent back to whatever code called this function.
Now it's your turn. Use your algorithm for the meters-to-feet
function plus the feetToMeters()
example to implement your
algorithm in the stub that you wrote earlier. Compile the code as
you go along (probably after you write each line of code); be sure it
compiles without errors or warnings when you finish.
Next, open driver.cpp
and personalize its documentation.
The program in this file was written to test the function we've written. Such programs are called driver programs because all they do is "test drive" the functions in a library.
Most driver programs use the same algorithm:
cin
.cout
the result of calling the function plus a
descriptive label.You've been given the code for part of the driver for this lab —
the part that tests the feetToMeters()
function.
You'll have to add another part to test-drive your
metersToFeet()
function. Also, you will have to
write the drivers for future labs from scratch so read over this code
carefully and make sure that you understand what it does.
Step 1 of the algorithm is merely an output statement. You've written a few of these already.
Step 2 is a matter of reading in data. This take a little more thought: you need to know what objects you need to input and their data types. Declare variables for these objects, and then read in the data.
Step 3 will usually consist of a variable declaration with an initialization that calls the function we're testing.
Step 4 is just an output statement that displays a label describing the result and displays the result itself.
Now, before we can use a library, we must include its header file in the driver file. This has been done already:
#include "metric.h"
Notice the saving we're already getting. We
write the header file once, but we're using it twice.
And this is just in one program! Be sure to use the #include "..."
form to include a header file from your
personal library.
Let's look more closely at Step 3 of our driver algorithm.
At the beginning of this lab, we quickly looked at the pow()
function. If we had two variables x
and y
, we can
call the function with the expression pow(x,y)
and
then use the value that this returns.
But we got the pow()
function from a system library. What do
we do with a function that we write ourselves? Exactly the same
thing.
Consider feetToMeters()
. It receives a number of feet. If
we want to convert 1.0 foot to meters, we could write this:
(For now, don't worry about the context — that is, what we do with the result). If we want to convert 2.0 feet to meters, we could writefeetToMeters(1.0)
If we want to convertfeetToMeters(2.0)
x
raised to the y
th power feet
to meters, we could write
In the case of our driver, however, the driver reads in a value, places it infeetToMeters(pow(x,y))
feet
, and we want to convert that value:
The driver uses this as the expression to compute the value of thefeetToMeters(feet)
meters
variable.
Our approach to the function has changed. When we declared the
function, we declared a parameter — a variable — to receive the
number of feet. But now that we're calling the function, we can pass
in a literal, a variable, or any double
expression. A value
that we pass into a function is known as an argument.
Helpful hint: A parameter is always a variable, it is part of a function definition, and we get to choose the name. An argument can be any expression, and it is part of a function call.
There are three important rules to remember when calling a function:
Compile and link all of your code, and test your program.
As it stands, however, the program only tests feetToMeters()
.
It should also test metersToFeet()
. There are several ways to
do this:
feetToMeters()
to test the
meters-to-feet function. This leads to a lot of extra code, but
not nearly as much as the first option.Let's try the third option here. So instead of reading in a number
of feet, let's read in a generic number. Let's call it measurement
. We can then use this one value as an argument to both functions. Its meaning in these functions will be different, a
number of feet in the one and a number of meters in the other, but
that's fine. Here's what the code would look like to test feetToMeters()
:
// Step 2. double measurement; cin >> measurement; // Step 3. double meters = feetToMeters(measurement); // Step 4. cout << measurement << " feet == " << meters << " meters\n";
Now all you have to do is use this code in the driver, and then duplicate Steps 3 and 4 for the meters-to-feet function.
Once the program is successfully translated, we are ready to test
what we have written, to see if we can discover any logic errors.
Execute the program, and use the following sample data to test the
correctness offeetToMeters()
:
Feet | Meters |
---|---|
1.0 | 0.3048 |
3.3 | 1.00584 |
−5.9 | ERROR |
Since feetToMeters()
is a linear function, two test values
are sufficient to verify its correctness. Be sure to note and
fix any discrepancies.
The third test value tests our precondition. This is also a good idea to make sure that the function fails when it's supposed to.
Question #5.8: What error message does the program give when you enter the invalid input?
Since you've been given feetToMeters()
, it should run
correctly. Your meters-to-feet function may not. The sample data
above can also be used to test your meters-to-feet function.
Question #5.9: What sample data are you going to use and what results do youexpect for your meters-to-feet function?
Debug and modify your program until you get the correct results from both functions.
After a library is released, the developers may start getting
complaints that the error message the functions give when they fail
are far too cryptic. The error message produced by assert()
is one such example. On some compilers, the message may be quite
difficult (and perhaps impossible for some people) to understand.
Alternatively, we might code our own error handling.
Consider, for example, feetToMeters()
. We formulated its
precondition almost at the very beginning and we wrote an expression for
it: feet >= 0
. We chose to use assert()
as a "quick and dirty" way to test this precondition.
But this isn't the only alternative. We would almost surely prefer more
"user-friendly" error messages, especially if our library is to be used
by other people. Think about what we want to happen: if the precondition is
false (i.e., feet
is negative), then we want to
display a helpful error message on the screen and stop the program.
For this, it is best to display the error messages using a new object
for output: cerr
. Although cerr
is of type ostream
and is used in the same way as
cout
to produce output to the screen,
cout
should be used for normal
output
while cerr
should be used for
error messages. (Why? See
the footnote at the end of this lab exercise.)
Four operations are needed here:
Description Predefined? Name Library feet
is negativeyes <
built-in if...then... yes if
statementbuilt-in display error message yes <<
iostream
stop the program yes exit()
cstdlib
The exit()
function, when executed, will
stop the program immediately. The function takes one argument, an
int
. The actual value of the argument is really up to you;
convention is that any non-zero value means the program stopped because of a
problem. So we may as well keep it simple and use -1
or
1
.
Using this information, we can replace Step 2 of the algorithm
for feetToMeters()
with:
feet
is negative, then...
So, in the code, we can replace the call to assert()
with this code:
This is much more informative that a cryptic computer-generated message.if (feet < 0) { cerr << "The number of feet is negative in feetToMeters()." << endl; exit (-1); }
Make this change to feetToMeters()
. Compile and test your
program, especially the failure test.
Once you have this working for feetToMeters()
, make similar
changes to your meters-to-feet function. The new behavior, objects,
operations, and algorithm should parallel the new design for
feetToMeters()
as should the new code.
It's a bit of a nuisance to have to execute the driver program three times for the three test values. Also, testing some libraries may require a much larger set of test values. Thus, it is usually best to build a repetition into driver programs to faciliate testing.
There are two kinds of repetition statements that we have studied thus far:
for
statement, andwhile
statement.
[Footnote: cout
sends its output to
a buffer and from there to the screeen only when directed to do
so or the buffer becomes full; but
cerr
sends its output directly to the screen. If a program
crashes sometime after an output statement has been executed, an error
message from cerr
will have been displayed on the
screen, whereas one from cout
might still be in the buffer and
never make it to the screen.]
Lab Home Page
Report errors to Larry Nyhoff (nyhl@cs.calvin.edu)