Saturday, 1 April 2017

Functions

As we have previously stated functions are essentially just groups of statements that are to be executed as a unit in a given order and that can be referenced by a unique name. The only way to execute these statements is by invoking them or calling them using the function’s name.

Traditional program design methodology typically involves a top-down or structured approach to developing software solutions. The main task is first divided into a number simpler sub-tasks. If these sub-tasks are still too complex they are subdivided further into simpler sub-tasks, and so on until the sub-tasks become simple enough to be programmed easily.

Functions are the highest level of the building blocks given to us in C and correspond to the sub-tasks or logical units referred to above. The identification of functions in program design is an important step and will in general be a continuous process subject to modification as more becomes known about the programming problem in progress.

We have already seen many C functions such as main( ) , printf(), etc. the common trait they share being the braces that indicate they are C functions.

Syntax :           return_type function_name (  parameter_list )
           {
          body of function ;
           }

The above is termed the function definition in C parlance.

Many functions will produce a result or return some information to the point at which it is called. These functions specify the type of this quantity via the return_type section of the function definition. If the return type is void it indicates the function returns nothing.

The function_name may be any valid C identifier and must be unique in a particular program.

If a function requires information from the point in the program from which it is called this may be passed to it by means of the parameter_list. The parameter list must identify the names and types of all of the parameters to the function individually. If the function takes no parameters the braces can be left empty or use the keyword void to indicate that situation more clearly.

Function Prototype ( declaration)

When writing programs in C it is normal practice to write the main() function first and to position all user functions after it or indeed in another file. Thus if a user function is called directly in main() the compiler will not know anything about it at this point i.e. if it takes parameters etc. This means we need to give the compiler this information by providing a function prototype or declaration before the function is called.

Syntax : type_spec function_name( type_par1, type_par2, etc. );

This declaration simply informs the compiler what type the function returns and what type and how many parameters it takes. Names may or may not be given to the parameters at this time.

For Example :- A more complicated “Hello World” program.

#include <stdio.h>   /* standard I/O function prototypes */

void hello( void ) ;       /* prototype  */

void main( void )
{
  hello () ;               //  function call
}

void hello ( )             // function definition
{
  printf ( "Hello World \n" ) ;
}

Function Definition  & Local Variables

A function definition actually defines what the function does and is essentially a discrete block of code which cannot be accessed by any statement in any other function except by formally calling the function.  Thus any variables declared and used in a function are private or local to that function and cannot be accessed by any other function.

For Example :-
           #include <stdio.h>
           void hello( void ) ;

           void main(  )
           {
           hello () ;
           }

           void  hello ( )
           {
           int i ;         /*  local  or automatic variable  */

           for ( i=0; i<10; i++ )
                printf( "Hello World \n" );    
           }

The variable i in the hello() function is private to the hello function i.e. it can only be accessed by code in the hello() function.

Local variables are classed as automatic variables because each time a function is called the variable is automatically created and is destroyed when the function returns control to the calling function. By created we mean that memory is set aside to store the variable’s value and by destroyed we mean that the memory required is released. Thus a local variable cannot hold a value between consecutive calls to the function.

Static Local Variables

The keyword static can be used to force a  local  variable to retain its value between function calls.

For Example :-
           #include <stdio.h>
           void hello( void ) ;

           void main ()
           {
           int i ;

           for ( i = 0; i < 10; i++ )
                hello ( ) ;
           }

           void hello( )
           {
           static int i = 1 ;

           printf( "Hello World call number %d\n", i ++ );
           }

The static int i is created and initialised to 1 when the function is first called and only then. The variable retains its last value during subsequent calls to the function and is only destroyed when the program terminates.

NB :  The variables i in main() and i in hello() are completely different variables even though they have the same name because they are private to the function in which they are declared. The compiler distinguishes between them by giving them their own unique internal names.
 

Scope Rules


The scope of an identifier is the area of the program in which the identifier can be accessed.

