2.5 Functions

Functions break large computing tasks into smaller ones, and enable people to build on what others have done instead of starting over from scratch. Appropriate functions hide details of operation from parts of the program that don't need to know about them, thus clarifying the whole, and easing the pain of making changes.

Basic of C functions

Functions break large computing tasks into smaller ones, and enable people to build on what others have done instead of starting over from scratch. Appropriate functions hide details of operation from parts of the program that don’t need to know about them, thus clarifying the whole, and easing the pain of making changes.

C has been designed to make functions efficient and easy to use; C programs generally consist of many small functions rather than a few big ones. A program may reside in one or more source files. Source files may be compiled separately and loaded together, along with previously compiled functions from libraries. We will not go into that process here, however, since the details vary from system to system.

Function declaration and definition is the area where the ANSI standard has made the most changes to C. It is now possible to declare the type of arguments when a function is declared. The syntax of function declaration also changes, so that declarations and definitions match. This makes it possible for a compiler to detect many more errors than it could before. Furthermore, when arguments are properly declared, appropriate type coercions are performed automatically.

Every function is defined exactly once. A program can declare and call a function as many times as necessary.

Declaration and Usage of Function

Function Declarations

The definition of a function consists of a function head (or the declarator) and a function block . The function head specifies the name of the function, the type of its return value, and the types and names of its parameters, if any. The statements in the function block specify what the function does. The general form of a function definition is as follows:

//function head
type function-name(parameter declarations)  
//function block
{ 
declarations and statements 
}

In the function head, name is the function’s name, while type (return-type) consists of at least one type specifier, which defines the type of the function’s return value. The return type may be void or any object type, except array types. Furthermore, type may include the function specifier inline, and/or one of the storage class specifiers extern and static.

A function cannot return a function or an array. However, you can define a function that returns a pointer to a function or a pointer to an array.

The parameterdeclarations are contained in a comma-separated list of declarations of the function’s parameters. If the function has no parameters, this list is either empty or contains merely the word void.

The type of a function specifies not only its return type, but also the types of all its parameters. The following listing is a simple function to calculate the volume of a cylinder.

// The  cylinderVolume( ) function calculates the volume of a cylinder.
// Arguments: Radius of the base circle; height of the cylinder.
// Return value: Volume of the cylinder.

extern double cylinderVolume( double r, double h )
{
   const double pi = 3.1415926536;     // Pi is constant
   return  pi * r * r * h;
}

This function has the name cylinderVolume, and has two parameters, r and h, both with type double. It returns a value with the type double.

return statement

The return statement ends execution of the current function, and jumps back to where the function was called:

return [expression];

expression is evaluated and the result is given to the caller as the value of the function call. This return value is converted to the function’s return type, if necessary.

A function can contain any number of return statements:

// Return the smaller of two integer arguments.
int min( int a, int b )
{
   if   ( a < b ) return a;
   else           return b;
}

The contents of this function block can also be expressed by the following single statement:

return ( a < b ? a : b );

The parentheses do not affect the behavior of the return statement. However, complex return expressions are often enclosed in parentheses for the sake of readability.

A return statement with no expression can only be used in a function of type void. In fact, such functions do not need to have a return statement at all. If no return statement is encountered in a function, the program flow returns to the caller when the end of the function block is reached.

Usage of Functions

The instruction to execute a function, the function call, consists of the function’s name and the operator ( ). For example, the following statement calls the function maximum to compute the maximum of the matrix mat, which has r rows and c columns:

maximum( r, c, mat );

The program first allocates storage space for the parameters, then copies the argument values to the corresponding locations. Then the program jumps to the beginning of the function, and execution of the function begins with first variable definition or statement in the function block.

If the program reaches a return statement or the closing brace } of the function block, execution of the function ends, and the program jumps back to the calling function. If the program “falls off the end” of the function by reaching the closing brace, the value returned to the caller is undefined. For this reason, you must use a return statement to stop any function that does not have the type void. The value of the return expression is returned to the calling function.

Scope of Variables

One of the C language’s strengths is its flexibility in defining data storage. There are two aspects that can be controlled in C: scope and lifetime. Scope refers to the places in the code from which the variable can be accessed. Lifetime refers to the points in time at which the variable can be accessed.

Three scopes are available to the programmer:

  • extern: This is the default for variables declared outside any function. The scope of variables with extern scope is all the code in the entire program.
  • static: The scope of a variable declared static outside any function is the rest of the code in that source file. The scope of a variable declared static inside a function is the rest of the local block.
  • auto: This is the default for variables declared inside a function. The scope of an auto variable is the rest of the local block.

