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 ).
(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.
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.