Identifiers declared inside a code block are said to have block scope. The block scope ends at the terminating } of the code block. Local variables for example are visible, i.e. can be accessed, from within the function, i.e. code block, in which they are declared. Any block can contain variable declarations be it the body of a loop statement, if statement, etc. or simply a block of code marked off by a curly brace pair. When these blocks are nested and an outer and inner block contain variables with the same name then the variable in the outer block is inaccessible until the inner block terminates.

Global variables are variables which are declared outside all functions and which are visible to all functions from that point on. These are said to have file scope.

All functions are at the same level in C i.e. cannot define a function within a function in C. Thus within the same source file all functions have file scope i.e. all functions are visible or can be called by each other ( assuming they have been prototyped properly before they are called ).

Returning a Value


The return statement is used to return a value to the calling function if necessary.

Syntax :           return expression ;

If a function has a return type of type void the expression section can be omitted completely or indeed the whole return statement can be omitted and the closing curly brace of the function will cause execution to return appropriately to the calling function.

For Example :-
           #include <stdio.h>

           int hello( void ) ;

           int main( )
           {
           int count, ch = ‘\0’;
    
           while (  ch  != 'q' )
                {
                count = hello( ) ;
                ch = getchar() ;
                _flushall() ;
                }
    
           printf( "hello was called %d times\n", i ) ;
           return 0 ;
           }

           int hello( )
           {
           static int i = 1 ;

           printf( "Hello World \n" ) ;
           // hello() keeps track of how many times it was called
return ( i++ ) ;           // and passes that information back to its caller
           }

NB : The return value of the function need not always be used when calling it. In the above example if we are not interested in know how often hello() has been called we simply ignore that information and invoke the function with

           hello() ;


NB : When the main() function returns a value, it returns it to the operating system. Zero is commonly returned to indicate successful normal termination of a program to the operating system and other values could be used to indicate abnormal termination of the program. This value may be used in batch processing or in debugging the program.

Function Arguments

The types of all function arguments should be declared in the function prototype as well as in the function definition.

NB : In C arguments are passed to functions using the call-by-value scheme. This means that the compiler copies the value of the argument passed by the calling function into the formal parameter list of the called function. Thus if we change the values of the formal parameters within the called function we will have no effect on the calling arguments. The formal parameters of a function are thus local variables of the function and are created upon entry and destroyed on exit.

For Example :-  Program to add two numbers.

     #include  <stdio.h>

     int add( int, int ) ; /* prototype -- need to indicate types only  */

     void main ( )
     {
     int x, y ;

     puts ( "Enter two integers ") ;
     scanf( "%d %d", &x, &y) ;

     printf( "%d + %d = %d\n" , x, y, add(x,y) ) ;
     }

     int add ( int a, int b )
     {
     int result ;

     result = a + b ;
     return result ;      // parentheses used for clarity here
     }

NB : In the formal parameter list of a function the parameters must be individually typed.   

The add() function here has three local variables, the two formal parameters and the variable result. There is no connection between the calling arguments, x and y, and the formal parameters, a and b, other than that the formal parameters are initialised with the values in the calling arguments when the function is invoked. The situation is depicted below to emphasise the independence of the various variables.


NB : The information flow is in one direction only.

For Example :-  Program that attempts to swap the values of two numbers.

     #include <stdio.h>
     void swap( int, int ) ;

     void main( )
     {
     int a, b ;
    
     printf( "Enter two numbers" ) ;
     scanf( " %d %d ", &a, &b ) ;
     printf( "a = %d ;  b = %d \n", a, b ) ;

     swap( a, b ) ;

     printf( "a = %d ;  b = %d \n", a, b ) ;
     }

     void swap( int , int )//This is original form of declarator
     int num1, num2 ;     // which you may see in older texts and code
     {
     int temp ;

     temp = num2 ;
     num2 = num1 ;
     num1 = temp ;
     }

Since C uses call by value to pass parameters what we have actually done in this program is to swap the values of the formal parameters but we have not changed the values in main(). Also since we can only return one value via the return statement we must find some other means to alter the values in the calling function.

