next up previous
Next: Pointers Up: Scientific programming in C Previous: Control statements

Functions

We have seen that C supports the use of predefined library functions which are used to carry out a large number of commonly occurring tasks. However, C also allows programmers to define their own functions. The use of programmer-defined functions permits a large program to be broken down into a number of smaller, self-contained units. In other words, a C program can be modularized via the sensible use of programmer-defined functions. In general, modular programs are far easier to write and debug than monolithic programs. Furthermore, proper modularization allows other people to grasp the logical structure of a program with far greater ease than would otherwise be the case.

A function is a self-contained program segment that carries out some specific, well-defined task. Every C program consists of one or more functions. One of these functions must be called main. Execution of the program always begins by carrying out the instructions contained in main. Note that if a program contains multiple functions then their definitions may appear in any order. The same function can be accessed from several different places within a program. Once the function has carried out its intended action, control is returned to the point from which the function was accessed. Generally speaking, a function processes information passed to it from the calling portion of the program, and returns a single value. Some functions, however, accept information but do not return anything.

A function definition has two principal components: the first line (including the argument declarations), and the so-called body of the function.

The first line of a function takes the general form

data-type  name(type 1  arg 1,  type 2  arg 2,  ...,  type n  arg n)
where data-type represents the data type of the item that is returned by the function, name represents the name of the function, and type 1, type 2, ..., type n represent the data types of the arguments arg 1, arg 2, ..., arg n. The allowable data types for a function are:
int       for a function which returns an integer value
double    for a function which returns an floating-point value
void      for a function which does not return any value
The allowable data types for a function's arguments are int and double. Note that the identifiers used to reference the arguments of a function are local, in the sense that they are not recognized outside of the function. Thus, the argument names in a function definition need not be the same as those used in the segments of the program from which the function was called. However, the corresponding data types of the arguments must always match.

The body of a function is a compound statement that defines the action to be taken by the function. Like a regular compound statement, the body can contain expression statements, control statements, other compound statements, etc. The body can even access other functions. In fact, it can even access itself--this process is known as recursion. In addition, however, the body must include one or more return statements in order to return a value to the calling portion of the program.

A return statement causes the program logic to return to the point in the program from which the function was accessed. The general form of a return statement is:

return  expression;
This statement causes the value of expression to be returned to the calling part of the program. Of course, the data type of expression should match the declared data type of the function. For a void function, which does not return any value, the appropriate return statement is simply:
return;
A maximum of one expression can be included in a return statement. Thus, a function can return a maximum of one value to the calling part of the program. However, a function definition can include multiple return statements, each containing a different expression, which are conditionally executed, depending on the program logic.

Note that, by convention, the main function is of type int and returns the integer value 0 to the operating system, indicating the error-free termination of the program. In its simplest form, the main function possesses no arguments. The library function call exit(1), employed in previous example programs, causes the execution of a program to abort, returning the integer value 1 to the operating system, which (by convention) indicates that the program terminated with an error status.

The program segment listed below shows how the previous program factorial.c can be converted into a function factorial(n) which returns the factorial (in the form of a floating-point number) of the non-negative integer n:

double factorial(int n) 
{
  /* 
     Function to evaluate factorial (in floating-point form)
     of non-negative integer n.
  */

  int count;
  double fact = 1.;

  /* Abort if n is negative integer */
  if (n < 0) 
   {
    printf("\nError: factorial of negative integer not defined\n");
    exit(1);
   }

  /* Calculate factorial */
  for (count = n; count > 0; --count) fact *= (double) count;

  /* Return value of factorial */
  return fact;      
}

A function can be accessed, or called, by specifying its name, followed by a list of arguments enclosed in parentheses and separated by commas. If the function call does not require any arguments then an empty pair of parentheses must follow the name of the function. The function call may be part of a simple expression, such as an assignment statement, or it may be one of the operands within a more complex expression. The arguments appearing in a function call may be expressed as constants, single variables, or more complex expressions. However, both the number and the types of the arguments must match those in the function definition.

The program listed below (printfact.c) uses the function factorial(), described above, to print out the factorials of all the integers between 0 and 20:

/* printfact.c */
/*
  Program to print factorials of all integers
  between 0 and 20
*/

#include <stdio.h>
#include <stdlib.h>

//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

double factorial(int n) 
{
  /* 
     Function to evaluate factorial (in floating-point form)
     of non-negative integer n.
  */

  int count;
  double fact = 1.;

  /* Abort if n is negative integer */
  if (n < 0) 
   {
    printf("\nError: factorial of negative integer not defined\n");
    exit(1);
   }

  /* Calculate factorial */
  for (count = n; count > 0; --count) fact *= (double) count;

  /* Return value of factorial */
  return fact;      
}

//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

int main()
{
  int j;

  /* Print factorials of all integers between 0 and 20 */
  for (j = 0; j <= 20; ++j) 
    printf("j = %3d    factorial(j) = %12.3e\n", j, factorial(j));

  return 0;
}
Note that the call to factorial() takes place inside a complex expression (i.e., a printf() function call). Note also that the argument of factorial() has a different name (but the same data type) in the two sections of the program (i.e., in the main() function and the factorial() function). The output from the above program looks like:
j =   0    factorial(j) =    1.000e+00
j =   1    factorial(j) =    1.000e+00
j =   2    factorial(j) =    2.000e+00
j =   3    factorial(j) =    6.000e+00
j =   4    factorial(j) =    2.400e+01
j =   5    factorial(j) =    1.200e+02
j =   6    factorial(j) =    7.200e+02
j =   7    factorial(j) =    5.040e+03
j =   8    factorial(j) =    4.032e+04
j =   9    factorial(j) =    3.629e+05
j =  10    factorial(j) =    3.629e+06
j =  11    factorial(j) =    3.992e+07
j =  12    factorial(j) =    4.790e+08
j =  13    factorial(j) =    6.227e+09
j =  14    factorial(j) =    8.718e+10
j =  15    factorial(j) =    1.308e+12
j =  16    factorial(j) =    2.092e+13
j =  17    factorial(j) =    3.557e+14
j =  18    factorial(j) =    6.402e+15
j =  19    factorial(j) =    1.216e+17
j =  20    factorial(j) =    2.433e+18      
%

