Functions
From Programming In C
| Table of contents |
Introduction
Functions are a useful way of referring to blocks of code, allowing the code to be called many times. Functions take some input information (the input parameters), do something with that information and then return a result (the return value).
Predefined functions, such as sin() and sqrt()have already been met in earlier sessions. It is possible to define your own functions in C. Being able to do this allows powerful programs to be developed in a modular manner that makes the development and debugging stages significantly easier than would otherwise be the case.
Defining a function
A function is defined as follows:
type function_name(parameter_list)
{
// code block here
}
type
Functions return a value. The type of this value (int, float, char etc) has to be specified. All of the standard C types met so far may be used as a function type. Sometimes a return value is not required, for example if the function prints an error message and then returns, in which case a return type of void should be used.
function_name
This is a label that is used to refer to the function from other code blocks. The rules for valid names are the same as those for variables.
parameter_list
Values are passed to a function via its parameter list. This a comma separated list of the values that the function takes. Each item in the list has a type specification. The parameter list can be blank, i.e. the function takes no parameters, in this case the keyword void should be used instead of a list (as is the case with "int main(void)". The names used for the parameters can be different from those used in the block of code that calls the function.
code block
The code block contains the operations that constitute the function. The parameters declared in the parameter list can be used as standard variables within this block. Temporary variables may also be declared for use within the block. Unless the function has a return type of void then the function needs to return a value of the correct type. This is done via the return command (see below).
A simple example
The code example below shows a simple example of defining and using a function.
#include <stdio.h>
#include <stdlib.h>
float product(float x, float y) // function definition - see note 2
{
float result; // a local variable - see note 3
result = x*y;
return result; // returning the value - see note 4
}
int main(void)
{
float a;
float b;
float answer;
// prompt user for values
printf("Please input two values to be multiplied:");
scanf("%f %f",&a,&b);
// call the function product
answer=product(a,b); // calling the function - see note 5
printf("The product of %f and %f is %f\n",a,b,answer);
system("pause");
return 0;
}
Notes
- This example is rather too simple to be useful, but does illustrate how functions are defined and used.
- The function product is defined here. It has two input parameters and returns a value of type float.
- result is a variable declared within the product code block. It can only be used in this block.
- The function returns the value stored in result (which is of type float).
- The values of a and b are passed to the function product, the result is stored in the variable answer. Notice that different parameter names are used in the function and main block.
Declaring Versus Defining Functions
In the example above the code for the function product appeared before the main block. This is fine for such a simple program, but in a realistic situation where there may be many tens if not hundreds of functions a program would rapidly become too difficult to follow. In such a situation it would be much better if the main() block comes first, followed by the functions. This is achieved using function prototypes. These are essentially the first line of a function definition terminated with a semicolon. For the function product() the prototype would be:
float product (float x, float y);
A function prototype tells the compiler about the function parameters and the return type - it is said to declare the function. The compiler uses this information to leave placeholders in the compiled code which is then filled in once the code that defines the code has been compiled. This definition code could come after the main function block or even be in a separate file. A function prototype must appear before the code block in which the function is used.
The example above can be rewritten as:
#include <stdio.h>
#include <stdlib.h>
float product(float x, float y); // see note 1.
int main(void)
{
float a;
float b;
float answer;
// prompt user for values
printf("Please input two values to be multiplied:");
scanf("%f %f",&a,&b);
// call the function product
answer=product(a,b);
printf("The product of %f and %f is %f\n",a,b,answer);
system("pause");
return 0;
}
float product(float x, float y) // see note 2
{
float result; // Note: this is a local variable
result = x*y;
return result; // returns the value to the function call
}
Notes
- The function is declared here.
- The function is defined here.
- The function type and parameter list types must be the same in the declaration and definitions.
In programs that use many (>5 say) functions the prototypes are generally stored in a separate header file and this file is then added to the #include list. Includes of a file written by the programmer use speech marks instead of the angular brackets ,<>, around the file names, e.g.
#include "declarations.h"
where declarations.h is a file that contains a list of function prototypes. The angular brackets tell the preprocessor that the file is located in one of the compiler's standard directories (folders).
An introduction to pointers
A pointer is a special type of variable that holds the address in the computer's memory of some value. For each data type T, in C there is a pointer-to-T type, T*. Some examples of pointer declarations are:
int* j; // j is a pointer to int float* z; // z is a pointer to float
Note it is valid to rewrite the above declarations as:
int *j; // j is a pointer to int float *z; // z is a pointer to float
i.e. the asterisk is next to the pointer variable name rather than the type. It is left to the programmer to decide which form to use.
In order to see how pointers are used, two new operators have to be introduced:
The address-of operator &
if x is some variable then the address of x within the memory of the computer is given by &x
The dereferencing operator *
If z is the pointer declared above then *z gives the value stored at the memory location that z holds. This operator is sometimes referred to as the indirection operator. The use of the * symbol in pointer declaration and as the dereferencing operator is slightly confusing at first, but with practice you will get used to it.
The use of these operators is shown below:
int j = 32; // an int variable
printf("j=%d\n\n",j);
int* Pj = &j; // Pj is a pointer to int and is assigned the address of j.
printf("Pj points to the address:%p which holds the value:%d\n\n",Pj,*Pj);
int new_var = *Pj; // the value pointed to by Pj is assigned to new_var
printf("new_var=%d\n",new_var);
Notice the use of the %p conversion code in the printf() call. The output from this code is:
j=32 Pj points to the address:0xbffffa00 which holds the value:32 new_var=32
Note: Do not expect to get the same address value on different machines, or on the same machine at different times. It is up to the operating system of the computer where to store values.
Pointers are very powerful and have a wide variety of uses in C programming. Some of these uses will be discussed later in Topic 7 and Topic 8.
Using functions to return multiple values
The functions met so far have returned a single value. Very often one needs to create a function that returns more than one value. Consider the code shown in example 5.3 below. What would you expect the output from this program to be?
#include <stdio.h>
#include <stdlib.h>
#include<math.h>
void swap( int x, int y) // Define function to swap two integers
{
int temporary = x; // store value
x = y;
y = temporary; // values now swapped
// print new values to show that swap has worked
printf("swap: x=%d, y=%d\n",x,y);
return;
}
int main(void)
{
// some code to use the function swap
int a=1;
int b=2;
// print out a,b before swapping
printf("before swap: a=%d, b=%d\n",a,b);
// call swap function
swap(a,b);
// print out new values
printf("after swap: a=%d, b=%d\n",a,b);
system("pause");
return 0;
}
You might expect that the values held by the two integers a and b would end up swapped round. The actual output from this code is shown below:
before swap: a=1, b=2 swap: x=2, y=1 after swap: a=1, b=2
This shows that the values were indeed swapped by the function, but on return from the function the values appear to have been swapped back!
To understand why this happens you have to understand that C passes the values of the variables to the function. This means that the original variables will remain untouched by the swap() function. This system is known as pass-by-value (or sometimes call-by-value). It may seem rather inconvenient in situations like this, but it does allow one to use expressions such as log(x) as a parameter and also helps to make sure that variables are not changed unless the programmer wants them to be.
Pointers provide a reasonably straightforward solution to the problem of changing the values of variables in another function. The address-of operator can be used to pass the location of a variable in memory to a pointer in a function and the dereferencing operator can be used to change the value stored at that memory location. To illustrate how this works, the previous example has been rewritten to use pointers:
#include <stdio.h>
#include <stdlib.h>
#include<math.h>
// Define function to swap two integers
void swap( int* x, int* y) // x and y are now pointers
{
int temporary = *x; // store value
*x = *y;
*y = temporary; // values now swapped
// print new values to show that swap has worked
printf("swap: x=%d, y=%d\n",*x,*y);
return;
}
int main(void)
{
// some code to use the function swap
int a=1;
int b=2;
// print out a,b before swapping
printf("before swap: a=%d, b=%d\n",a,b);
// call swap function
swap( &a, &b); // pass address of a and of b
// print out new values
printf("after swap: a=%d, b=%d\n",a,b);
system("pause");
return 0;
}
This code generates the following output:
before swap: a=1, b=2 swap: x=2, y=1 after swap: a=2, b=1
This time the values have been swapped. Schemes such as this allow functions to return multiple values by changing the values of one or more of the parameters. This is known as pass-by-reference. You have already made use of this. Recall the use of the scanf() function in Topic 2 which used the address-of operator (&) in order for the inputted values to stored in variables.
An aside: not all functions are (always) good
In Topic 2 the math.h library was briefly introduced. The functions declared in this library are pretty much essential for computational physics. However these functions were programmed to work with no consideration for computational efficiency and accuracy. Many of these functions should be used with caution. For instance, often a low, integer, power of a variable or number is required (e.g. a square or a cube of a number). The pow() function assumes that, in general, it will be called with a non-integer power, and, therefore, implements a full-blown (and computationally very expensive) series expansion. Clearly, it is not computationally efficient to use this function to square or cube a quantity. Worse, because of the finite size of the memory blocks used by computers the series expansion method is often not as accurate. So to get around this put in the explicit multiplication of your variable, i.e. instead of:
y = pow(x,3);
you should use:
y = x*x*x;
If you are ever involved in any serious numerical computational programming (such as those of you planning to do the PHY207 course next semester) then you should probably always look for alternatives to the functions provided by math.h. A good, free, alternative source is the GNU Scientific Library (http://www.gnu.org/software/gsl/).
Recursion
In C it is possible for a function to call itself, a process known as recusion. [Note: Not all computer languages allow recursion. FORTRAN77, a language widely used for numerical applications, is one of those that do not.] A function that calls itself is said to be recursive. A recursive function requires a call to itself and a test to see if some stop condition has been met bringing to an end the recursion.
Why would you want to use recursion? Well in some circumstances it can lead to rather elegant programming solutions. Consider for example the calculation of a factorial. You might initially think of using a loop to do this:
unsigned int factorial (unsigned int n)
{
int i;
int m=1;
if (n>=1)
for (i=1;i<=n;i++) m*=i;
return m;
}
Note the use of unsigned int in order to ensure that only positive integers are used.
Another way of calculating a factorial comes out of realizing that:
i.e. n factorial is n times (n-1) factorial.
This can be implemented in C as:
unsigned int factorial (unsigned int n)
{
if (n<=1) return 1; // - see note 1
return (n*factorial(n-1)); // - see note 2
}
Notes
- This line terminates the recursion process.
- This is the recursive call to factorial
- This function has two return statements. This is allowed in C
Recursion is not just for moderately obscure mathematical functions.It turns out that many problems when considered properly can be written in terms of recursion. The reading/writing of lists from/to a file can readily be implemented in a recursive manner. Another example is the Towers of Hanoi problem that was very popular as a toy in Victorian times (see Kelley & Pohl section 5.15).
Further Reading
A Book on C Kelley and Pohl
- pages 197-244 Chapter 5: Functions,
- pages 42, 46 (pointers),
