Tuesday, 11 April 2017

Control Statements




Expressions and Statements

As mentioned at the outset every C program consists of one or more functions which are just groups of instructions that are to be executed in a given order. These individual instructions are termed statements in C.

We have already seen some simple examples of C statements when introducing the set of C operators. For example

                        x = 0 ;

is a simple statement that initialises a variable x to zero using the assignment operator. The statement is made up of two parts : the assignment operation and the terminating semi-colon. The assignment operation here is termed an expression in C. In general an expression consists of one of C’s operators acting on one or more operands. To convert an expression into a C statement requires the addition of a terminating semi-colon.

A function call is also termed an expression. For example in the hello world program the statement

                        printf( “Hello World\n” ) ;

again consists of an expression and a terminating semi-colon where the expression here is a call to the printf standard library function.

Various expressions can be strung together to make more complicated statements but again are only terminated by a single semi-colon. For example

            x = 2 + ( 3 * 5 ) - 23 ;

is a single statement that involves four different expressions.

When designing most programs we will require to build up sequences of statements. These collections of statements are called blocks and are encased between pairs of curly braces. We have already encountered these statement blocks in the case of the main function in the hello world program where the body of the function was encased in a pair of curly braces.

We will also come across statement blocks in the next few sections when we discuss some of the statements that allow us control over the execution of the simple statements.

There are two types of control statements : iteration statements that allow us to repeat one or more simple statements a certain number of times and decision statements that allow us to choose to execute one sequence of instructions over one or more others depending on certain circumstances.

Control statements are often regarded as compound statements in that they are normally combined with simpler statements which carry out the operations required. However it should be noted that each control statement is still just a single statement from the compiler’s point of view.

Iteration Statements

for statement

The for statement is most often used in situations where the programmer knows in advance how many times a particular set of statements are to be repeated. The for statement is sometimes termed a counted loop.

Syntax :  for ( [initialisation] ; [condition] ; [increment] )
                 [statement body] ;

initialisation :- this is usually an assignment to set a loop counter variable for example.
condition :- determines when loop will terminate.
increment :- defines how the loop control variable will change each time the loop is executed.
statement body :- can be a single statement, no statement or a block of statements.


NB : The square braces above are to denote optional sections in the syntax but are not part of the syntax. The semi-colons must be present in the syntax.
           

For Example : Program to print out all numbers from 1 to 100.

           #include <stdio.h>

           void main()
           {
             int x ;

             for ( x = 1;  x <= 100; x++ )
                printf( "%d\n", x ) ;
           }


Curly braces are used in C to denote code blocks whether in a function as in main() or as the body of a loop.
For Example :- To print out all numbers from 1 to 100 and calculate their sum.
                       
           #include <stdio.h>

           void main()
           {
             int x, sum = 0 ;

             for ( x = 1; x <= 100; x++ )
             {
                printf( "%d\n", x ) ;
                sum += x ;
             }
             printf( “\n\nSum is %d\n”, sum ) ;
           }

Multiple Initialisations

C has a special operator called the comma operator which allows separate expressions to be tied together into one statement.

For example it may be tidier to initialise two variables in a for loop as follows :-

           for ( x = 0, sum = 0; x <= 100; x++ )
                {
                  printf( "%d\n", x) ;
                  sum += x ;
                }

Any of the four sections associated with a for loop may be omitted but the semi-colons must be present always.

For Example :-
           for ( x = 0; x < 10;   )
                printf( "%d\n", x++ ) ;
           ...
           x = 0 ;
           for (  ; x < 10; x++ )
                printf( "%d\n", x ) ;

An infinite loop may be created as follows

           for ( ;  ;  )
                statement body ;

or indeed by having a faulty terminating condition.

Sometimes a for statement may not even have a body to execute as in the following example where we just want to create a time delay.

                        for ( t = 0; t < big_num ; t++ )  ;

or we could rewrite the example given above as follows

                        for ( x = 1; x <= 100; printf( "%d\n", x++ ) ) ;
The initialisation, condition and increment sections of the for statement can contain any valid C expressions.

for ( x = 12 * 4 ; x < 34 / 2 * 47 ; x += 10 )
    printf( “%d “, x ) ;


It is possible to build a nested structure of for loops, for example the following creates a large time delay using just integer variables.

           unsigned int x, y ;

           for ( x = 0; x < 65535; x++ )
                for ( y = 0; y < 65535; y++ ) ;

