...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.
The C++ programming language is like C but with classes and other object-oriented features added on. The first C++ compilers simply translated C++ programs to C, which was then fed into a C compiler. Also during the early days of Java, someone bothered to write a Java-to-C compiler (rather than compiling Java to bytecode/classfiles).
How is a translation like this possible, since C lacks so many of the core elements of a language like Java? For one thing, C's programming concepts aren't much different from how the computer (at the hardware level) actually works--- and obviously Java programs can be run on a real computer. Besides, the Church-Turing thesis tells us that all models of computing are equivalent to each other.
As we began to imagine in class yesterday, function pointers help a lot for doing something in C that looks like polymorphism. (In fact, the use of functions pointers to determine how a sorting algorithm orders elements, like we saw yesterday, is polymorphism, just not subtype polymorphism.) In this lab, we will fill out the details.
The problem/example we're going to work on is something you may have seen in Programming I as an early example of polymorphism. Suppose we have several types that represent different kinds of mathematical functions polynomials, rationals, exponentials, step functions, and the like. Suppose they all have certain operations: we want to be able to evaluate the function at an x value, find a derivative, evaluate a definite integral, etc.
Here's the interface that defines the supertype:
/** * Function.java * * This provides a common interface and super type for all * kinds of functions we wish to model. * * @author Thomas VanDrunen * Wheaton College, CS 241, Fall 2005 * In-class example * Oct 24, 2005 */ public interface Function { /** * Evaluate this function for a given value of x. * @param x The value at which to evaluate the polynomial. * @return The value of this polynomial at x */ public double evaluate(double x); /** * Find the derivative of this function. * @return The function that is the derivative of this one */ public Function derivative(); /** * Calculate the definite integral of this function, given upper and lower * bounds. * @param lower The x value at which the region begins; the lower bound * @param upper The x value at which the region ends; the upper bound * @return The value of the definite integral */ public double integrate(double lower, double upper); } /** * Polynomial.java * * Class to model polynomials ... */ import java.util.Scanner; public class Polynomial implements Function{ /** * The coefficients of the terms in this polynomial * (the item at position i is the coefficient of x^i) */ private double[] coefficients; /** * Constructor. Set the (instance variable) coefficient * array to a copy of the (formal parameter) one given. * @param coeffs An array containing the coefficients of * this polynomial. */ public Polynomial(double[] coeffs) { coefficients = new double[coeffs.length]; for (int i = 0; i < coefficients.length; i++) coefficients[i] = coeffs[i]; } /** * Constructor. Query the user for the coefficients. */ public Polynomial() { Scanner keyboard = new Scanner(System.in); // for user input System.out.print("Please enter the degree"); int degree = keyboard.nextInt(); // the polynomial's degree keyboard.nextLine(); coefficients = new double[degree + 1]; for(int i = 0; i < coefficients.length; i++) { System.out.print("Please enter the " + i + "th degreed coefficient--> "); coefficients[i] = keyboard.nextDouble(); keyboard.nextLine(); } } /** * Evaluate this polynomial at the given x-value. * Loop through the coefficients, keeping a running power of * x and a running result. * @param x The value at which we're evaluating this polynomial. * @return The value of the polynomial at the given x value. */ public double evaluate(double x) { double evaluation = coefficients[0]; // the value (result) // (we initialize it to the constant term) double xpow = x; // "running power" of x-- x^i on the ith iteration. for (int i = 1; i < coefficients.length; i++) { evaluation += xpow * coefficients[i]; xpow *= x; } return evaluation; } /** * Calculate the derivative of this polynomial. * Make a new array (one unit smaller), each element * equal to the equivalent element in the original array * one position over, times the other position's exponent. * @return The polynomial that is the derivative of this one. */ public Function derivative() { // the new coefficients double[] diffCoef = new double[coefficients.length - 1]; for (int i = 0; i < diffCoef.length; i++) diffCoef[i] = coefficients[i+1] * (i + 1); return new Polynomial(diffCoef); } /** * Calculate an antiderivative (indefinite integral) of * this polynomial. Compare with the algorithm for * differentiating. * @return A polynomial that is an antiderivative of this one */ private Polynomial antidifferentiate() { // the new coefficients double[] antidiffCoef = new double[coefficients.length + 1]; for (int i = 1; i < antidiffCoef.length; i++) antidiffCoef[i] = coefficients[i-1] / i; return new Polynomial(antidiffCoef); } /** * Compute a definite integral of this polynomial. * Find the indefinite integral, evaluate at a and b, and * subtract. * @param a The lower bound * @param b The upper bound * @return The definite integral over the given bounds. */ public double integrate(double a, double b) { Polynomial antiDerivative = antidifferentiate(); return antiDerivative.evaluate(b) - antiDerivative.evaluate(a); } /** * Compute a string represenation of this polynomial. * @return A string displaying this polynomial. */ public String toString() { String toReturn = "" + coefficients[0]; for (int i = 1; i < coefficients.length; i++) toReturn += " + " + coefficients[i] + "x^" + i; return toReturn; } } /** * Exp.java * * Class to model functions in the form f(x) = c e^x. * * @author Thomas VanDrunen * Wheaton College, CS 241, Fall 2005 * In-class example * Oct 24, 2005 */ public class Exp implements Function { /** * The linear coefficient of this exponential expression */ private double coefficient; /** * Constructor. * @param coefficient The coefficient. */ public Exp(double coefficient) { this.coefficient = coefficient; } /** * Evaluate this function for a given value of x. * Raise e to the given power and multiply by the coefficient. * @param x The value at which to evaluate the polynomial. * @return The value of this polynomial at x */ public double evaluate(double x) { return coefficient * Math.pow(Math.E, x); } /** * Find the derivative of this function. * This is its own derivative. * @return The function that is the derivative of this one */ public Function derivative() { return this; } /** * Calculate the definite integral of this function, given upper and lower * bounds. * Since this is its own antiderivative, simply evaluate at the upper and lower * bounds and take the difference. * @param lower The x value at which the region begins; the lower bound * @param upper The x value at which the region ends; the upper bound * @return The value of the definite integral */ public double integrate(double lower, double upper) { return evaluate(upper) - evaluate(lower); } } /** * Step.java * * This class models a step function, * providing support for evaluation, derivation, and integration. * * @author Thomas VanDrunen * Wheaton College, CS 241, Fall 2005 * In-class example * Oct 24, 2005 */ import java.util.*; public class Step implements Function { /** * The value of x at which the step should occur. */ private double stepPoint; /** * The value of the function after the step. */ private double stepLevel; /** * Constructor based on a given step point and step level. * @param stepPoint The x value at which the step occurs * @param stepLevel The value of the function after the step */ public Step(double stepPoint, double stepLevel) { this.stepPoint = stepPoint; this.stepLevel = stepLevel; } public double evaluate(double x) { if (x < stepPoint) return 0; else return stepLevel; } public Function derivative() { double[] zero = { 0.0 }; return new Polynomial(zero); } public double integrate(double lower, double upper) { if (lower == upper) return 0; else if (lower > upper) return - integrate(upper, lower); else if (upper < stepPoint) return 0; else if (lower > stepPoint) return stepLevel * (upper - lower); else return stepLevel * (upper - stepPoint); } }
In lab we will explore how to set up a system of C structs together with function pointers to mimic the effect of the Java classes above.
Copy the following files from course public directory.
cp -r ~tvandrun/Public/cs245/lab14/* .
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, as we saw with the "book" example in class.
In the present 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;
differentiate_p()
and
integrate_p()
also are done for you.
Your task
Your task is to finish this file by filling in the functions
to make a new polynomial object and destroy an object.
Although the destroy_p()
function appears first
in the file, I recommend you write the newPolynomial()
function first, because then you can think about the destroy_p()
function in terms of undoing everything you do in newPolynomial()
.
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.
destroy_p()
Deallocate the coefficient array,
the polynomial object, and the function object.
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 look at the types for step and exponential functions in their respective header files, and finish the functions in their implementation files. Specifically, for step functions...
integrate_s()
is done for you.
evaluate_s()
is the only evaluate
function you'll need to write today; it isn't too bad, and if
you run into trouble you can look at the equivalent Java code.
differentiate_s()
should return a new
polynomial always equal to zero (ie, one coefficient, equal to 0, for
the constant term).
newStep()
and destroy_s()
will be similar to the analogous functions for polynomial
,
but specific to the components of the step
struct.
And for exponential functions...
evaluate_e()
and integrate_e()
are done for you.
differentiate_e()
should return a new,
identical exponential.
(The Java version just returns this
.
Why should you not do that in the C version?)
newExponential()
and destroy_e()
will be similar to the analogous functions for polynomial
,
but specific to the components of the step
struct.