Due: February 3rd
This assignment continues from PA03. In
PA03, the function to be integrated is not part of the arguments to integrate
.
This assignment makes the function part of the argument. As a result,
the integration function is more flexible because everything needed
is passed as arguments.
Learning Goals
You will learn how to:
- Create a type for functions that have the same types of arguments and return value
- Make a structure with an attribute as a function pointer
- Write a function that takes a pointer to this structure (modified from PA03)
- Write Makefile to test
- Write a main function that test integration function
- Write a function that integrates multiple functions
- Read data from a file and write output to another file
- Detect errors and return proper code (
true
,false
,EXIT_SUCCESS
, orEXIT_FAILURE
)
Please be aware that this you need to write a lot more for this assignment. Please start early. You are encouraged to study PA01 - PA03 thoroughly before starting this assignment. This assignment is designed with the assumption that you are familiar with the details in PA01 - PA03.
Background
Function Pointers
Every function in a C program refers to a specific address. You have seen that in PA02. It is possible to create pointers to store functions' starting addresses, and then call functions by using those addresses, instead of the function name directly.
In C, a function pointer is declared by this syntax:
typedef type (* typename) (arguments)
For example:
typedef int (* funcptr)(int, int);
creates a type for pointers to functions that take two integers as input arguments and return one integer.
Here is an example of creating and using function pointers:
//create a function pointer type called "myfuncptr"
//myfuncptr functions take one int as an argument and return an int
typedef int (* myfuncptr)(int);
myfuncptr f = foo; //f now refers to the function foo
int x = f(3); //equivalent to: int x = foo(3)
In this assignment, you need to create a type called funcptr
(you must
use this name) for the functions you want to integrate. A function of type funcptr
should take as argument one double
, and return a double
.
With this type, it is possible to create an array of five functions called
func1
, func2
, ..., func5
.
The program can go through these five functions in the same way as going through normal array elements.
Function Pointer as Attribute/Field
An important concept in developing high-quality software is making a
function's behavior controlled by the argument(s) and nothing else. In
PA03, the function to be integrated was not in the argument -- we assumed it was a function called func
. This can
easily create confusion. Similarly, global variables and static
variables can create confusion because they can make functions behave
in ways that are not completely controlled by the input arguments.
Thus, you should avoid using global and static variables.
After creating the type for function pointers, it is possible to make a function pointer an attribute of a structure. Now, everything needed to run the integration function is passed into the integration function as the argument.
Reading and Writing Files
This assignment asks you to read and write data from files. You can see examples of doing this in earlier assignments (PA01 and PA03)
The key to working with files is to understand the basic file manipulation API (an "Application Program Interface" -- the functions that provide certain behavior):
FILE *
: All files are manipulated through pointers toFILE
structures. We will call the pointer to aFILE
structure a file "handle."FILE * inf;
declares a file handle calledinf
.-
FILE * fopen(const char * path, const char * mode)
: Open a file whose file name is stored in the character arraypath
. The character arraymode
indicates how you want to access the file:"r"
: open the file for reading, starting at the beginning of the file."r+"
: open the file for reading and writing, starting at the beginning of the file"w"
: open the file for writing; this deletes any content already in the file."w+"
: open the file for reading and writing; this deletes any content already in the file."a"
: open the file for appending, starting at the end of the current contents of the file."a+"
: open the file for reading and appending: reads start at the beginning of the file, writes append to the end of the file.
inf = fopen("test1", "r")
will open the file named "test" for reading, and return the necessary file info to theFILE
pointerinf
. Ifinf
isNULL
after callingfopen
, then the operation failed. You can find out more by typingman fopen
on the ecegrid machines. int fscanf(FILE * file, const char * format, ...)
: This works just likescanf
, but reads from the file pointerfile
.fscanf(inf, "%lf\n", &d)
reads a double (%lf
) from the input file and stores the result in d. You can find out more by typingman fscanf
on the ecegrid machines.int fprintf(FILE * file, const char * format, ...)
: This works just likeprintf
but writes to the file pointerfile
. Note that forfprintf
to work, the file you pass should have been opened in a mode that allows writing. You can find out more by typingman fprintf
on the ecegrid machines.int fclose(FILE * file)
: Close the file. You should always close files when you are done using them.
What do you need to do?
Your job is to write an integration function using the same integration method as PA03 (you can even re-use your code from there). Unlike the integration function in the previous assignment, however, your integration method will need to accept a new kind of structure, one that contains a function pointer pointing to the function you want to integrate. This will require both defining the function pointer type you need, as well as a new structure that contains all the necessary information to perform an integration.
Files You Need to Modify
Unlike in previous assignments, this assignment asks you to modify many files. The exact details of what each file should contain are provided in the initial files for the assignment (pay careful attention to the comments in each file!), but briefly, here are the files you need to modify, and what you need to do:
pa04.h
: You will define a the function pointer type and a new structure that contains all the components necessary to perform an integration, including a pointer to the function to be integrated, as well as a field to store the result of the integration. Please follow the naming convention mentioned in pa04.h strictly. While calculating partial credit, if your fucntion names and variable names do not match with the instructor's key, your program will fail to compile and you will be given zero credit.answer04.c
: This file contains your integration functions:integrate
, which takes the structure you defined inpa04.h
and uses that information to perform a numerical integration, andrunIntegrate
, which takes the name of an input file and an output file as arguments and:- Reads the upper limit, lower limit, and number of intervals from the input file.
- Uses
integrate
to numerically integrate five functions (provided for you, calledfunc1
,func2
,func3
,func4
, andfunc5
). - Writes the results of each integration out to the output file
pa04.c
: This file contains yourmain
function, which gets the names of the input and output files from the command line (argv[1]
andargv[2]
) and callsrunIntegrate
.Makefile
: You will need to write your own Makefile to build and test your program (the template we provide only defines one "recipe":clean
). See below.
Improve Makefile
By now, you have seen at least three Makefile in PA01, PA02, and PA03. Please read them carefully and understand what they do.
You should have discovered that every Makefile so far has this pattern
file.o: file.c
$(GCC) $(CFLAGS) -c file.c
and file is replaced by specific file names.
If five object files are needed, this pattern has written five times. This is tedious and error prone (do you remember DRY vs. WET code)?
Fortunately, there is a simple way to write this as a rule.
c.o:
$(GCC) $(CFLAGS) -c $*.c
This means whenever a .o
file is needed, Makefile will look for the
corresponding .c
file and use gcc
(with the flags) to compile it. This
can dramatically reduce the effort writing Makefile
Makefile can do much more than creating executable files. You can run test cases using Makefile. You have seen this in PA01 and PA03.
In PA01, testsome
and testall
show how to run multiple test cases.
In PA03, pa03-test1 and pa03-test2 also show how to run multiple test cases.
Your Makefile for PA04 needs to run the five test cases stored in testdir,
using a target called testall
. Remember to have your testall
recipe
compile your files if needed! Look at previous Makefiles we have given
you to see how to add this dependency. The name of the executable should be 'pa04'.
This is essential as we will use your executable file to test some other
testcases as well. Also, when you run the five test cases using 'testall'
target, the output files generated for each input test file should be named
output_<test file name>
e.g. for file test1, ouptput file name should be output_test1.
Don't forget to define the flags RUN_INTEGRATE
and TEST_INTEGRATE
to
make sure that the code you write in answer04.c gets compiled!
main function
This is the first assignment that requires a complete main
function.
Partial Credits
It is very important that you follow the instructions precisely (including functions' names and variables' names). These names allow the grading programs to replace some of incorrect parts by correct parts (written by the teaching staff) and give you some points.