1
2
3
4
5
2
3
4
5
6
3
4
5
6
7
4
5
6
7
8
5
6
7
8
9
For Example : Program to produce the following table of values

                        #include <stdio.h>

                        void main()
                        {
              int j, k ;

              for ( j = 1; j <= 5; j++ )
              {
                for ( k = j ; k < j + 5; k++ )
                {
              printf( "%d  ", k ) ;
           }
           printf( “\n” ) ;
        }
           }

                       

while statement

The while statement is typically used in situations where it is not known in advance how many iterations are required.

Syntax :           while ( condition )
                statement body ;


For Example : Program to sum all integers from 100 down to 1.

#include <stdio.h>
void main()
{
  int sum = 0, i = 100 ;

  while ( i )
     sum += i-- ;// note the use of postfix decrement operator!
  printf( “Sum is %d \n”, sum ) ;
}

where it should be recalled that any non-zero value is deemed TRUE in the condition section of the statement.

A for loop is of course the more natural choice where the number of loop iterations is known beforehand whereas a while loop caters for unexpected situations more easily. For example if we want to continue reading input from the keyboard until the letter 'Q' is hit we might do the following.
char ch = '\0' ;     /* initialise variable to ensure it is not 'Q' */

while ( ch != 'Q' )
     ch = getche() ;
or more succinctly

while ( ( ch = getche() ) != 'Q' ) ;

It is of course also possible to have nested while loops.

For Example : Program to guess a letter.

#include <stdio.h>
void main()
{
  char ch, letter = 'c'  ;      // secret letter is ‘c’
  char finish = ‘\0’ ;
    
  while ( finish != ‘y’ || finish != ‘Y’  )    
  {
    puts( "Guess my letter -- only 1 of 26 !" );
          
    while( (ch=getchar() ) != letter )// note use of parentheses
    {
     printf( "%c is wrong -- try again\n", ch ) ;
     _flushall() ;                   // purges I/O buffer
    }
    printf ( "OK you got it \n Let’s start again.\n" ) ;

    letter += 3 ;// Change letter adding 3 onto ASCII value of letter
//  e.g. ‘c’ + 3 = ‘f’
    printf( “\n\nDo you want to continue (Y/N) ? “);
    finish = getchar() ;
    _flushall() ;
 }
     }

do while


The terminating condition in the for and while loops is always tested before the body of the loop is executed -- so of course the body of the loop may not be executed at all.
In the do while statement on the other hand the statement body is always executed at least once as the condition is tested at the end of the body of the loop.

Syntax :           do
           {
           statement body ;
           }  while ( condition ) ;
For Example : To read in a number from the keyboard until a value in the range 1 to 10 is entered.

           int i ;

           do
           {
             scanf( "%d\n", &i ) ;
             _flushall() ;
           }   while ( i < 1 && i > 10 ) ;

In this case we know at least one number is required to be read so the do-while might be the natural choice over a normal while loop.

 

 

break statement


When a break statement is encountered inside a while, for, do/while or switch statement the statement is immediately terminated and execution resumes at the next statement following the statement.

For Example :-
...
for ( x = 1 ; x <= 10  ; x++  )
{
     if ( x > 4  )
           break ;

     printf( “%d  “ , x ) ;
}              
printf( "Next executed\n" );//Output : “1  2  3  4  Next Executed”
                        ...

continue statement


The continue statement terminates the current iteration of a while, for or do/while statement and resumes execution back at the beginning of the loop body with the next iteration.

For Example :-
...
for ( x = 1; x <= 5; x++ )
{
     if ( x == 3 )
           continue ;
     printf( “%d  “, x ) ;
}
printf( “Finished Loop\n” ) ;   // Output : “1 2 4 5 Finished Loop”
                        ...

3.3 Decision Statements

if statement

The if statement is the most general method for allowing conditional execution in C.

Syntax :           if  ( condition )
                statement body ;
           else
                statement body ;
or just :
                        if ( condition )
                statement body ;

In the first more general form of the statement one of two code blocks are to be executed. If the condition evaluates to TRUE the first statement body is executed otherwise for all other situations the second statement body is executed.
            In the second form of the statement the statement body is executed if the condition evaluates to TRUE. No action is taken otherwise.

For Example : Program to perform integer division avoiding the division by zero case.
                       