Three lifetimes are available to the programmer. They do not have predefined keywords for names as scopes do. The first is the lifetime of extern and static variables, whose lifetime is from before main() is called until the program exits. The second is the lifetime of function arguments and automatics, which is from the time the function is called until it returns. The third lifetime is that of dynamically allocated data. It starts when the program calls malloc() or calloc() to allocate space for the data and ends when the program calls free() or when it exits, whichever comes first.

Local block

A local block is any portion of a C program that is enclosed by the left brace ({) and the right brace (}). A C function contains left and right braces, and therefore anything between the two braces is contained in a local block. An if statement or a switch statement can also contain braces, so the portion of code between these two braces would be considered a local block. Additionally, you might want to create your own local block without the aid of a C function or keyword construct. This is perfectly legal. Variables can be declared within local blocks, but they must be declared only at the beginning of a local block. Variables declared in this manner are visible only within the local block. Duplicate variable names declared within a local block take precedence over variables with the same name declared outside the local block. Here is an example of a program that uses local blocks:

#include <stdio.h>
void main(void);
void main()
{
/* Begin local block for function main() */
int test_var = 10;
printf(“Test variable before the if statement: %d\n”, test_var);
if (test_var > 5)
{
/* Begin local block for “if” statement */
int test_var = 5;
printf(“Test variable within the if statement: %d\n”, test_var);
{
/* Begin independent local block (not tied to any function or keyword) */
int test_var = 0;
printf(“Test variable within the independent local block:%d\n”, test_var);
}
/* End independent local block */
}
/* End local block for “if” statement */
printf(“Test variable after the if statement: %d\n”, test_var);
}
/* End local block for function main() */

This example program produces the following output:

Test variable before the if statement: 10                                                                            
Test variable within the if statement: 5
Test variable within the independent local block: 0
Test variable after the if statement: 10

Notice that as each test_var was defined, it took precedence over the previously defined test_var. Also notice that when the if statement local block had ended, the program had reentered the scope of the original test_var, and its value was 10.

Functions and Storage Class Specifiers

The function in the listing above is declared with the storage class specifier extern. This is not strictly necessary, since extern is the default storage class for functions. An ordinary function definition that does not contain a static or inline specifier can be placed in any source file of a program. Such a function is available in all of the program’s source files, because its name is an external identifier. You merely have to declare the function before its first use in a given translation unit. Furthermore, you can arrange functions in any order you wish within a source file. The only restriction is that you cannot define one function within another. C does not allow you to define “local functions” in this way.

You can hide a function from other source files. If you declare a function as static, its name identifies it only within the source file containing the function definition. Because the name of a static function is not an external identifier, you cannot use it in other source files. If you try to call such a function by its name in another source file, the linker will issue an error message, or the function call might refer to a different function with the same name elsewhere in the program.

The function printArray( ) in the following listing might well be defined using static because it is a special-purpose helper function, providing formatted output of an array of float variables.

// The static function printArray( ) prints the elements of an array
// of float to standard output, using printf( ) to format them.
// Arguments:    An array of float, and its length.
// Return value: None.

static void printArray( const float array[ ], int n )
{
  for ( int i=0; i < n; ++i )
  {
    printf( "%12.2f", array[i] );     // Field width: 12; decimal places: 2
    if ( i % 5 == 4 ) putchar( '\n' );// New line after every 5 numbers
  }
  if ( n % 5 != 0 ) putchar( '\n' ); // New line at the end of the output
}

If your program contains a call to the printArray() function before its definition, you must first declare it using the static keyword:

static void printArray(const float [ ], int);

int main( )
{
  float farray[123];
  /* ... */
  printArray( farray, 123 );
  /* ... */
}

Function prototype

A function prototype in C++ is a declaration of a function that omits the function body but does specify the function’s name, arity, argument types and return type. While a function definition specifies what a function does, a function prototype can be thought of as specifying its interface. Just like a blueprint, the prototype tells the compiler what the function will return, what the function will be called, as well as what arguments the function can be passed. The general format for a prototype is simple:

type function_name ( arg_type arg1, ..., arg_type argN );

arg_type just means the type for each argument — for instance, an int, a float, or a char. It’s exactly the same thing as what you would put if you were declaring a variable.

There can be more than one argument passed to a function or none at all (where the parentheses are empty), and it does not have to return a value. Functions that do not return values have a return type of void. Lets look at a function prototype:

int mult ( int x, int y );

This prototype specifies that the function mult will accept two arguments, both integers, and that it will return an integer. Do not forget the trailing semi-colon. Without it, the compiler will probably think that you are trying to write the actual definition of the function.

When the programmer actually defines the function, it will begin with the prototype, minus the semi-colon. Then there should always be a block with the code that the function is to execute, just as you would write it for the main function. Any of the arguments passed to the function can be used as if they were declared in the block.

Lets look at an example program:

