|
course.wilkes.edu/CS125Labs |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Lab 3a: Functions & Libraries
IntroductionAs you likely found out the last time you purchased your last textbook, books can be quite expensive. One way that this expense can be reduced is the idea of sharing: If you are unable to afford the full price for a book, and can find another person with the same problem, then you might purchase the book together, and share it. By having one person use the book and then another person re-use the book, the individual cost of using the book is cut in half. A library is a generalization on this idea of sharing. If a community of people pool their resources, then they can buy and share a centralized collection of books. By cooperating this way, each person has access to a greater set of books than he or she could afford individually. The ideas of sharing, libraries and re-use are very important in C++, and we have already (unwittingly) used them extensively. For example, in the previous lab exercises, we wrote #include <iostream>Recall that the effect of this directive is to insert the contents of the file iostream (in which cin, cout,
<< and >> are declared) into our source program.
Recall also that each of the programs we have written has
re-used this same set of declarations.
The file iostream is thus similar to a library of books,
in that it contains a set of declarations that can be
shared and re-used by any program that needs them.
In fact, iostream is a part of the iostream library
-- a software library of I/O related functions.
Software libraries are particularly useful and important,
because with carefuly planning, they can reduce the cost of programming.
For example, in a previous lab exercise, we used the While C++ provides a number of ready-made libraries for us, C++ also permits programmers to create their own libraries, which are technically called library modules, or just modules. (We will use the terms library and module interchangeably.) Creating a library is the subject of today's exercise.
Review
Let us briefly review what we know about function libraries.
We have seen that C++ provides a variety of function libraries,
including
To use a library function, a program must use the
#include <cmath>which inserts declarations of the library's functions into the program. To call a function, you must give its name and a pair of parentheses containing any arguments the function needs to do its job: result = pow(x, y);When control reaches such a function call, the arguments x and y are evaluated and passed to the function,
which performs its task and (if appropriate) returns a value
back to its caller (in this case x^y).
Planning a Library
To keep our presentation simple, we will construct our library in
our working directory for this exercise
(e.g.,
The library we will create today will provide us with a set of functions
to convert English-system measurements into their metric-system counterparts.
We will call our library The first thing that we must decide is what measurement conversions we wish our library to provide. For example, the following are just a few of the useful conversions:
Our exercise today is to write a subprogram
(more precisely, a function) to convert feet into meters.
By storing this function in library
Library StructureA library consists 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 it provides, without specifying their details. The part of the function that is provided in the header file is called its declaration or prototype. We will use the term prototype throughout this manual. By contrast, the role of the implementation file is to provide complete definitions of the library's functions. An implementation file thus contains a series of function definitions. Why this separation? The reason has to do with program maintenance. If we are writing a library, then we expect that a number of programs will make use of it. It is often the case that even a well-designed library may need to be updated (i.e., maintained), if a better way is discovered to perform one of its functions. If we have designed our functions carefully, then updating a library function should simply involve altering its definition (in the implementation file), not its prototype (in the header file). Now suppose that a program could access and make use of the definition details of a library function. If it did, then updating the function will likely change those details, and if the program were written in such a way as to be dependent on those details, then it would have to be updated, too. That is what we want to avoid at all costs -- we should never have to modify a program as a result of updating a library function.
By separating a library into two files,
we can place the prototypes in one file and make it visible to a program
(via the
Header File Structure
Begin by making sure that your working directory is
Using your text editor, edit OpeningDocumentation PrototypeListwhere OpeningDocumentation is a comment describing the
purpose of the library, who wrote it, when it was last updated, and so on;
and PrototypeList
is any sequence of valid C++ declarations.
Function DesignAs we have seen, software that is carefully designed is better than software that is poorly designed. The same holds true for functions, so let's spend a few moments using object-centered design to design our function.
BehaviorOur function should receive from its caller the number of feet to be converted, and should check that this value is positive. It should convert that quantity to meters by multiplying it by 0.3048, and return the resulting value to the caller. Note that where a program typically inputs values from the keyboard, a function typically receives values from whoever called the function. Similarly, where a program typically outputs values to the screen, a function typically returns a value to whoever called the function Note also that we want to try and anticipate what could go wrong. Since the function will only produce a correct result if the value it receives is not negative, we say that this function has a precondition, a condition that must be true in order for the function to execute correctly.
ObjectsThe objects of a function are similar to those of a program, except that in addition to describing the type and kind of each object, we also want to describe its movement: 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?
This information provides us with what we need to specify the problem our function solves:
Specification:
receive: feet, the (double) number of feet to be converted.
precondition: feet is not negative.
return: meters, the (double) equivalent of feet in meters.
Note that we include the function's precondition as part of its
specification.
This specification in turn provides us with what we need to write a function prototype. The simplified pattern for a function prototype is ReturnType FunctionName ( ParameterDecList ) ;where
A precise function specification tell us how to build the function's prototype. To illustrate:
FeetToMeters():
double FeetToMeters(double feet);
After you have added this line to Since parameters are like variables, their names usually begin with a lowercase letter, with every word after the first capitalized. By contrast, each word in a function's name is capitalized. 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. (And the specification provides all of the necessary information to write the prototype.)
OperationsContinuing with our design, our list of needed operations is quite short:
return statement permits a function
to return a value to its caller.
To test our precondition and halt the program if it fails,
we can use the
AlgorithmWe can then organize these operations and objects into the following algorithm: 1. Receive feet from the caller. 2. Halt the program if feet is negative. 3. Compute meters = feet * 0.3048. 4. Return meters.Given an algorithm, we are ready to define FeetToMeters()
in our implementation file.
Implementation File Structure
As stated previously, the definitions of library functions
are stored in the library's implementation file,
so using your text editor, open the file The general pattern for an implementation file is as follows: OpeningDocumentationTake a moment to personalize the opening documentation, and add a #include directive that inserts the header file
of our metric library.
(Note that quotes, not <>, surround the file name.
This tells the compiler to search the working directory for the file,
instead of the special system include directory.)
Defining A Function (Coding)The general pattern for a function definition is
ReturnType FunctionName ( ParameterDefList )
{
StatementList
}
where
A function stub is a minimal function definition --
like a function prototype, but followed by a pair of empty braces,
instead of a semicolon.
For example, we might define the following stub for double FeetToMeters (double feet) trueInsert this stub at the end of the implementation file and then compile it using the command: Compile command: g++ -c metric.cppIf all is well, then the function stub in metric.cpp
should compile without generating any error messages.
If you do get errors, find and correct them before continuing.
Take a moment and consider what we just did. We just compiled a file that contains no main program and the compiler didn't generate an error. How can this be? The answer is the idea of separate compilation. Translation of a program is actually a two-step process:
-c switch tells g++ to just do the first step
(i.e., stop after compiling), and so it produces the object file
metric.o.
What happens if you compile metric.cpp without the -c switch?
Now that we have a stub, we can "fill it in" using our algorithm and stepwise translation.
1. Receive feet from the caller.This step is taken care of for us by the C++ function-call mechanism. When the caller evaluates an expression that calls our function, such as: FeetToMeters(2.5);a copy of the argument 2.5 will automatically be stored in parameter feet in our function.
That is, when our function begins execution, the value of feet
will be the value of the argument with which the function was called
(2.5, in this example).
2. Halt the program if feet is negative.
We can accomplish this step using the C++ assert(BooleanExpression);which, given a boolean expression, allows execution to proceed normally if the boolean expression evaluates to true, but halts the program and displays a diagnostic message if it evaluates to false. We can thus test our function's precondition by adding the line assert(feet >= 0);as the first statement in the function. To use assert(), you must #include the header file
cassert (or assert.h, depending
on how ANSI-compliant your compiler is).
3. Compute meters = feet * 0.3048.
Using the declaration statements and expressions that we learned about
in lab 2, add a declaration statement to
4. Return meters.
The third operation can be performed using the C++
return Expression;where Expression is any valid C++ expression.
In the stub of
When Click here for Part II
|
Home Membership
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Last update: Friday, February 9, 2001 at 11:06:57 AM. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||