The basic property of functions is that they enable us to break a program down into a number of smaller units. There are several different reasons why programs should be broken down in this way. Different users attach different importance to these reasons; some of the considerations below may be considered irrelevant by some practitioners.
If a solution has been created for a particular sub-problem (an implementation of the solution involving means for storing the related data items, and the means of accessing them in an appropriate way) may be useful in more than one problem. An example is the sorting of a number of items into a particular order; sorting is a basic operation which is needed at many points in many computer problems.
Units which solve frequently occurring sub-problems can therefore be re-used in appropriate places in a variety of larger problems.
In this C course we teach just the programming techniques needed for producing useful re-usable code; we are not emphasising just WHY you may choose to break down your problem into these particular units. We need what are called "functions" and "structures" in C; on other courses you may learn techniques for using functions as the basis of a particular methodology for the design of programs.
In this section of the course, we teach the techniques for implementing functions. For those who have used other programming languages, the concept of a "function" in C and C++ corresponds to a "procedure" or "function" elsewhere.
First we look at a simple example.
/* First the declaration and definition */ /* of two functions */ void dothis () { printf( "Hello\n" ); } /* end of dothis */ void dothat () { printf( "Goodbye\n" ); } /* end of dothat */ /* Now the program calls them */ main () { /* These are the calls */ dothis (); ....; dothat (); } /* end of main */
The parentheses following the function identifier in the function calls in the main program indicate that "dothis" and "dothat" are identifiers representing functions to be executed. The functions may, of course, be called as many times as you wish within the program.
For each function the compiler needs two distinct items of information.
This should be compared with the declarations of variables, where again there are two distinct aspects to any declaration, one to inform the compiler about the type and identifier for compile-time, and one to arrange to claim space at run-time.
In the above simple example, declaration and definition are combined.
These two parts (declaration and definition) of a function can be separated in C as in the example
/* declarations */ void dothis (); void dothat (); main () { /* calls */ dothis (); ....; dothat (); } /* end main */ /* definitions */ void dothis () { printf( "Hello\n" ); } /* end of dothis */ void dothat () { printf( "Goodbye\n" ); } /* end of dothat */
The first declaration "void dothis();" is to warn the compiler what to expect as calls of the function, for example by introducing the identifier to be used. The compiler will not now be surprised to see the calls of the function when it encounters them in the main program. Although the compiler knows that dothis is the identifier of a function, not of a variable, it still requires parentheses after the identifier in function calls in the program.
The later definition "void dothis() { ... }" with executable statements within curly braces will define between those curly braces the code to be executed whenever the function is called. It will involve executable C code.
Variables which need to be accessed within both the function(s) and the main program must now be declared before both.
Our rates of pay exercise earlier becomes
int rate, hours, pay; void calc_pay() { if ( hours ... ) { pay = rate * hours; } else if ( hours <= ... ) { pay = ...; } else { pay = ....; } /* end calc_pay function */ main() { printf( "Type rate and hours: " ); scanf( "%d%d", &rate, &hours ); calc_pay(); printf( "Pay is %f\n", pay ); } /* end main */
To compute the area of a circle of given radius, a program might be
#define pi 3.14159 float radius; float area; void calc_area() { area = pi * radius * radius; } /* end cal_area */ main() { printf( "Type a value: " ); scanf( "%f", &radius ); calc_area(); printf( "Area is %f\n", area ); } /* end program */
To calculate the volume of a cylinder using the formulae
we would use functions as in the definition and write
#define pi 3.14159 float height, radius, area, volume; void calc_area() { area = pi * radius * radius; } /* end calc_area */ void cal_volume() { calc_area(); volume = height * area; } /* end calc_volume */ main() { printf( "Type height, radius: " ); scanf( "%f%f", &height, &radius ); calc_volume(); printf( "Volume %f\n", volume ); } /* end main */
The functions or main program can also have variables declared within them, at the start of the code. These are called local variables and can be referred to only from within that function or program. The Halberstam loop eaxmple earlier might be written
int value; int result; void halberstam() { int counter = 0; while value > 1 ) { if ( value % 2 == 0 ) { /* even */ value %= 2; } else { value = 3 * value + 1; } counter++; } /* end while loop */ } /* end function */ main() { printf( "Type the number: " ); scanf( "%d", &number ); halberstam(); printf( "Result is %d\n", result ); } /* end main */
The variable "counter" can be referred to only from within the halberstam function.
If a local identifier is the same as a global one,
Generally, and identifier is first searched for as a local variable; if no local declaration of that identifier is found, a global declaration is used.
Function identifiers themselves count a global identifiers. They cannot also be used as global variable identifiers. If they are used as a local variable identifier in a second function, then that function cannot be called from within that second function.
The use of global variables as shown above is generally not a good way to develope functions. See later!
The above examples were very simplistic. In more significant examples, we will wish to pass certain values into a function when it is invoked, and may wish to obtain results directly from the call. For example, if we are performing mathematics and require a "square root" function, we will wish
sqrt( 2.71828 );
or
sqrt( 2 * x );
y = sqrt( 2.71828 );
or
if ( sqrt( 2 * x ) > y ) ...
and use the result directly.
The function declaration now needs to inform the compiler of
A function to compute the square root of a given value.
/* identifier "sqrt" */ /* takes one double parameter */ /* gives double result */ double sqrt ( );
A function to compute the larger of two integer values.
/* identifier "max" */ /* takes two integer parameters, */ /* gives integer result */ int max ( );
A function to compute the volume of a cylinder.
/* identifier "cyl_vol" */ /* takes two float parameters */ /* delivers float result */ float cyl_vol();
A function to print an error message involving an integer value.
/* identifier "error" */ /* takes integer parameter */ /* gives no returned result */ void error ( );
The above functions would be called from elsewhere (perhaps from the main program, perhaps from another function) by statements such as the following.
Square root would take a "double" parameter and return a double result.
double y = sqrt ( 2.0 ); if ( sqrt ( x * 5 ) > 2 ) .... printf( "sqrt 2 is %f\n", sqrt( 2.0 ) );
We would normally expect to use the returned result as a value.
Maximum of two integers
int this, that; scanf( "%d%d", &this, &that ); biggest = max( this, that ); printf( "Larger value is %d\n", max ( this, that ) ); non_negative = max( this, 0 );
Volume of a cylinder
mass = cyl_vol( hgt, rad ) * density; if ( cyl_vol( height, radius ) > 100 ) {
Reporting an error
if ( n < 0 ) { error ( 5 ); /* no result */ } if ( n > 100 ) { error ( 6 ); /* no result */ }
A function declaration consists of
<returned type> <identifier> ( );
Do not forget the semi-colon!
The returned type may be any ordinary C type such as int , float and so on, or the type void if no value is being returned to the main program.
In some older programming languages, the word "function" was used only when a result was to be delivered; the word "procedure" or "function" was used is there was no result to deliver. In C and C++ all such objects are called functions.
The function definition needs to inform the compiler of the actions to be taken whenever the function is called. In the declaration we described only the type of the result. The compiler needs parameter type information to enable it to handle the parameters correctly wherever the function is called. In order to describe the actions to be taken when the function is called, we need to identify each of the parameters, so that we can refer to them and use them from within the code which forms the body of the function.
We want a function to deliver the larger of the values of the two values given as parameters.
/* deliver the larger of the 2 params */ int max ( i, j ) int i, j; { if ( i > j ) { return i; } return j; } /* end max */
The "return" keyword acts both to specify the particular value to be returned by the call of the function, and to cause dynamic exit from the function.
If the function returns type void we leave the function using the statement
return;
with no value specified; the function will leave anyway at the end of its code. If the function returns a non-void type, it must always use "return ..." to exit, it cannot just leave at the end of the code, and the return must be followed by a value which the compiler can force into the required type.
Notice that we do not need an "else" statement in this example because of the dynamic exit caused by the "return i;" statement.
We will declare and define two functions for this.
/* Two declarations */ float circ_area(); float cyl_vol(); float circ_area( radius ) float radius; { return pi * radius * radius; } /* end circ_area definition */ float cyl_vol( height, radius ) float height, radius; { return height * circ_area( radius ); } /* end cyl _vol definition */
The declaration would be
int halberstam();
and the definition
int halberstam( number ) int number; { int counter = 0; while value > 1 ) { if ( value % 2 == 0 ) { value %= 2; } else { value = 3 * value + 1; } counter++; } /* end while loop */ return counter; } /* end function */
We want a function to print an error message and then abandon the program.
void error ( errno ) int errno; { printf( "Error number %d has occurred\n", errno ); printf( "Program abandoned\n" ); exit ( 1 ); } /* end error */
In this example, the "exit" causes the whole program to terminate; if we merely wished to report the error and continue the program, we would use a
return;
statement with no value given. If the function has its delivered type declared as void its code does not return values, and the function is left by using
return;
The exit function causes the completer program to terminate. Convention with the exit function is that
exit( 0 ); /* indicates normal exit */ exit( 1 ); /* indicates error exit */ exit( 2 ); /* indicates error exit */
The UNIX shell can use the value returned by a terminating program to determined whether it terminated successfully or not.
However, with the Ceilidh system, all programs must use
exit( 0 );
since any other exit assumes that some accidental error has occurred.
We need to calculate the square root of the given parameter value, and return it. Please forgive the crude method!
The declaration would be
double sqrt(); /P} The definition could be
double sqrt( double x ) { /* code not guaranteed, I'm in a hurry! */ double accuracy = 0.00000001; double low = 1, high = x, mid = x / 2; if ( x < 0 ) { error( 5 ); /* error if negative parameter */ } if ( x < 1 ) { low = 0; high = 1; } while( high - low > accuracy ) { if ( mid * mid > x ) { high = mid; } else { low = mid; } /* end if guess too large */ mid = ( low + high ) / 2; } /* end while accuracy not yet reached */ return mid; } /* end sqrt */
A definition consists of
<returned type> <identifier> ( <param type> <param ident>, <param type> <param ident>, .... ) { /* No semi-colon after the ")" */ ....; /* body */ } /* usually an end comment here */
The compiler needs to know the type of the value to be returned by the function so that it can be treated correctly in the call of the function. If no value is to be returned (cf "error" or "dothis" above) then the return type is given as "void".
The particular value to be returned is specified by the line
return <value>;
With a void function write simply
return;
The compiler will do type conversions where necessary. If the function declaration says that it returns a float and the function includes "return 1;" then the compiler knows to sort this out.
The parameter type sequence in the definition and the calls must agree. The compiler will make the necessary type conversions to the delivered result in a call of the function.
The parameters in the function definition are called the formal parameters. The parameter values supplied in calls of the function are called actual parameters.
The main program and function definitions can appear in any order in your program. It is normally clearest to read and understand if the main program appears before the function definitions. Reading the main program gives an overview of the sequence of operations.
Function declarations MUST appear before the function is called. They are usually put together before the main program.
We can declare local variables at the start of our function code if we require additional variables for computations within the function. The declarations appear after the opening curly brace, just as they do in the main program.
If a local identifier clashes with a global constant, or with the name of another function, then that global constant or other function becomes inaccessible within this function. The local object will be referred to by the identifier.
When a function is called, the values of the (actual) parameters at the point where the function is called are calculated, and passed to the function definition for execution. Within the function, the parameters act like variables, initialised to the value of the corresponding actual parameter at the call, and their values can be changed by ordinary assignment.
Thus if a function definition is
int fred( int number ) { ....; number = number + p; ....; } /* end function fred */
the identifier "number" can be considered as a local variable to the function. Changing its value inside the function as shown in the above example has no effect on the outside world.
If the call of the function is
int counter = ...; fred( counter );
the value of the variable counter in the calling program will not be changed. We could also call the function by
fred( 23 );
which would pass the value 23 to the formal parameter. This way of passing parameters is referred to as "passing by value".
The parameters as specified at the start of the function definition are referred to as "formal parameters".
The parameters substituted in any particular call of the function are referred to as "actual parameters".
The complete program with function declaration, main program and function definition will look roughly as follows. We use an example with the Halberstam function.
/* Program example */ /* Halberstam function being declared */ /* then used */ /* then defined */ /* Declare the function */ int halberstam( ); /* The main program */ main() { int numb = 0, calcs = 0; /* Loop reading integers until we meet a zero */ while ( printf( "Enter the number now please: " ), scanf( "%d, &numb ), numb != 0 ) { /* This is the call */ calcs = halberstam( numb ); printf( "numb %d result %d\n", numb, calcs ); } /* end while read number > 0 */ } /* end main program */ /* Now for the definition */ int halberstam( numb ) int numb; { int calcs = 0; /* Error exit */ if ( numb <= 0 ) { return -1; } /* Loop counting how many times */ while ( numb != 1 ) { if ( numb % 2 ) { /* number is odd */ numb *= 3; numb++; } else { /* number is even */ numb /= 2; } calcs++; } /* End of while loop */ return calcs; } /* end function halberstam */
In general functions will perform operations on data, and not perform their own input output except
We show here two functions taking float parameters, and giving float results. The first takes a radius length, and delivers the area of a circle of that radius. The second takes an area, and delivers the radius of a circle with that area.
const float pi = 3.14159; float area ( float radius ) { return pi * radius * radius; } /* end radius to area */ float radius ( float area ) { if ( area < 0 ) { cerr << "Sqrt invalid param " << area << "\n"; /* you might choose to exit here with "exit( 1 );" */ return -1; } return sqrt ( area / pi ); } /* end area to radius */
Calls of these functions might be
float a1, r1, a2, r2; scanf( "%f", &r1 ); a1 = area( r1 ); scanf( "%f", &a2 ); r2 = radius( a2 ); if ( r2 < 0 ) { ...
This function sums the series
for a given value of "x". We keep adding terms until we reach a term whose value is less than 0.001.
float expl( float x ) { /* sum the exponential series */ float total = 0; float term = 1; int count = 0; /* now loop */ while ( term > 0.001 ) { total += term; count++; term *= x/count; } /* return the total */ return total; } /* end expl */
Calls of this function might be
printf( "Value is %f\n", expl( 1 ) );
We require a function to print out a given message as a question, and return TRUE if the user replies "y" and FALSE otherwise. The type of a message (a string of text contained within double quotes) is "char *" (the reasons for this will appear later), and the definition of the function might be:
int ok( message ) char *message; { char ch; printf( "%s [Type y or n]? ", message ); scanf( "%c", &ch ); return ch == 'y'; }
In a real situation, the function body might repeat the question until the given answer is "y" or "n". In the above example, we respond with TRUE if the answer is "y" and FALSE otherwise.
Note that we do not need to say
if ( ch == 'y' ) return 1; return 0;
We can return the comparison result directly.
Calls of this function might be:
if ( ok( "Was that correct" ) ) { ....; } else { ....; } if ( ok ( "Overwrite the file" ) && ok ( "Are you sure" ) ) { ....; }
Observe the subtlety of the last example. The program first asks
and, because the "&&" operator is lazy, only if the reply to the first question is "y" asks for confirmation
If the reply to the first question is "n", the second question is not asked.
A number of standard mathematical functions are available. For details try
man exp man sqrt man trig
or
ls /usr/man/man3/*.3m
Copyright Eric Foxley 1996
Notes converted from troff to HTML by an Eric Foxley shell script, email errors to me!