...and then [Men] would or could be made to mate with Orcs, producing new breeds, often larger and more cunning. There is no doubt that long afterwards, in the Third Age, Saruman rediscovered this, or learned of it in lore, and in his lust for mastery commited this, his wickedest deed: the interbreeding of Orcs and Men, producing both Men-orcs large and cunning, and Orc-men treacherous and vile.
--- J.R.R. Tolkien, Morgoth's Ring
How can we tolerate these indignities [being forced to program in Fortran or C equivalents because more creative programming languages are supressed]? The frustratingly simple answer is the universal nature of programming languages---the fact that one can program in any one of them what can be programmed in any other. We are able to clever our way out of any programming box. The more difficult the task, the more pride we can take in the accomplishment.
--- Richard Gabriel and Ron Goldman, "Mob Software: The Erotic Life of Code"
The goal of this lab is to practice using function pointers to emulate in C the object oriented features of encapsulating (data and functionality together), subtyping, and polymorphism.
Review the pre-lab reading as necessary.
Copy the following files from course public directory.
cp -r ~tvandrun/Public/cs245/lab15/* .
As described in the pre-lab, in this lab you will make a collection of structs that will model various kinds of mathematical functions and will carry with them function pointers that simulate methods for doing various operations on them--- specifically, evaluation at an x value, computing a derivative, and computing a definite integra. The Java solution will help you remember the formulas and such for that. (It wouldn't be too hard for you to figure this all out from scratch, as long as you've had calculus and remember it, but that would take too long. I'd rather your attention be focused on the new ideas of function pointers etc.)
function.h
Open function.h
.
This is equivalent to Function.java
in that its
purpose is to define a type according to a set of operations,
like an interface.
Open functiondriver.c
and notice how
this type is used.
As we know, C's facility for making new types is the struct. Structs cannot have behavioral members (ie, methods), but we can emulate methods by letting it have function pointers. For a moment, consider a different example. Take a circle type. We could make a struct that would contain "methods" for computing the circumerence and area, might look like
struct circle_t { double radius; double (*circumference) (struct circle_t * this); double (*area) (struct circle_t * this); }; typedef struct circle_t circle;
Note that the functions circumference()
and area()
have a circle *
parameter.
That's because otherwise the function has no reference to the "receiver"
of the "method call."
We need to pass that reference as a parameter (which is why I call that
parameter this
).
This is actually a realistic "translation" of what's going behind the
scenes during method dispatch in Java;
in Java bytecode, for example, all non-static methods have one extra
parameter for passing a reference to the receiver.
We would then need to write functions to serve for
circumference()
and area()
.
double circumference_c(circle* this) { return 3.14159 * 2 * this->radius; } double area_c(circle* this) { return 3.14159 * this->radius * this->radius; }
Finally, a constructor-like function would create an "instance."
circle* newCircle(double radius) { circle* toReturn = (circle*) malloc(sizeof(circle)); toReturn->circumference = circumference_c; toReturn->area = area_c; toReturn->radius = radius; return toReturn; }
Notice how the constructor-like function needs to initialize the function pointers.
Turning back to our example of implementing different kinds
of mathematical function objects,
we have an extra problem: We don't know what data member
function
should have, since that will vary among the subtypes.
To handle this, we give struct function_t
a field
data
of type void *
,
which is C's way of saying "pointer to a value of an unknown type"
(compare with Java's Object
class).
We will set data
to refer to structs polynomial
,
step
, and exponential
.
Two more things about function.h
:
First, it also has a function pointer called destroy
,
to refer to a function that deallocates the structure.
Second, the #ifndef FUNCTION ... #endif
stuff
prevents this file from being included more than once.
Inspect the type definition for polynomial
in
polynomial.h
,
and understand how the implementation of an evaluate
function works in polynomial.c
.
The function is called evaulate_p()
("p" is for "polynomial")
to distinguish it from the evaluate()
function for
the function struct.
For convenience (to avoid lots of casts), you will probably want almost all of the functions you write today to include a line like
polynomial* data = (polynomial*) this->data;
Your task is to finish this file by filling in the other functions.
(integrate_p()
is also done for you).
differentiate_p()
Notice that for finding a
derivative, you need to make a new polynomial (which means
making both a polynomial
and function
object.
You'll have to recall calculus rules to figure out what the coefficient
array will have to be for the resulting polynomial, but notice that
it will be one item shorter than the original.
destroy_p()
Deallocate the coefficient array,
the polynomial object, and the function object.
newPolynomial()
Create a new polynomial and function
object.
Do not just set the new polynomial's array to the array you are given,
since you don't have control on when that array may be deallocated.
Instead, copy its contents into a new array.
Deallocate everything you allocate! For every use of malloc
or calloc
, be able to identify a call to free
which
will undo the allocation.
Then compile (using the provided makefile
) and test using
the given driver.
Then make types for step functions and exponential functions in the appropriate files. Uncomment the appropriate lines in the driver to test them.
For the exponential type, you will probably want to use the
standard function pow()
, which works just like
Java's Math.pow()
.
You'll need to include math.h
.
Notice that in the makefile, the final compilation is done using
the -lm
flag, which links the math library (I don't know
why C requires a special flag).
The math library also defines e with the constant M_E
.