Ideally, function definitions should always precede the corresponding function calls in a C program. This requirement can usually be satisfied by judicious ordering of the various functions which make up a program, but, almost inevitably, restricts the location of the main() function to the end of the program. Hence, if the order of the two functions in the above program [i.e., factorial() and main()] were swapped then an error message would be generated on compilation, since an attempt would be made to call factorial() prior to its definition. Unfortunately, for the sake of logical clarity, most C programmers prefer to place the main() function at the beginning of their programs. After all, main() is always the first part of a program to be executed. In such situations, function calls [within main()] are bound to precede the corresponding function definitions: fortunately, however, compilation errors can be avoided by using a construct known as a function prototype.

Function prototypes are conventionally placed at the beginning of a program (i.e., before the main() function) and are used to inform the compiler of the name, data type, and number and data types of the arguments, of all user-defined functions employed in the program. The general form of a function prototype is

data-type  name(type 1,  type 2,  ...,  type n);
where data-type represents the data type of the item returned by the referenced function, name is the name of the function, and type 1, type 2,..., type n are the data types of the arguments of the function. Note that it is not necessary to specify the names of the arguments in a function prototype. Incidentally, the function prototypes for predefined library functions are contained within the associated header files which must be included at the beginning of every program which uses these functions.

The program listed below is a modified version of printfact.c in which the main() function is the first function to be defined:

/* printfact1.c */
/*
  Program to print factorials of all integers
  between 0 and 20
*/

#include <stdio.h>
#include <stdlib.h>

/* Prototype for function factorial() */
double factorial(int);    

int main()
{
  int j;

  /* Print factorials of all integers between 0 and 20 */
  for (j = 0; j <= 20; ++j) 
    printf("j = %3d    factorial(j) = %12.3e\n", j, factorial(j));

  return 0;
}

//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

double factorial(int n) 
{
  /* 
     Function to evaluate factorial (in floating-point form)
     of non-negative integer n.
  */

  int count;
  double fact = 1.;

  /* Abort if n is negative integer */
  if (n < 0) 
   {
    printf("\nError: factorial of negative integer not defined\n");
    exit(1);
   }

  /* Calculate factorial */
  for (count = n; count > 0; --count) fact *= (double) count;

  /* Return value of factorial */
  return fact;      
}
Note the presence of the function prototype for factorial() prior to the definition of main(). This is needed because the program calls factorial() before this function has been defined. The output from the above program is identical to that from printfact.c.

It is generally considered to be good programming practice to provide function prototypes for all user-defined functions accessed in a program, whether or not they are strictly required by the compiler.8 The reason for this is fairly simple. If we provide a prototype for a given function then the compiler can carefully compare each use of the function, within the program, with this prototype so as to determine whether or not we are calling the function properly. In the absence of a prototype, an incorrect call to a function (e.g., using the wrong number of arguments, or arguments of the wrong data type) can give rise to run-time errors which are difficult to diagnose.

When a single value is passed to a function as an argument then the value of that argument is simply copied to the function. Thus, the argument's value can subsequently be altered within the function but this will not affect its value in the calling routine. This procedure for passing the value of an argument to a function is called passing by value.

Passing an argument by value has advantages and disadvantages. The advantages are that it allows a single-valued argument to be written as an expression, rather than being restricted to a single variable. Furthermore, in cases where the argument is a variable, the value of this variable is protected from alterations which take place within the function. The main disadvantage is that information cannot be transferred back to the calling portion of the program via arguments. In other words, passing by value is a strictly one-way method of transferring information. The program listed below, which is another modified version of printfact.c, illustrates this point:

/* printfact2.c */
/*
  Program to print factorials of all integers
  between 0 and 20
*/

#include <stdio.h>
#include <stdlib.h>

/* Prototype for function factorial() */
double factorial(int);    

int main() 
{
  int j;

  /* Print factorials of all integers between 0 and 20 */
  for (j = 0; j <= 20; ++j) 
    printf("j = %3d    factorial(j) = %12.3e\n", j, factorial(j));

  return 0;
}

//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

double factorial(int n) 
{
  /* 
     Function to evaluate factorial (in floating-point form)
     of non-negative integer n.
  */

  double fact = 1.;

  /* Abort if n is negative integer */
  if (n < 0) 
   {
    printf("\nError: factorial of negative integer not defined\n");
    exit(1);
   }

  /* Calculate factorial */
  for (; n > 0; --n) fact *= (double) n;

  /* Return value of factorial */
  return fact;      
}
Note that the function factorial() has been modified such that its integer argument n is also used as the index in a for statement. Thus, the value of this argument is modified within the function. Nevertheless, the output of the above program is identical to that from printfact.c, since the modifications to n are not passed back to the main part of the program. Note, incidentally, the use of a null initialization expression in the for statement appearing in factorial().


next up previous
Next: Pointers Up: Scientific programming in C Previous: Control statements
Richard Fitzpatrick 2006-03-29