The solution is to use call by reference where the addresses of the calling arguments are passed to the function  parameter list and the parameters are pointers which we will encounter later on. For example when we use the scanf() standard library function to read values from the keyboard we use the & operator to give the address of the variables into which we want the values placed.

Recursion

A recursive function is a function that calls itself either directly or indirectly through another function.
Recursive function calling is often the simplest method to encode specific types of situations where the operation to be encoded can be eventually simplified into a series of more basic operations of the same type as the original complex operation.

This is especially true of certain types of mathematical functions. For example to evaluate the factorial of a number, n
                                    n! = n * n-1 * n-2 * ...  * 3 * 2 * 1.

We can simplify this operation into

                        n! =  n * (n-1)!
where the original problem has been reduced in complexity slightly. We continue this process until we get the problem down to a task that may be solved directly, in this case as far as evaluating the factorial of 1 which is simply 1.

So a recursive function to evaluate the factorial of a number will simply keep calling itself until the argument is 1. All of the previous (n-1) recursive calls will still be active waiting until the simplest problem is solved before the more complex intermediate steps can be built back up giving the final solution.

For Example : Program to evaluate the factorial of a number using recursion.

#include <stdio.h>
short factorial( short ) ;
void main()
{
short i ;

printf(“Enter an integer and i will try to calculate its factorial : “ ) ;
scanf( “%d”, &i ) ;
printf( “\n\nThe factorial of %d, %d! is %d\n”, i, i, factorial( i ) ) ;  
}

short factorial( short num )
{
if ( num <= 1 )
     return 1 ;
else
     return ( num * factorial( num - 1 ) ) ;
}

This program will not work very well as is because the values of factorials grow very large very quickly. For example the value of 8! is 40320 which is too large to be held in a short so integer overflow will occur when the value entered is greater than 7. Can you offer a solution to this ?

While admittedly simple to encode in certain situations programs with recursive functions can be slower than those without because there is a time delay in actually calling the function and passing parameters to it.
There is also a memory penalty involved. If a large number of recursive calls are needed this means that there are that many functions active at that time which may exhaust the machine’s memory resources. Each one of these function calls has to maintain its own set of parameters on the program stack.

 #define directive

This is a preprocessor command which is used to replace any text within a C program with a more informative pseudonym.

For Example :-             #define  PI  3.14159

When the preprocessor is called it replaces each instance of the phrase PI with the correct replacement string which is then compiled. The advantage of using this is that if we wish to change the value of PI at any stage we need only change it in this one place rather than at each point the value is used.

Macros

Macros make use of the #define directive to replace a chunk of C code to perform the same task as a function but will execute much faster since the overhead of a function call will not be involved. However the actual code involved is replaced at each call to the macro so the program will be larger than with a function.

For Example :-  Macro to print an error message.

#define  ERROR printf( "\n Error \n" )

void main( )
{
     ...
if ( i > 1000 )
     ERROR ;         /*  note must add ; in this case to make correct …C statement  */
}

Macros can also be defined so that they may take arguments.

For Example :-
           #define    PR( fl )  printf( "%8.2f ", fl )

           void main()
           {
           float num = 10.234 ;
    
           PR( num ) ;
           }

What the compiler actually sees is  :  printf( "%8.2f ", num ) ;

While admittedly advantageous and neat in certain situations care must be taken when coding macros as they are notorious for introducing unwanted side effects into programs.

For Example :-
           #define MAX(A, B)   ( ( A ) > ( B ) ? ( A ) : ( B )  )

           void main( )
           {
           int i = 20, j = 40 , k ;

           k = MAX( i++, j++ ) ;
           printf( " i = %d, j = %d\n", i, j );
           ...
           }
The above program might be expected to output the following

            i = 21, j = 41

whereas in fact it produces the following

            i = 21, j = 42

where the larger value is incremented twice. This is because the macro MAX is actually translated into the following by the compiler

            ( ( i++ ) > ( j++ ) ? ( i++ ) : ( j++ ) )

so that the larger parameter is incremented twice in error since parameter passing to macros is essentially text replacement.

