|
|
Module 8 - Functions
This module makes use of files found in the tar file module8.tar.
We briefly introduced the concept of functions in the Module 2.2 Extend section; functions are pieces of code that we give a name and pass data to in exactly the same way we implement the main program. Functions are used in order to:
- compartmentalize our C code into logical, easy-to-manage chunks.
- write a set of calculations once, and run that set numerous times over the course of a C program.
We define functions in the following way:
return-dtype func-name (type1 arg1, type2 arg2, ..., typen argn) {
statement-1
statement-2
.
.
.
statement-n
return(var);
}
Where:
- return-dtype is the data type of the whatever value is returned by the function (For functions that don't return a value to the calling program, this can be void).
- func-name is the name of the function; it is the name used to call the function from within the main program or other functions.
- The typen argn pairs define the arguments (and their data types) that are passed to the function by the calling program.
- var is a variable or calculation that is of the same type as return-dtype. It is the value that is returned to the program or function that called func-name.
- Often, variables in the calling program are set the the value returned by the function, using the program statement:
prog-var = func-name(pass-var);
Notice that:
- the statements, calculations that make up the function, are enclosed within curly braces { and } , just like the main program.
- functions that do not have a return-dtype of void will include, somewhere in the function, at least 1 return statement (if a conditional statement branches the code, then more then 1 return statement may be used).
In order to use a function within the main program, one simply uses the function name, and passes in the required arguments, making sure that the data types of those passed arguments match those expected by the function.
Let's see how programs we've worked with before can be adapted to use functions and pointers; the bisect program from Module 2.2 will incorporate the notion of functions, the matrix-vector product program will be adapted to use both functions and pointers, as will our Gaussian reduction program from Module 5.2.
The Bisection Method With Functions
In Module 2.2, we covered the bisection method for finding the root of an algebraic equation. Briefly the bisection method requires 2 x axis location guesses; it bisects the 2 guesses, and checks for which half of the bisection the functions changes sign on. It loops over this bisection method until it locates the where the function crosses the X axis. You can find out more about the Bisection Method at the Wikipedia web site and this MIT tutorial.
We use this method again, only this time we encapsulate a portion of the program in a separate function called from within the main program.
It's important to note that what follows is not representative of how programs are divided up into subfunctions, but is intended only as an aid in showing the grammar and syntax for defining and calling functions in programs.
Take a look at bisect_fn.c, (You can click on the preceeding link for a formatted html version, or access the actual code after decompressing module8.tar, via the UNIX shell command tar cvf module8.tar, and finding bisect_fn.c inside the resulting module8 directory). Let's step through this program:
- The code we're most interested in is defined in lines 18 thru 22. Here is where we define the function called fxn:
- In line 18, fxn is preceeded by float, which means that the function fxn returns a value of type float.
- In the parentheses following the function name fxn, we define the type of the value passed to fxn, and what name we'll refer to it as during the run of this function. In this case, (float x) means that we pass a value of type float to fxn, and we call it x during the course of fxn's execution.
- The open curly brace at the end of line 18 indicates that the statements that define the calculations of fxn follow.
- We define one variable, y, local to fxn, in line 19. It is the value we'll return back to the main program, and as a result must match the type we've defined fxn as returning.
- The only line in fxn that performs a calculation is line 20; it sets y to the cube of the passed value x. Once again, as a reminder, this is atypical of most functions, which are usually moderately to significantly complex and incorporate a number of calculations, and which may return different values, based on internal conditionals and computations.
- Line 21 performs the return of the preceeding line's calculation.
- The code that follows is mostly standard fare; briefly:
- We include libraries in lines 5 thru 7, math for using abs to find the absolute value of a number, stdio for input/output, and stdlib to exit the program if certain conditions aren't met.
- After the definition of fxn in lines 18 to 22, we begin the main program. Lines 25 to 28 define variables used.
- We get the left and right guesses from the user in lines 30 thru 33, and we use an if-then statement in line 43 to 46 to confirm that one guess is negative, and the other positive; if not, then we exit the program after printing out a message to the user.
- Lines 51 thru 68 encode the loop of the bisection method. It's a do loop, so it iterates at least once. In it, we:
- Find the point that bisects the 2 x values (line 52).
- Find the y values for the function (The cube) at the left, right and bisection points (Lines 54 to 56). We do this by calling our defined fxn function, which finds and returns the cube of the passed value.
- 58 thru 63 implements an if-then-else conditional to find which side of the bisection the function changes signs. We reset either xleft or xright to the bisection.
- In lines 65 to 67, we print out the results for the current iteration, and increment the counter.
- Finally, the while portion at the end of the do loop checks to see if the absolute value of bisection falls within the tolerance input by the user.
- Once we terminate the do loop, we print out the final results in line 70 and end the program.
So the fxn function in this case is simply the function that we search for the root for via the bisection method. Still, it serves as easy implmentation of functions so we can easily see the correct syntax and structure.
Using Functions and Pointers for Matrix-Vector Multiplication
Next, we see functions implemented in a more sophisticated way when multiplying a matrix by a vector in the program mvp.c.
mvp.c in this module differs from mvp.c from Module 5.1 in that functions are used to:
- check to make sure that memory has been allocated to pointers, and the file pointers point to valid files (the function get_data)
- perform the actual matrix-vector multiplication (the function matvec)
In addition, instead of employing the C notion of arrays to store the matrix and vector and perform the multiplication, we use pointers. This requires more sophisticated computations to calculate the pointer location for assigning values, but it provides us with greater flexibility in dynamically declaring and allocating memory.
Let's step through some of the code (Particularly, the code that's been changed to use functions and pointers)
- We include libraries in lines 1 to 3; for the Mac's version of UNIX, malloc() and sizeof() are defined in stdlib.h. For other UNIX boxes, they may be defined in the library malloc.h. If you get compile-time errors complaining about unknown functions for malloc and sizeof, try including malloc.h.
- Lines 9 thru 22 implement a function called get-data. This function checks to make sure that:
- Lines 25 to 50 implement the function matvec, the actual matrix-vector multiplication takes place in this function. This function is passed
- the matrix (mat),
- the vector to multiply it by (rhs, or right-hand side),
- the vector to place the results in (lhs, for left-hand side),
- and n, the size of the matrix and vector.
- matvec spends the first half confirming that the passed pointers point to allocated memory.
- The second half of matvec is the actual matrix-vector multiplication. Here is where we give up the ease of accessing elements within a C array with the combersome calculations for pointers. In general, to find the pointer equivalent for an n x n matrix, our calculation is:
Original-ptr + (previous-rows * elements-per-row) + current-element
Specifically, to find element Mat[j][i] (or, the ith element in row j) for an n x n matrix, the pointer computation is,
Mat + (j * n) + i
- This is the computation for the pointer location in Line 45 of matvec. (Keep in mind that we're not talking about the matrix-vector multiplication computation; this has been covered in Module 5.1. We're simply talking about the computation to increment the pointer to find the correct data location in memory).
- The main program is defined in lines 53 to 131, briefly:
- variables are declared in lines 54 to 64.
- we open the files containing the matrix and vector values for reading in lines 66 to 67.
- In line 70, we allocate memory to store 100 characters. This string will be the name of the file to place results in, and is opened for writing in line 74.
- Our first scan of the matrix and vector files is for the matrix and vector size, in lines 76 and 77.
- In 79 thru 85, we use an if-then-else statement confirm that the sizes match, if so, we set n to their size. If not, we print out a size mismatch error message and exit the program.
- The if-then statement of lines 87 to 90 sets a limit of a 10 x 10 matrix for this program.
- Now that we have determined the common size n, we use it to allocate sufficient memory for the matrix, right-hand side vector (to multiply the matrix by), and left-hand side vector (to store the results in) in lines 93 to 95.
- Line 97 makes the first call to the function get-data (This is probably a poor choice for the function name; it doesn't get data, but just confirms that the matrix pointer and file pointers are declared and allocated memory. If not, a negative number is returned from get-data. Otherwise, get-data returns 0.
- The value returned from get-data is assigned to the integer variable check, and it's used in the if-then statement in lines 98 to 101 determine whether to continue with the program or exit.
- We perform this test for each memory location in the matrix pointer in the for loop implemented in lines 103 to 108.
- Lines 114 to 117 initialize all values in the result vector lhs to 0.
- Line 119 calls matvec, which performs the matrix-vector multiplication as mentioned above. matvec returns 0 if the multiplication was successful, or a negative number if there are problems with any of the pointers passed to it.
- Lines 125 to 128 implement the for loop that writes the result vector lhs values to the file with the name input by the user.
- We close the result file in line 130 and finally exit the program.
So, to summarize, the interesting aspects of this program are:
- The definitions of the functions matvec for the matrix-vector multiplication, and get-data to check for valid pointers.
- The use of pointers to deal with matrix and vector elements instead of the C array concept. This requires special calculations to access the proper array elements, but provides for greater flexibility in dynaimcally allocating space during run-time.
Continue to the Apply Section ->
|
|