#include <stdio.h>
void main()
{
 int numerator, denominator ;

 printf( "Enter two integers as follows numerator, denominator :" );
 scanf( "%d,  %d", &numerator, &denominator  ) ;

if ( denominator != 0 )
   printf( "%d / %d = %d \n", numerator, denominator,
numerator / denominator );
else
   printf( "Invalid operation - unable to divide by zero \n” );

As with all other control statements the statement  body can also involve multiple statements, again contained within curly braces.

Example :- Program to count the number of occurrences of the letter 'a' in an input stream of characters terminated with a carriage return.

#include <stdio.h>
void main()
{
  int count = 0, total = 0 ;
  char ch ;

  while (  ( ch = getchar() ) != 13 ) // 13 is ASCII value for
                                      //carriage return
  {
     if ( ch == 'a' )
     {
        count ++ ;
        printf( “\n Retrieved letter ‘a’ number %d\n”, count ) ;
     }
     total ++ ;
     _flushall() ;
  }

  printf( "\n\n %d letters a typed in a total of %d letters.",
           count, total ) ;
     }

Nested if statements

if - else statements like all other decision or iteration statements in C can be nested to whatever extent is required. Care should be taken however to ensure that the if and else parts of the statement are matched correctly -- the rule to follow is that the else statement matches the most recent unmatched if statement.


For Example :-
if ( x > 0 )
  if ( x > 10 )
    puts ( " x is greater than zero and also greater than 10 ");
  else
    puts ("x is greater than zero but less than or equal to 10");

The else clause matches the most recent unmatched if clause, if ( x > 10 ). For more clarity the above section could be rewritten as follows using curly braces with no execution penalty :-

if ( x > 0 )
{
 if ( x > 10 )
  puts ( " x is greater than zero and also greater than 10 ");
 else
  puts ( "x is greater than zero but less than or equal to 10 ");
}
                       

if - else - if  ladder

When a programming situation requires the choice of one case from many different cases successive if statements can be tied together forming what is sometimes called an if-else-if ladder.

Syntax :           if  ( condition_1 )
                statement_1 ;
           else if ( condition_2 )
                statement_2 ;
           else if ( condition_3 )
                statement_3 ;
           ...

           else if ( condition_n )
                statement_n ;
           else
                statement_default ;

Essentially what we have here is a complete if-else statement hanging onto each else statement working from the bottom up.

For Example : Another guessing game.

void main()
{
 int secret = 101, guess, count = 0 ;
    
 printf( “\n Try and guess my secret number.\n\n” ) ;

 while ( 1 )         // infinite loop until we break out of it
 {
  printf( “\n Make your guess: ” ) ;
  scanf( “%d”, &guess ) ;
  count ++ ;

  if (   guess < secret )
printf( “\nA little low. Try again.” ) ;
  else if ( guess > secret )
printf( “\nA little high. Try again.” ) ;
  else
  {
   printf( “\nOk you got it and only on attempt %d.”, count );
   break ;
   }
  }
}


NB : Caution is advisable when coding the if-else-if ladder as it tends to be prone to error due to mismatched if-else clauses.



Conditional Operator :-  ?:


This is a special shorthand operator in C and replaces the following segment

     if ( condition )
           expr_1 ;
     else
           expr_2 ;

with the more elegant

     condition ? expr_1 : expr_2 ;

The ?: operator is a ternary operator in that it requires three arguments. One of the advantages of the ?: operator is that it reduces simple conditions to one simple line of code which can be thrown unobtrusively into a larger section of code.

For Example :-  to get the maximum of two integers, x and y, storing the larger in max.

           max =  x >= y  ? x  :  y  ;

The alternative to this could be as follows

           if ( x > = y  )
                max = x ;
           else
                max = y ;

giving the same result but the former is a little bit more succinct.


The switch Statement


This is a multi-branch statement similar to the if - else ladder (with limitations) but clearer and easier to code.

Syntax :           switch ( expression )
                {
                case constant1 :   statement1 ;
                           break ;

                case constant2 :   statement2 ;
                           break ;

                ...

                default :   statement ;
                }

The value of expression is tested for equality against the values of each of the constants specified in the case statements in the order written until a match is found. The statements associated with that case statement are then executed until a break statement or the end of the switch statement is encountered.

When a break statement is encountered execution jumps to the statement immediately following the switch statement.

The default section is optional  -- if it is not included the default is that nothing happens and execution simply falls through the end of the switch statement.

The switch statement however is limited by the following

·      Can only test for equality with integer constants in case statements.

·      No two case statement constants may be the same.

·      Character constants are automatically converted to integer.


For Example :-  Program to simulate a basic calculator.

#include <stdio.h>
void main()
{
  double num1, num2, result ;
  char op ;

  while ( 1 )
  {
    printf ( " Enter number operator number\n" ) ;
    scanf ("%f %c %f", &num1, &op, &num2 ) ;
    _flushall() ;

    switch ( op )
    {
     case '+' :  result = num1 + num2 ;
                        break ;
     case '-' :   result = num1 - num2 ;
                        break ;
     case ‘*’ :  result = num1 * num2 ;
                        break ;
     case ‘/’ :  if ( num2 != 0.0 ) {
result = num1 / num2 ;
break ;
}
     // else we allow to fall through for error message
    
     default :  printf ("ERROR -- Invalid operation or division
                          by 0.0" ) ;
    }
    printf( "%f %c %f = %f\n", num1, op, num2, result) ;
  }  /* while statement  */
}

NB : The break statement need not be included at the end of the case statement body if it is logically correct for execution to fall through to the next case statement (as in the case of division by 0.0) or to the end of the switch statement (as in the case of default : ). 

Efficiency Considerations

In practical programming the more elaborate algorithms may not always be the most efficient method of designing a program. In fact in many situations the simple straightforward method of solving a problem ( which may be disregarded as being too simplistic ) is quite often the most efficient. This is definitely true when program development time is taken into consideration but is also true in terms of the efficiency of the actual code produced.

Nevertheless quite apart from algorithmic considerations, there are a number of areas we can focus on in the code itself to improve efficiency.

One of the most important efficiency indicators is the time it takes for a program to run. The first step in eliminating sluggishness from a program is to identify which parts of the program take the most time to run and then to try and improve these areas. There are many profiling tools available which will help to establish these areas by timing a typical run of the program and displaying the time spent in each line of code and the number of times a each line of code is executed in the program. A small improvement in the efficiency of a single line of code that is called many times can produce dramatic overall improvements.

Most sources of inefficiency result from the inadvertent use of time expensive operations or features of the language. While modern compilers contain many advanced optimising features to make a program run faster in general the more sloppy the code the less improvements can be made by these optimisations. Therefore it is important to try and eliminate as much inefficiency as possible by adopting some simple guidelines into our programming practices.

Unnecessary Type Conversions

In many situations the fact that C is weakly typed is an advantage to the programmer but any implicit conversions allowed in a piece of code take a certain amount of time and in some cases are not needed at all if the programmer is careful. Most unnecessary conversions occur in assignments, arithmetic expressions and parameter passing.

Consider the following code segment which simply computes the sum of a user input list of integers and their average value.

           double average, sum = 0.0 ;
           short value, i ;          
           ...
           for ( i=0; i < 1000; i ++ ) {
                scanf( “%d”, &value ) ;
                sum = sum + value ;
                }
           average = sum / 1000 ;

1. The conversion from value, of type short int, to the same type as sum, type double, occurs 1000 times in the for loop so the inherent inefficiency in that one line is repeated 1000 times which makes it substantial.

If we redefine the variable sum to be of type short we will eliminate these conversions completely. However as the range of values possible for a short are quite small we may encounter overflow problems so we might define sum to be of type long instead.
The conversion from short to long will now be implicit in the statement but it is more efficient to convert from short to long than it is from short to double.
2. Because of our modifications above the statement

           average = sum / 1000 ;

now involves integer division which is not what we require here. ( Note however that an implicit conversion of 1000 from int to long occurs here which may be simply avoided as follows :-

           average = sum / 1000L ;

with no time penalty whatsoever as it is carried out at compile time.)

To remedy the situation we simply do the following :-

           average = sum / 1000.0 ;

3. The statement
           sum = sum + value ;

also involves another source of inefficiency. The variable sum is loaded twice in the statement unnecessarily. If the shorthand operator += were used instead we will eliminate this.

           sum += value ;                       

Unnecessary Arithmetic            


In general the lowest level arithmetic is more efficient especially in multiplication and division which are inherently expensive operations.

For Example :-
           double d ;
           int i ;
          
           d = i * 2.0 ;
                       
This operation requires that the variable i is converted to double and the multiplication used is then floating point multiplication.

If we instead write the statement as

           d = i * 2 ;

we will have integer multiplication and the result is converted to double before being assigned to d. This is much more efficient than the previous and will give the same result ( as long as the multiplication does not overflow ).

Again very little will be saved in a single such operation but when one of many the saving may amount to something for example the expression

           2.0 * j * k * l * m

where j, k, l and m are integers might involve four floating point multiplications rather than four integer multiplications when coded with efficiency in mind.

The use of the ‘to the power of ‘ function, pow() in C, is another common example of unnecessary arithmetic.

Computing the value of num2 or num3 for example should never be done in a program using the pow function especially if num is an integer. This is because there is an overhead in actually calling the pow function and returning a value from it and  there is an overhead if a type conversion has to be made in passing the parameters to pow() or assigning the return value from the function. Instead straightforward multiplication should be used i.e.

           num * num
rather than
           pow( num, 2 ) ;

When large powers are involved it does make sense to use the pow function but again the situation should be evaluated on its own merit.

For example if we want to print a table of numn where n = 1 ... 99. If we do the following

           double num ;
           int k ;
           for ( k = 1; k <100; k++ )
             printf(“%lf to %d = %lf\n”, num, k, pow( num, k ));

we will end up with approximately  Ã¥n = 4950  multiplications plus 99 function calls. Whereas if we had used
           double sum = num ;
           for ( k = 2; k <= 100; k++ )
                {
                printf( “%lf to %d = %lf\n”, num, k, sum ) ;
                sum *= num ;
                }

we will require just ( n -1 ) i.e. 98 multiplications in total.


C’s Bit-wise operators may also be used to improve efficiency in certain situations.

Computing the value of 2n can be done most efficiently using the left shift operator i.e.

           1 << n


Determining whether a value is odd or even could be done using

           if ( num % 2 )
                printf( “odd” ) ;
           else
                printf( “even” ) ;

but it is more efficient to use

           if ( num & 1 )
                printf( “odd” ) ;
           else
                printf( “even” ) ;

3.5 Exercises


1. Write a program which prints out the ASCII and hex values of all characters input at the keyboard terminating only when the character  `q' or `Q' is entered.

2. Write a program to keep count of the occurrence of a user specified character in a stream of characters of known length ( e.g. 50 characters ) input from the keyboard. Compare this to the total number of characters input when ignoring all but alphabetic characters.

Note: The ASCII values of 'A'...'Z' are 65...90 and 'a'...'z' are 97...122.

3. Write a program to find the roots of a user specified quadratic equation.

Recall the roots 

The user should be informed if the specified quadratic is valid or not and should be informed how many roots it has, if it has equal or approximately equal roots (b2 == 4ac), if the roots are real
(b2 - 4ac > 0) or if the roots are imaginary (b2 - 4ac < 0). In the case of imaginary roots the value should be presented in the form ( x + i  y ).

Note that C has a standard library function sqrt( ), which returns the square root of its operand, and whose prototype can be found both in the help system.

NB : The floating point number system as represented by the computer is “gappy”. Not all real numbers can be represented as there is only a limited amount of memory given towards their storage. Type float for example can only represent seven significant digits so that for example 0.1234567 and 0.1234568 are deemed consecutive floating point numbers. However in reality there are an infinite number of real numbers between these two numbers. Thus when testing for equality, e.g. testing if b2 == 4ac, one should test for approximate equality i.e. we should test if
b2-4ac < 0.00001 where we are basically saying that accuracy to five decimal places will suffice.

4. Write a program that allows the user to read a user specified number of double precision floating point numbers from the keyboard. Your program should calculate the sum and the average of the numbers input. Try and ensure that any erroneous input is refused by your program, e.g. inadvertently entering a non-numeric character etc.

5. Write a program to print out all the Fibonacci numbers using short integer variables until the numbers become too large to be stored in a short integer variable i.e. until overflow occurs.

      a. Use a for loop construction.
      b. Use a while loop construction.

Which construction is most suitable ?

Note: Fibonacci numbers are (1,1,2,3,5,8,13,...

5. Write a program which simulates the action of a simple calculator. The program should take as input two integer numbers then a character which is one  of +,-,*,/,%.  The numbers should be then processed according to the operator input and the result printed out. Your program should correctly intercept any possible erroneous situations such as invalid operations, integer overflow, and division by zero.

No comments:

Post a Comment