Efficiency Considerations

As we have mentioned previously the use of functions involves an overhead in passing parameters to them and obtaining a return value from them. For this reason they can slow down your program if used excessively.

The alternative to this is to use macros in place of functions. This eliminates the penalty inherent in the function call but does make the program larger. Therefore in general macros should only be used in place of small ‘would be’ functions.

The penalty involved in the function call itself is also the reason why iterative methods are preferred over recursive methods in numerical programming.

Exercises


1.  Write a program that can convert temperatures from the Fahrenheit scale to Celsius and back. The relationship is °C  =  (5/9)(°F - 32). Your program should read a temperature and which scale is used and convert it to the other, printing out the results. Write one or more functions to carry out the actual conversion.

2. Write a program that reads in the radius of a circle and prints the circle’s diameter, circumference and area. Write functions for appropriate logical tasks in your program. You should #define all appropriate constants in your program.

3.  Write and test a function to convert an unsigned integer to binary notation. Use the sizeof operator to make the program machine independent i.e. portable.

4.  Write and test two functions, one that packs two character variables into one integer variable and another which unpacks them again to check the result.

5. Write and test a circular shift left function  for one byte unsigned variables i.e. unsigned characters.

e.g.    10011100   circular shift left by 2  yields  01110010.

6.  An integer is said to be prime if it is divisible only by one and itself. Write a function which determines whether an integer is prime or not. To test your function write a program that prints out all prime numbers between 1 and 10,000.

7.  Write and test a function to read in a signed integer from the standard input device. Your function should not be allowed to use the scanf function. The function should have the prototype

                             int getint( void ) ;

and should be able to accommodate the presence of a minus sign, a plus sign or no sign ( i.e. positive by default ). Note that the ASCII values of the digits 0 - 9 are consecutive, 48 - 57 and that if we wish to convert the digit ‘3’ for example to an integer we simply subtract the ASCII value of digit ‘0’ from its ASCII value i.e. ‘3’ - ‘0’ = 51 - 48 = 3.

8.  Write and test a function to get a floating point number from the standard input device. The function should have the prototype

                             float getfloat( void ) ;

and should be able to accommodate normal floating point notation and exponential notation. You should make use of the getint() function in exercise 5. to read in the various components of the floating point number.

9.  Write and test a function
                              double  power ( double x,  int n );

to calculate the value of x raised to the power of n.

 (a)  Use your own calculations.

 (b)  Use the standard library function pow() ( for more information use help system ) paying particular attention to type compatibilities.

10. Write a recursive function to calculate and print out all the Fibonacci values up to and including the nth . Recall that the Fibonacci series is 1, 1, 2, 3, 5, 8, 13, ... .To test your program allow the user to enter the value of n and then print out the series. The program should run continuously until it is explicitly terminated by the user.

11.  Programming Assignment : Non-linear Equations.

Your basic task in this assignment is to write a C program which will allow the user to solve the following non-linear equation
 
to an accuracy of 10-5 using both the Bisection method and Newton's method.
 
In order to be able to solve equations using these methods you will need to provide your program with suitable initial estimates to the actual root, an interval over which the sign of f(x) changes in the case of the Bisection method and an initial estimate x0 where f'( x0 ) is not very small in the case of Newton's method.

To help the user to provide such estimates your program should first tabulate the function f(x), repeatedly until the user is satisfied, over a user specified interval [ x1, x2] and using a user specified tabulation step.

This should allow the user to begin by tabulating the function over a large interval with a coarse tabulation step and to fine down the interval and step until he / she can determine the behaviour of f(x) in or about the actual root.

Once the user is satisfied with the tabulation your program should read in the appropriate initial estimates from the user and test their validity and then solve the equation using both methods. Finally your program should compare the number of iterations required to compute the root using both methods and print out the value of the root.

You should break your program up into appropriate logical functions and try to intercept all erroneous situations as soon as possible and take appropriate action.

Note : The exact solution to the above equation is 1.595869359.

No comments:

Post a Comment