#include <stdio.h>
#include <conio.h>
int mult ( int x, int y );
int main()
{
  int x;
  int y;
  printf("Please input two numbers to be multiplied: ");
  scanf("%d%d", &x,&y);
  printf("The product of your two numbers is %d\n", mult ( x, y )) ;
  return 0;
  getch();
}
int mult ( int x, int y )
{
  return x * y;
}

Parameters passing

The parameters of a function are ordinary local variables. The program creates them, and initializes them with the values of the corresponding arguments, when a function call occurs. Their scope is the function block. A function can change the value of a parameter without affecting the value of the argument in the context of the function call. In the following listing, the factorial( )function, which computes the factorial of a whole number, modifies its parameter n in the process.

//factorial( ) calculates n!, the factorial of a non-negative number n.
// For n > 0, n! is the product of all integers from 1 to n inclusive.
// 0! equals 1.
// Argument:     A whole number, with type unsigned int.
// Return value: The factorial of the argument, with type long double.

long double factorial(register unsigned int n)
{
  long double f = 1;
  while ( n > 1 )
    f *= n--;
  return f;
}

Although the factorial of an integer is always an integer, the function uses the type long double in order to accommodate very large results. As the above listing illustrates, you can use the storage class specifier register in declaring function parameters. The register specifier is a request to the compiler to make a variable as quickly accessible as possible. No other storage class specifiers are permitted on function parameters.

Arrays as Function Parameters

If you need to pass an array as an argument to a function, you would generally declare the corresponding parameter in the following form:

type name[ ]

Because array names are automatically converted to pointers when you use them as function arguments, this statement is equivalent to the declaration:

type *name

When you use the array notation in declaring function parameters, any constant expression between the brackets ([ ]) is ignored. In the function block, the parameter name is a pointer variable, and can be modified. Thus the function addArray() in the following listing modifies its first two parameters as it adds pairs of elements in two arrays.

// addArray( ) adds each element of the second array to the
// corresponding element of the first (i.e., "array1 += array2", so to speak).
// Arguments:    Two arrays of float and their common length.
// Return value: None.

void addArray( register float a1[ ], register const float a2[ ], int len )
{
  register float *end = a1 + len;
  for ( ; a1 < end; ++a1, ++a2 )
    *a1 += *a2;
}

An equivalent definition of the addArray() function, using a different notation for the array parameters, would be:

void addArray( register float *a1, register const float *a2, int len )
{  /* Function body as earlier. */  }

An advantage of declaring the parameters with brackets ([ ]) is that human readers immediately recognize that the function treats the arguments as pointers to an array, and not just to an individual float variable. But the array-style notation also has two peculiarities in parameter declarations :

  • In a parameter declaration and only there C allows you to place any of the type qualifiers const, volatile, and restrict inside the square brackets. This ability allows you to declare the parameter as a qualified pointer type.
  • Furthermore, in C you can also place the storage class specifier static, together with a integer constant expression, inside the square brackets. This approach indicates that the number of elements in the array at the time of the function call must be at least equal to the value of the constant expression.

Here is an example that combines both of these possibilities:

int func( long array[const static 5] )
{ /* ... */ }

In the function defined here, the parameter array is a constant pointer to long, and so cannot be modified. It points to the first of at least five array elements.

In the following listing, the maximum( ) function’s third parameter is a two-dimensional array of variable dimensions.

// The function maximum( ) obtains the greatest value in a
// two-dimensional matrix of double values.
// Arguments:    The number of rows, the number of columns, and the matrix.
// Return value: The value of the greatest element.

double maximum( int nrows, int ncols, double matrix[nrows][ncols] )
{
  double max = matrix[0][0];
  for ( int r = 0; r < nrows; ++r )
    for ( int c = 0; c < ncols; ++c )
      if ( max < matrix[r][c] )
        max = matrix[r][c];
  return max;
}

The parameter matrix is a pointer to an array with ncols elements.

Pointers as Function Parameters

Since C passes arguments to functions by value, there is no direct way for the called function to alter a variable in the calling function. For instance, a sorting routine might exchange two out-of-order arguments with a function called swap. It is not enough to write

swap(a, b);

where the swap function is defined as

void swap(int x, int y) /* WRONG */ 
{ 
int temp; 
temp = x; 
x = y; 
y = temp; 
} 

Because of call by value, swap can’t affect the arguments a and b in the routine that called it. The function above swaps copies of a and b.

The way to obtain the desired effect is for the calling program to pass pointers to the values to be changed:

 swap(&a, &b);

Since the operator & produces the address of a variable, &a is a pointer to a. In swap itself, the parameters are declared as pointers, and the operands are accessed indirectly through them.

void swap(int *px, int *py) /* interchange *px and *py */ 
{ 
int temp; 
temp = *px; 
*px = *py; 
*py = temp; 
} 

{

Pictorially in Figure

swap function with pointer parameters
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s