Stats Meth 420
Stats Meth 420
SECTION I
UNIT 1: INTRODUCTION TO ALGORITMS AND C
Data Concepts : Variables, Constants, data types like : int, float char,
double and void.
Qualifiers : Short and ling size qualifiers, signed and unsigned qualifiers.
Declaring variables. Scope of the variables according to block. Hierarchy
of data types.
2
UNIT II : BASIC OF C
SECTION II
UNIT IV: FUNCTIONS, STRUCTURES, RECURSION AND UNION
File Handling : Different types of files like text and binary, Different types
of functions fopen(), fclose(), fputc(), fscanf(), fprintf(), getw(), putw(),
fread(), fwrite(), fseek()
Recommended Books :
1
FUNDAMENTALS OF ALGORITHMS
Unit Structure
1.0 Objectives
1.1 Introduction
1.2 An Overview
1.0 OBJECTIVES
1.1 INTRODUCTION
To write any program in any language, you require the concepts that is
business logic. These concepts are nothing but the step by step procedure. This
procedure consists of input, process and output. This is generally a thinking
process. One program can have multiple logics or concepts.
1.2 AN OVERIVEW
Definition 1.1
Definition 1.2
An algorithm is a set of rules that specify the order and kind of arithmetic
operations that are used on specified set of data.
Definition 1.3
Definition 1.4
Definition 1.5
* an algorithm is finite
For each algorithm, especially a new one, we should ask the following basic
questions:
* does it terminate?
* is it correct?
The internet enables people all around the world to quickly access and retrieve
large amounts of information. In order to do so, clever algorithms are employed
to manage and manipulate this large volume of data. Examples of problems
which must be solved include finding good routes on which the data will travel.
There are also decision problems that are NP-hard but not NP-complete,
for example the halting problem. This is the problem which asks "given a
program and its input, will it run forever?" That's a yes/no question, so this
is a decision problem. It is easy to prove that the halting problem is NP-
hard but not NP-complete. For example, the Boolean satisfiability problem
can be reduced to the halting problem by transforming it to the description
of a Turing machine that tries all truth value assignments and when it finds
one that satisfies the formula it halts and otherwise it goes into an infinite
loop. It is also easy to see that the halting problem is not in NP since all
problems in NP are decidable in a finite number of operations, while the
halting problem, in general, is not. There are also NP-hard problems that
are neither NP-complete nor undecidable. For instance, the language of
True quantified Boolean formulas is decidable in polynomial space, but
not non-deterministic polynomial time.
I] A notation – Little Oh
f ( x)
lim = 0.
x →∞ g ( x)
For example,
• 2 x ∈ o( x 2 )
• 2 x 2 ∈ o( x 2 )
• 1/ x ∈ o(1)
o( f ) + o( f ) ⊆ o( f )
o( f ) + o( g ) ⊆ o( fg )
o(o( f )) ⊆ o( f )
o ( f ) ⊂ o( f )
(and thus the above properties apply with most combinations of o and O).
As with big O notation, the statement "f(x) is o(g(x))" is usually written as f(x) =
o(g(x)), which is a slight abuse of notation.
Let f(x) and g(x) be two functions defined on some subset of the real numbers.
One writes ??????
if and only if, for sufficiently large values of x, f(x) is at most a constant times
g(x) in absolute value. That is, f(x) = O(g(x)) if and only if there exists a positive
real number M and a real number x0 such that
In many contexts, the assumption that we are interested in the growth rate as
the variable x goes to infinity is left unstated, and one writes more simply that
f(x) = O(g(x)).
11
The notation can also be used to describe the behavior of f near some real
number a (often, a = 0): we say
f ( x ) = O ( g ( x )) as x → a
| f ( x ) |≤ M | g ( x ) | for | x − a |< δ
f ( x)
| f ( x ) = O ( g ( x )) as x → a if and only if lim sup <∞
x→a g ( x)
Example of Big oh
• If f(x) is a sum of several terms, the one with the largest growth rate is
kept, and all others omitted.
• If f(x) is a product of several factors, any constants (terms in the product
that do not depend on x) are omitted.
For example, let f(x) = 6x4 − 2x3 + 5, and suppose we wish to simplify this
function, using O notation, to describe its growth rate as x approaches infinity.
This function is the sum of three terms: 6x4, −2x3, and 5. Of these three terms,
the one with the highest growth rate is the one with the largest exponent as a
function of x, namely 6x4. Now one may apply the second rule: 6x4 is a product
of 6 and x4 in which the first factor does not depend on x. Omitting this factor
results in the simplified form x4. Thus, we say that f(x) is a big‐oh of (x4) or
mathematically we can write f(x) = O(x4).
One may confirm this calculation using the formal definition: let f(x)= 6x4 − 2x3 +
5 and g(x) = x4. Applying the formal definition from above, the statement that
f(x) =O(x4) is equivalent to its expansion,
12
| f ( x ) |≤ M | g ( x ) |
for some suitable choice of x0 and M and for all x > x0. To prove this, let x0= 1 and
M = 13. Then, for all x > x0:
| 6 x 4 − 2 x3 + 5 |≤ 6 x 4 + | 2 x3 | +5
≤ 6 x4 + 2 x4 + 5x4
= 13 x 4 ,
= 13 | x 4 |
so
| 6 x 4 − 2 x3 + 5 |≤ 13 | x 4 | .
Usage of Big oh
There are two formally close, but noticeably different, usages of this notation:
infinite asymptotics and infinitesimal asymptotics. This distinction is only in
application and not in principle, however—the formal definition for the "big O"
is the same for both cases, only with different limits for the function argument.
Big O notation is useful when analyzing algorithms for efficiency. For example,
the time (or the number of steps) it takes to complete a problem of size n might
be found to be T(n) = 4n2 − 2n + 2.
As n grows large, the n2 term will come to dominate, so that all other terms can
be neglected — for instance when n = 500, the term 4n2 is 1000 times as large as
the 2n term. Ignoring the latter would have negligible effect on the expression's
value for most purposes.
13
T ( n) = O ( n 2 )
or
T ( n) ∈ O ( n 2 )
Note that "=" is not meant to express "is equal to" in its normal mathematical
sense, but rather a more colloquial "is", so the second expression is technically
accurate (see the "Equals sign" discussion below) while the first is a common
abuse of notation.[1]
x2
ex = 1 + x + + O( x3 ) as x → 0 expresses the fact that the error, the
2
difference e x − (1 + x + x 2 / 2) is smaller in absolute value than some constant
times | x3 | when x is close enough to 0.
Properties of Big oh
If a function f(n) can be written as a finite sum of other functions, then the
fastest growing one determines the order of f(n).
For example f (n) = 9 log n + 5(log n)3 + 3n 2 + 2n3 ∈ O(n3 ).
14
O(nc) and O(cn) are very different. The latter grows much, much faster,
no matter how big the constant c is (as long as it is greater than one). A function
that grows faster than any power of n is called superpolynomial. One that grows
more slowly than any exponential function of the form cn is called
subexponential. An algorithm can require time that is both superpolynomial and
subexponential; examples of this include the fastest known algorithms for
integer factorization.
Changing units may or may not affect the order of the resulting
algorithm. Changing units is equivalent to multiplying the appropriate variable
by a constant wherever it appears. For example, if an algorithm runs in the order
of n2, replacing n by cn means the algorithm runs in the order of c2n2, and the
big O notation ignores the constant c2. This can be written as c 2 n 2 ∈ O( n 2 ). If,
however, an algorithm runs in the order of 2n, replacing n with cn gives 2cn =
(2c)n. This is not equivalent to 2n in general.
Changing of variable may affect the order of the resulting algorithm. For
example, if an algorithm's running time is O(n) when measured in terms of the
number n of digits of an input number x, then its running time is O(log x) when
measured as a function of the input number x itself, because n = Θ(log x).
15
For any k > 0 and c > 0, O(nc(logn)k) is a subset of O(nc + a) for any a > 0, so may be
considered as a polynomial with some bigger order.
Check your progress
1) Give the difference between the various notations Little Oh, Big Oh and
Omega.
17
Our computing agent must be able to keep track of a variety of values needed in
executing the algorithm. The values involved in the execution of an algorithm
make up the state of the computation. The state is dynamic over the execution
time of the algorithm, as values associated with particular attributes of the
algorithm may change as the algorithm executes.
As algorithm writers, we need a way to describe the various values in the state
of the computation. Each value will be describe with a name, written in
lowercase letters, and called a variable. For example, we may want to write an
algorithm that outputs the integers between one and ten. In order to know
which value to output next, we could establish a variable count that will
represent the count to output next.
Sometimes we need to keep track of lists of associated values, for example a list
of ten names. Rather than think up ten different variable names, we can use list
notation and refer to name[1], name[2], ..., name[10] to refer to the ten names.
We say that name is the list of values and that the integer in [] is the index
indicating which value in the list we are referring to.
When using a list of values, we may use a variable to keep track of which item in
the list we are interested in. For example we may have a variable i that keeps
track of which name we want to look at. name[i] would then refer to the value
in list name found at the index indicated by the value of i.
Any of the following may be used to indicate the agent is outputting information
to the user: Output Print Display
The command can be followed by text in quotes, which is output verbatim, and
variables, whose value is output. Some examples:
Input from the user is indicated by Input followed by a list of variables that will
store the values input from the user.
For example, Input in inputs a single value, storing it into the variable named n.
Algorithms need to be able to change the value of variables. To set the value of
a variable to a new value we will use the following command:
Set variable To value Where variable is the name of the variable whose value
the algorithm is changing and value is an expression whose value should be
stored in the variable.
Many times, the expressions set into the variable are mathematical expressions.
The usual operators +, ‐, *, / are available. We also have two other operators,
div and mod. The value x div y is the integer portion of x / y. The value x mod y is
the integer remainder of x / y. For example, 7 div 2 is 3 and 7 mod 2 is 1
(because 2 goes into 7 three times with a remainder of 1).
Expressions can also be boolean. Boolean expressions have only two possible
values, true or false. The operators used in boolean expressions include the
usual comparison operators: <, ≤, >, ≥, = and logical operators, AND, OR, NOT.
20
To describe a point in the algorithm in which steps are executed only if some
condition is true, we use the if statement. if (condition) then BEGIN statements...
END statements... indicates any statements may be placed between the
BEGIN/END. The statements are executed only if the condition expression
evaluates to true.
Iteration
Repeat
statements...
Until (condition)
executes the statements inside the Repeat/Until block, then tests the condition.
If the condition is false, the statements are executed again. Note that the
statements are always executed at least once.
RepeatWhile (condition)
BEGIN
statements...
END
tests the condition. If it is true, the statements inside the BEGIN/END block are
executed, and then the condition is tested again. When the condition is false,
control falls out of the loop.
Example
The following algorithm inputs an integer and outputs the values between 1 and
that value.
Input n
Set count To 1
BEGIN
Output count
END
Thus, we have studied the basics of algorithm. How the real time problems can
be solved by algorithms as well as the different notations are used to write
algorithm that are studied. Thus, algorithms are very much helpful to develop
the logical concepts on paper.
2) http://lcm.csa.iisc.ernet.in/.
22
Bubble sort, selection sort and insertion sort have average complexity of
O(n^2)... merge sort and heap have complexity O(nlog n)....and quick sort
has O(n log n ) for average
case ...its worst case...
The best, worst and average case complexity refer to three different
ways of measuring the time complexity (or any other complexity
measure) of different inputs of the same size. Since some inputs of size
n may be faster to solve than others, we define the following
complexities:
2
ALGORITHMS PROBLEMS AND ANALYSIS OF
ALGORITHMS
Unit Structure
2.0 Objectives
2.1 Introduction
2.0 OBJECTIVES
After going through this chapter, you will be able to:
• Develop fundamental algorithms
• Analyze algorithms in terms of time and space complexities
• Write different algorithms for different problems
25
2.1 INTRODUCTION
Whenever the algorithms are written, the two things are considered.
First one is the time complexity which will check the time utilization of CPU as
well as other resources. And second thing is space complexity which will check
the space utilizarion of primary memory. There has to be immediate output and
minimum space utilization. These are the different factors such as turn around
time, throughput, etc
All it needs to know is what exchange arguments look like. This way we can put
the exchange function after our main function. We could have easily put
exchange before main and gotten rid of the declaration. The following code
segment will fix exchange to use pointers, and move exchange above main to
eliminate the need for the forward declaration.
#include <stdio.h>
int temp;
temp = *a;
*a = *b;
*b = temp;
void main()
{
27
int a, b;
a = 5;
b = 7;
exchange(&a, &b);
• You use regular variables if the function does not change the values of
those arguments
• You MUST use pointers if the function changes the values of those
arguments
Step 1
_____
(A) 10100000
(B) 00001010
------------------
(A) 10101010 XOR Result – contents of A
(B) 00001010 B is unchanged
Step 2
_____
(B) 00001010
(A) 10101010
------------------
28
Step 3
_____
(A) 10101010
(B) 10100000
------------------
(A) 00001010 XOR Result – contents of A
(B) 10100000 B is unchanged - same as after Step 2
Finally A has 00001010 and B has 10100000 (the two variables have been
swapped) and a temporary variable is not used.
Here the idea is to take the array of set of positive as well as integer values. An
array is nothing but the set of similar type of elements. Now, in this array, the
integer positive values are checked.
To solve this problem, three arrays are declared. First two arrays
will store the input values whereas the third array will store the result of
the first two arrays addition.
The following algorithm is for summation of set of numbers:
#include <stdio.h>
void main()
{
int a[50],n, b[50],I, c[50];
printf(“Enter the size of the array\n”);
scanf(“%d”,&n);
printf(“Enter the elements of the array\n”);
for I=0;I < n;I++)
scanf(“%d”,&a[I]);
for I=0;I < n;I++)
scanf(“%d”,&b[I]);
for I=0;I < n;I++)
c[I] = a[I] + b[I];
for I=0;I < n;I++)
printf(“%d”,c[I]);
}
30
rev_num += (num%10)*base_pos;
base_pos *= 10;
3)Return rev_num
4) Define main function
a. Call the above function by passing the values
b. Display the result (reversed number)
#include <stdio.h>
}
return rev_num;
}
int main()
31
getchar();
→ return 0;
To get an idea about the given problem, let us take the example and get a result.
Following is an example for the same:
152/2 = 76
152/4 = 38
152/8 = 19
#include <stdio.h>
int main()
{
int n, sum = 0, i;
scanf("%d",&n); // read the input
for(i = 1; i<n; i++)
{
if(n % i == 0)
{
32
sum += i;
}
}
if(sum == n)
{
printf("%d",n);
}
Else
{
printf("NO\n");
}
→ return 0;
}
while(i<=n)
{
c=0;
for(j=1;j<=i;j++)
{
if(i%j==0)
c++;
}
if(c==2)
printf("%d",i)
i++;
}
getch();
}
15 7 ns 100,000 ns
65 32 ns 150,000 ns
250,000 ns
1,000 500 ns
36
I] Best case
Thus, in this chapter we have studied how to develop the algorithms along with
the source code. After doing these small problems, more complex problems can
be solved by developing the algorithm. Running time of algorithms are analyzed
afterwards in terms of best case / average case / worst case.
Algorithm:
Input: num
divide by 10 to rev_num
3) Give the example of some algorithm along their run time complexity.
Ans: There are following three algorithms with their run time complexity:
It says that after a certain point (n0), f(n) is bounded above by a constant (d)
times g(n). The constant (d) helps accommodate the variation in the algorithm.
For algorithms,
An algorithm that is O(log_2(n)) takes logarithmic time. While the running time
is dependent on the size of the input, it is clear that not every element of the
input is processed.
40
3
OPERATORS IN C AND TYPE CONVERSIONS
3.0 OBJECTIVES
At the end of this chapter, you will be able to perform various types of
operations on the data values which are to be processed.
3.1 INTRODUCTION
Operators which operate upon one operand are called as unary operators.
Those which operate on two operands are called binary operators and those
which operate on three operands are called ternary operators.
The Arithmetic operators include Add, Subtract, Multiply, Divide and modulus.
Add operator is denoted by + sign. This operator adds the value of the first
operand to the second and is written as a + b
Ex. a = 5, b = 6 then a + b = 11
Subtract operator is denoted by – sign. This operator subtracts the value of the
second operand from the value of the first operand
Multiply operator is denoted by * sign. This operator multiplies the value of the
first operand with the value of the second operand.
Divide operator is denoted by / sign. This operator divides the value of the first
operand by the value of the second.
Modulus operator is denoted by % sign. This operator divides the value of the
first operand by the value of the second and the result of the operation is the
remainder.
Ex. a = 12, b = 5 a %b = 2
42
Relational operators are used to compare two operands. These operators are
used in the test conditions of the control statements (Chapter V).
Less than operator is denoted by < sign. This operator used to test whether first
operand is less than the second.
Ex. x< y
Less than or equal to operator is denoted by < = sign. This operator is used to
test whether the first operand is less than or equal to the second operand.
Ex. x <= y
Greater than operator is denoted by > sign. This operator is used to test
whether the first operand is greater than the second operand.
Ex. x> y
Greater than or equal to is denoted by > = sign. This operator is used to test
whether the first operand is greater than or equal to the second operand.
Ex. x > = y
The equality operator is denoted by = = sign and it is used to test whether the
first operand is equal to the second operand.
Ex. x= = y
43
The not equal operator is denoted by ! = sign and it is used to test whether the
first operand is not equal to the second operand.
Ex. x! = y
Logical operators AND , OR are used to join two or more test conditions and the
Logical operator NOT is used with a single condition.
The AND operator evaluates the first condition and if it is true, it evaluates the
second condition and if this condition is also true, the result of the AND
operator is true. However, if the first condition is false, the second condition is
not evaluated and the result of the AND operator is false. In other words, the
outcome of the AND operator is true only if both the conditions are true.
Ex. if (x = = 2 ¦¦ y > 3)
The OR operator evaluates both the conditions and even if any one condition is
true, the result of the OR operator is true. In other works, the outcome of the
OR operator is FALSE only if both the conditions are false.
NOT operator is used with a single condition along with the equal = sign. NOT
operator is denoted by ! and it is used to test whether an operand is not equal
to another operand.
Ex. x ! = y
44
Ex. (i) x = 20
(ii) x = a + b
They are + = , ‐ = , * = , / = , % =
Each of the operators first performs the arithmetic operation and then the
assignment operation.
Ex. int a = 21
a+ = 5 ⇒ a = a + 5 26
a − = 5 ⇒ a = a − 5 16
a* = 5 ⇒ a = a * 5 105
a/ = 5 ⇒ a = a/5 4
a % = 5 ⇒ a = a %5 1
45
Both these operators have a special property. They can be placed before or after
the operand.
If the operator is placed before the operand, it is called as prefix operator and if
it placed after the operand, it is called as postfix operator.
If the operator is a prefix operator the value of the operand changes before it is
used and if it is a postfix operator, the value of the operand changes after it has
been used. Both these operators are unary operators.
y = − − x;
Conditional operators are denoted by ‘?’ and : These operators are used in the
place of if / else statements.
Ex. int x, y;
The Bit operators include AND OR, XOR, left shift and right shift. These operators
change the value of the operand at the bit level. The value is converted into its
corresponding binary form and is then operated upon.
47
AND operator is denoted by ‘&’ and it is used to join two operands by AND. If
the corresponding bits of both the operands is 1 (ON) then the result of the
operator is 1 otherwise it is 0.
Ex.
int x = 3, y = 5,
z = x & y;
x = 3 = 00000011
y = 5 = 00000101
x & y = 00000001
=1
Ex. int x = 3, y = 5, z;
z = x | y;
x = 3 = 00000011
y = 5 = 00000101
y = 5 = 00000101
z=7
XOR is denoted by ‘ ∧ ’ and it is used to join the two operands by XOR. The result
is 1 when only one of the two operands is 1 otherwise it is zero.
Ex. int x = 3, y = 5, z;
z = x ∧ y;
x = 3 = 00000011
y = 5 = 00000101
x ∧ y = 00000110
∴z = 6
48
This operator is denoted by < < and is used to shift off the bits on the left hand
side and all vacated bits on the right hand side are replaced by zeros.
Ex. int x = 3, z;
z = x << 2;
x = 3 = 00000011
x << 2 = 00001100
z = 12
This operator is denoted by > > and is used to shift the bits on the right hand
side and all vacated bits on the left hand side are replaced by zeros.
Ex. int x = 3, z;
z = x >> 1;
x = 3 = 00000011
x >> 1 = 00000001
z =1
Ex. + + m, k ‐ = 2
49
3.2 EXPRESSIONS IN C
Ex. 3∗ a + b
Any expression produces a value. Whenever two or more operators are present
in an expression, the expression is evaluated in a particular order. This order is
called as the precedence. The operator with the highest precedence is evaluated
first and then to the next level and so on.
Associativity
The following Table shows various operators, their precedence and their
associativity.
9 ¦¦ Left to Right
50
*=,/=,%=
11 , Left to Right
Type Conversion
Whenever an expression involves operands which are not of the same type, it is
called as mixed data type expression and before such an expression is
evaluated; all operands have to be converted into the same type.
The conversion from lower data type to higher data type is done automatically
and the values of the operands are not affected.
(1) If one operand is declared as long double, the other operand gets
converted to long double.
(2) If one operand is declared as double, the other gets converted to double.
(3) If one operand is declared as float, the other gets converted to float.
Ex.
In the above example the integers 5 in (i) and 3 in (iii) get converted to float
before the division takes place and since a is declared as float the output will be
same as the outcome of the operation.
Converting data types from lower level to higher level is automatic. However
converting data type from higher level to lower level is not automatic. This can
be done explicitly and is called type cast.
int y = int( x )
This will give the value of y as 12 since the decimal part will get truncated.
(i) a = 5/3 1 1
In the above example (ii), (iii) and (iv) integers get converted to float and the
outcome of the operations are real but since a has be declared as integer, the
decimal part gets truncated in the output.
52
Illustrations
main ()
Working x y
Line 1 10 5
Line 2 8 5
Line 3 7 12
Output 712
main ()
int a = 4, b = 3, c, d ; Line 1
c = 3 ∗ a + +; Line 2
d = a + b ∗ 2; Line 3
print f ("% d ",c);
print f ("% d ",d );
return 0;
53
Working a b c d
Line 1 4 3
Line 2 5 3 12
Line 3 5 3 12 11
Output 12
11
main ()
int a, b, h, k ;
a = 12, b = 5; Line 1
h = (3 ∗ a + b) / 2; Line 2
a − = − − b, b∗ = 3; Line 3
k = (4 ∗ a − b) / 3 Line 4
print f ("% d ", h, k );
return 0;
Working a b h k
Line 1 12 5
Line 2 12 5 20
Line 3 8 12 20
Line 4 8 12 20 6
Output 206
54
main ()
int a = 6; Line 1
float b = 5.5, c; Line 2
c = − − a + b ∗ 3; Line 3
print f ("% f ", c);
return 0;
Working a b c
Line 1 6
Line 2 6 5.5
Output 20.500000
main ()
int a = 4, b = 7, c, d ; Line 1
c = a + b; Line 2
(c > 10)?20 + c : d = 40 − c; Line 3
print f ("% d ", d );
return 0;
}
55
Working a b c d
Line 1 4 7
Line 2 4 7 11
Line 3 4 7 11 31
Output 31
int a = 10, b = 4, c
c = a + b;
a + +, b + = 2;
print f ("%d %d %d ", a, b, c);
return 0;
} (Answer : 11614)
56
int a = 1, b = 5, c = 4, d ;
c + = a + +;
d = + + b ∗ 3;
print f ("%d %d ", a, c);
print f ("%d %d ", b, d );
return 0;
(Answer: 25
618)
int x = 4, y = 5, z = 2;
y − = z + +;
x∗ = 2;
print f ("%d %d %d ", x, y, z );
return 0;
(Answer: 833)
{
57
int p = 5, q = 2, r = 7;
r + = 4;
p∗ = 2, q − −;
r + = p ∗ q;
print f ("%d %d %d ", p, q, r );
return 0;
(Answer : 10121)
int l = 4, m = 3, n = 7, k ;
k = + + n;
m + = 2, l / = 2;
k = m + n − l;
print f ("%d ", k );
return 0;
(Answer : 10)
int k = 8, l = 5;
k − = l + +;
(k > l ) ? print f ("%d ", k ); printf ("%d, l);
print f ("%d ", l );
return 0;
}
58
(Answer : 6)
(Answer : 19
100
8)
}
59
(Answer : Pass = 1
Fail = 0)
int a, b, h;
a = 5, b = 8;
h = (a + b) / 2;
print f ("%d ", h);
return 0;
(Answer : 6)
int x = 5, y = 7;
x + = y − −;
print f ("%d % d ", x, y );
return 0;
(Answer : 126)
60
4
DATA INPUT AND OUTPUT FUNCTIONS
4.0 OBJECTIVES
At the end of this chapter, you will be able to input various types of data and
obtain the output in a desired form.
4.1 INTRODUCTION
This chapter covers steps (i) and (iii) i.e. you will learn how to input data and
how to obtain the output.
For this purpose, there are certain functions called as library functions. These
functions form a part of the C language.
The formatted input statement is the scanf () function and the formatted output
function is the print () function.
This function is used to input data to the computer with the help of the
keyboard.
The control string consists of format specification which specifies the type of
data to be input. It consists of the % sign and the conversion character.
f for float
The control string is read from left to right and each format specification is
assigned to the arguments from left to right. The number of format
specifications must match with the number of arguments.
this indicates that the user has to input the values of the two variables
x and y where x will take integer value and y will take floating point value.
In case of numeric and character type data, each argument is preceded by the
ampersand & sign which is called as a pointer, as shown in the above example.
However, in case of string data, the pointer is not used.
The printf () function is used to obtain the output in a desired format. The syntax
of the printf () function is as follows:
The control string gives
the format
printf ( “Control String”, list of arguments); specification.
The conversion characters are the same as for the scanf () function.
However, in the format specifications, modifiers are used to obtain the output in
a desired format. Such modifiers are also called as flags.
‐ to print the output left justified. (By default output appears right
justified 0)
w.n to specify the width along with the number of decimal places.
These modifiers are placed between the % sign and the conversion character.
We shall understand the use of the control string with the help of the following
examples:
1) int x = 1234;
.
Statement Output
b
64
(ii) width = 6. x consists of 4 digits, 2 blank spaces on the left hand side since by
default data appears right justified.
(iii) Since x does not have a sign, it is treated as positive and format specification
includes + sign. Hence output shows + sign on the left side of the value.
(iv) ‐ sign indicates that the data is left justified. Hence blank spaces appear on
the right hand side.
v) width is 6. x contains 4 digits, hence the 2 additional places are padded with
zeros.
Whenever the output requires printing float type of data, number of decimal
places can be specified using the format % w.nf. where w denotes the minimum
width, n denotes the number of decimal places.
Example
Float x = 12.3456
Statement Output
Explanation
(i) %f indicates 6 decimal places hence 2 zeros on the right hand side
(ii) no. of decimal places is less than the given value hence the 3rd decimal
gets rounded off.
(iv) ‐ sign given the output left justified hence 2 blank spaces appear on the right
hand side
(v) total width is 9 and 7 places have been consumed hence two blank spaces
or the left hand side
Example
char x = “A”;
Statement output
String type of output uses the format specification % ws, % w.ns and % ‐ w.ns
where w denotes the minimum width of the string and n denotes the first P
characters to be printed.
Example
Statement output
Unformatted Input output functions are used to input and output data without
any specifications.
We shall study the input functions and then the output functions.
4.3.1 getchar () function is used to input a single character with the help of the
standard input device, the keyboard.
When the getchar() function is used in a program, the user has to enter a
character and press the enter key so that the character is stored and displayed
on the screen. The getchar() function is linked to the header file stdio.h
67
4.3.2 getch() and getche() are used to input a single character with the help of
the key board. When a character is input using getch() and getche() functions,
there is no need to press the enter key after the character is input.
The difference between getch() and getche() function is that getche() echoes the
character input, onto the screen. These two functions are linked to the header
file conio.h
4.3.3 gets() function is used to input a string of characters. After the string has
been input, the user has to press the enter key at the end of the string. This
function is linked to the header file stdio.h
In case of an error or in case of end of the file, EOF is returned instead of the
character.
putchar() is used to display a single character on the screen. This function uses
the header file stdio.h
Examples
main ()
{char k;
k = getchar();
putchar(k);
return 0;
Output A
{ char k;
69
k = getch();
putch(k);
return 0;
Output A
{char k;}
k= getche();
putch(k);
return 0;
Input ‘K’ (No need of enter key. But value is echoed on screen)
Output K
gets(city);
puts (city);
return 0;
Output DELHI
70
(a) getchar()
(b) getch()
(c) gets()
(d) puts()
main()
return 0;
Output:
bb 12 ‐ 27 bbb
71
b 24.92
main()
return 0;
Pbbbbbbb 16.2700
main()
return 0;
Output ‐ 37200.920000
main ()
int z = 16;
return 0;
Output:
abb12.500000016
bbbabbbb13 +16 b
73
5
Control Statements for Decision Making
Unit Structure
5.0 Objectives
5.1 Introduction
5.2 Branching Statements
5.4.2 do loop
5.0 OBJECTIVE
5.1 INTRODUCTION
Branching statements are used to check a test condition and depending upon
the outcome of the test condition, branch to the corresponding statement. The
Branching statements include:
(i) If Statement
5.2.1 If Statement
Format I
next statement;
In this format, the computer checks the test condition and if the condition is
true, statement 1 gets executed and then the next statement is executed. If the
test condition is false, statement 1 is ignored and directly the next statement is
executed.
Example
# include <stdio.h>
main()
{ int x ;
scanf(”%d”, & x ),
if (x > 10) x + = 5;
printf (”%d”, x );
return 0;
In the above example, the user has to input the value of x. If this value is greater
than 10, x increases by 5 and then its value gets printed. However if x < = 10, the
value of x does not change before printing.
Format II
else statement 2;
next statement
76
Example.
# include <stdio.h>
main()
{ int marks;
printf (”PASS” );
else
printf( “FAIL” );
return 0;
In the above example, if the marks input are more than or equal to 35 PASS is
printed otherwise FAIL is printed.
if (test condition 1)
statement 1;
else
if (test condition 2)
statement 2;
else statement 3;
77
Example
Sales Rate
Solution:
# include <stdio.h>
main()
scanf ( “ %f ”, &sales );
com = 0;
else
if (sales = 8000)
else
else
return 0;
If and if / else statements can be used if a test condition gives only 2 outcomes
true or false. However, there arise situations where there may be more than 2
outcomes for a test condition. In such cases, switch statement is used.
switch (expression)
default : statement K; }
The expression following switch is evaluated. This value is matched with the
various case constants and when a match is found, the corresponding statement
is executed.
The break statement breaks the loop, i.e. the remaining statements are ignored
by the computer and the control is transferred out of the loop.
79
Default statement ‐
Inside the switch() loop is the default statement. This statement is executed
when none of the case constants match with the expression.
Note that the default statement does not require a break statement since it is
the last statement in the loop.
Example
# include <stdio.h>
main ()
{ int marks;
char grade;
return 0;
In the above example if the marks entered are 84, marks / 10 = 8 and hence
grade will be B and if no match is found grade will be F.
80
While loop is used to execute a set of Statements called the body, repeatedly as
long as the test condition is true.
The test condition is checked and if the condition is true the body of the loop is
executed and the control is transferred back to the test condition. This process
is repeated as long as the test condition is true and when the test condition is
false, the control is transferred to the next statement after the loop.
main ()
{s + = i ;
i++;
return 0;
do
The do‐while loop is executed as follows. The body of the loop will be executed
once and then the test condition will be checked. If it is true, the control is
transferred back to the loop. This process continues as long as the test condition
is true and when the test condition is false, the control is transferred to the next
statement after the loop.
82
Example
Calculate and print the sum 252 + 232 + .......... + 12 using the do‐while
loop
Solution:
# include <stdio.h>
main()
do
{s + = i*i;
i ‐ =2;
printf ( “sum=%d”, s );
return 0;
(i) In the while loop, the test condition is placed before the body of the loop and
if the test condition is not true, the body of the loop will not be executed even
once.
(ii) In the case of the do‐while loop statements the test condition is placed after
the body of the loop. Hence, the body of the loop is executed at least once.
83
The for statement is a loop statement which allows the user to execute a set of
statements repeatedly, desired number of times.
body
The for loop is executed as follows exp 1 is executed once at the beginning of
the loop where the initial value is assigned to the variable.
The body of the loop is executed and at the end of the loop the control is
transferred to exp 3 where the value of the variable gets altered.
The control is then transferred to exp. 2 where the test condition is checked if
the condition is true, the body of the loop gets executed.
This process continues as long a the test condition is true and when the test
condition is false, the loop gets terminated and control is transferred to the next
statement after the loop.
84
Example
Solution:
main ()
{int i, s=0;
{ s + = i;
return 0;
Jump statements are used to exit a loop and transfer control to different
statements in the program.
The break statement is used to exit from the while, do‐while, for and switch
statements and transfer control to the statement following the loop statement.
If the break statement is used with a nested loop, it only exits the innermost
loop and control is transferred to the next statement outside the loop.
# include <stdio.h>
main()
{ int i ; s = 0;
for ( i = 4; i < 7; i + +)
{ if (i % 2! = 0) break;
printf ( “% ‐ 4d”, i );
return 0;
Output: 4 bbb
The continue statement is used with the while, do‐while and for statements.
86
Whenever the continue statement is given in the loop, it breaks the loop and
transfers control to the next iteration of the loop.
In case of the while and do‐while loop, the continue statement causes the
control to transfer to the test condition and then continues the execution as a
normal loop statement.
In case of the for loop, the continue statement causes the control to be
transferred to the next iteration and then to the test condition, after which the
execution continues like a normal for loop.
# include <stdio.h>
main ()
{int i;
for ( i = 1; i < 8; i + +)
if (i % 2! = 0)
continue;
printf ( “\ n %d”, i ); }
return 0;
Output will be
6
87
The goto label statement is a jump statement. It causes the computer to locate
the identifier label and execute the statement following the label.
goto LABEL;
.....................
......................
LABEL : statement;
Example
# include <stdio.h>
{ int age;
return 0;
The label name follows the rules of identifiers and is followed by a colon.
88
(i) Goto... label should not be used in the middle of the body of the loop
statement
(ii) Goto... label should not be used in the middle of the switch statement.
S = 22+52+82+.................502
# include <stdio.h>
main()
{int i, s;
s=0;
{s + = i * j;}
printf ( “%d”, s );
return 0;
S = 2 x 3 + 4 x 5 + 6 x 7 + ...........+20x21
# include <stdio.h>
main()
{ int i, j, s;
s=o;
{s + = i * j} ;
printf ( “sum=%d”, s );
return 0;
3) Write a program to input Sales and calculate and print the sales tax as follows
# include <stdio.h>
main()
else
else
else
return 0;
4) Write a program to calculate and print the greatest common divisor between
2 positive integers
main()
{ int. x, y;
While (x ! = y )
{ if (x > y)
x = x ‐ y;
(else y = y ‐ x;
printf ( “GCD = %d ”, x );
return 0;
5) Write a program to input 10 values of x and calculate and print the arithmetic
mean and the standard deviation. Where mean
=
∑ x , sd = ∑ x 2
− (mean) 2
N N
# include <stdio.h>
# include <math.h>
91
main ( )
{int i, x, s, s1;
s=0, s1 = 0;
{ scanf ( “ %d ”, & x );
s + = x; s1+ = x * x;
mean = s/10;
return 0;
else
else
else
printf ( “Teacher” );
switch (Code)
main()
{int i=1;
while (i < 5)
i+=2;}
return 0; }
Output will be
bbb 1
bbb 3
main()
int i = 10, s = 0;
do
{s+=2*i;
i ‐ = 3;
93
return 0;
S=42
main ()
{ int i;
{ if (i%2 == 0) continue;
printf ( “% 5d”, i );
return 0;
Bbbb3bbbb5
main ()
{ int i; s=0;
{ if (i%3 == 0) break;
printf ( “%d”, i );
s+=i;
94
return 0;
57
12
v) # include <stdio.h>
{ int i = 8;
do
{printf ( “%d”, i );
i/=2;
return 0;
84
2. Write a program to print all integers between 75 and 125 which are divisible
by 8, using the while loop.
4. Write a program to input the annual income and calculate the income tax as
follows.
Income Tax
Excess 30%
5. Write a program to input the number of calls made by the subscriber and
calculate and print the telephone bill as follows:
Print the name of subscriber, telephone no., no of calls and bill amount.
a) switch statement?
b) for statement
c) while statement
96
d) do‐while statement.
a) break statement
b) default statement
c) continue statement
else
else
else
if printf ( “peon” );
97
main ()
{a=5, b=3;
do
{ a + = b;
b + + ;}
while (a<15);
return 0;
main ()
{ a = a ‐ b;
b + = 5;
return 0;
7520
98
5525
main()
{ int x = 10, i ;
{if (x %2 = = 0)
x + +;
else
X‐‐;
printf ( “\n%d”, x ),
return 0;
10
11
10
11
10
main()
{int i;
{(i %3)! = 0)
continue;
99
printf ( “%d\n”, i );
return 0;
V) # include <Stdio>
main ()
{int i = 10, k = 7;
K+i;
i + 3; }
printf ( “%‐4d”, k );
→ return 0;
6
ARRAYS AND STRINGS
Unit Structure
6.0 Objectives
6.1 Introduction
6.0 OBJECTIVES
6.1 INTRODUCTION
In the previous chapters we have used some basic data types like int, float,
double and char type. All these data types have a basic limitation and that is
they can store only one value at a time. Due to this inherent limitation they can
store only small amount of data and can be used in applications which do not
require large volumes of data. In real life applications it is more likely that we
will encounter larger amounts of data. It is important and interesting to note
that one variable of any type can actually handle a large amount of data but not
simultaneously. For e.g. if we wish to find the average of hundred numbers we
do not need hundred variables to store them as we can process them one at a
time. The problem would arise if we wanted all of them at the same time. What
are the situations in which this would be necessary? We will discuss some of the
applications in this chapter which need simultaneous storage of large sets of
numbers or names.
To process large amounts of data we need a special data type that would
efficiently store data and allow the user to access and process the data
efficiently. C Language meets the need of the user to support large sets of data
by introducing a data structure called array.
An array is a fixed size collection of elements all of the same primary data type.
It can be thought of as group of similar data items which are referred by a
common name. In it’s most basic form an array can be used to represent a list of
entities such as names or roll numbers of students or item numbers. Some
typical examples could include
Since an array represents a structured related set of items such as the examples
given above indicate it is classified as a Data Structure in C language. There are
other data structures in C such as lists, trees and queues some of which will be
discussed in later chapters. Presuming that there are 50 students whose marks
are to be stored an array can be defined to represent the data as below.
marks[50]
Each item in this list is called an element of the array. The individual elements
stored in the arrays are stored sequentially with the first array element stored in
the first reserved location and the second element in the second reserved
position and so on. Arrays are particularly useful when a large number of values
are required to be stored in separate variables such as in sorting. Arrays are also
known as subscripted variables.
The facility to use a single name to represent a collection of items and the ability
to access any individual member by specifying the index allows the user to
develop compact and efficient programs. Arrays can be used to represent
simple lists of the type mentioned above and they can also be used to represent
tables with rows and columns. Arrays which represent such lists which are two
dimensional ( rows and columns) or more are called multi‐dimensional arrays.
We will first understand one dimensional arrays and then proceed to two or
more dimensions.
103
A list of items which is given one common variable name and comprising of only
one subscript is called a single subscripted variable or a one‐dimensional array.
The concept of a subscript is not new and we have used it in mathematics
whenever we represent a sequence of numbers such as x1, x2, x3,… and so on to
represent a set of related numbers. In C the subscripted variable is expressed as
x[1]. x[2], x[3], …… . It is important to note that the subscript can begin with
zero.
The above statements declares to the computer that there will be an array by
the name marks and size 5 and its individual elements will be marks[0],
marks[1], marks[2], marks[3], and marks[4]. It is important for a beginner not to
confuse the index 0,1,2,3,4 with the actual values. The values to the array
elements ( which are individual variables in their own right) can be assigned in a
number of ways. One way to do that is
or
Much like the other variables in C language an array also needs to be declared
before it is used so that the C compiler or interpreter can allocate space for
them in memory. The format for declaring an array is
Here the type specifies the type of element that will be contained in the array,
and this can be any of the basic types of C such as int, float, double or char. The
size indicates the maximum number of elements that can be stored in the array
and is generally an integer number or a symbolic constant with an integer value.
Fro e.g. float temp[20]; indicates that temp is a array of type float and the
maximum number of elements it can store is 20,. The index or the subscript can
be any number from 0 to 19.
105
For e.g. char cityname [10]; will declare cityname as an array comprising of
maximum 10 characters. If we assign the name "MUMBAI" to this array, then it
is stored in the memory as follows
'M'
'U'
'M'
'B'
'A'
'I'
'\0'
Observe that the string is terminated with the null character '\0'. So it is always
necessary to provide for one extra space whenever size is mentioned while
declaring arrays of strings.
Example of a program to read an array of ten integer numbers and find the
average.
main()
{ int i ;
x [i] = y;
/* Print the values of the element sof the array and their sums */
printf("%d\n", x[i]);
10
15
20
25
30
35
40
45
50
55
Once an array is declared it must be supplied with initial values. Since arrays like
other variables in C language are memory locations there is a possibility that
they may contain some initial garbage values. The process of supplying the
values to an array variable is called initialization.
Initializing an array can be done in two ways. Either at Compile time or Run
time.
Like other variables in C language an array can be initialized when they are being
declared. It can be done as follows
108
Will declare an array of size 5 and will assign number 1 to the first element that
is roll[0], number 2 to roll[1] and so on respectively.
then the first three elements will be given values 1,2 and 3 respectively and all
the remaining elements will get the value zero. If the array is of type character
then the remaining values will be NULL.
Character arrays can also be initialized in the same way by specifying the array
type and assigning it to a string.
Arrays can also be initialized at the run time stage by specifying their values in a
for or while loop particularly when the array is large.
For e.g.
109
int x[50];
x[i] = 0;
It is also possible to input values directly into an array by using a keyboard input
function such as scanf().
scanf("%d%d", &x[1],&x[2]);
will give the values supplied by the user to the two elements of the array x.
Based on the matter presented the student should be able to solve some simple
problems using one dimensional arrays.
In the section above we discussed array variables which were in the form of a
single dimension list. However there can be situations where we may want to
store data which is the form of a table. A table has rows and columns much like
a matrix.
Consider the following table which shows region wise sales of four products.
There are four regions and four products whose sales figures are represented in
the table below
110
Products
Regions
Product1 Product1 Product1 Product1
Each row in the above table represents the sales of that region and each column
represents the sales of a particular product.
The table can also be seen as a 4 x 4 matrix with four rows and four columns.
In matrix terminology the value 300 in the first column and first row would be
represented by using two subscripts X11 where the first subscript represents the
row and the second subscript represents the column. In general the values
would be represented by Xij where I represents the row i and j represents the
column.
In the C language it is possible to define the table given above by using two
dimensional arrays. Two dimensional array to represent the above table would
be denoted as X[4][4] .
Like single dimensional arrays the index of both the rows and columns would
begin with the number 0. So for a a two dimensional array X[4][4] the arrays
variables would begin from X[0][0] and would go on till X[3][3]. In the context of
the above table the value 300 would be stored in X[0][0] and the value 500 in
the variable X[0][1].
We now illustrate the use of two dimensional arrays as follows. We will write a C
language program which reads in the value of the above two dimensional table
and calculate region wise and product wise sales and also find the Total Sales of
the organization.
/* C language Program to Calculate Region Wise Product wise Sales using two
dimensional arrays */
#define REGIONS 4
#define PRODUCTS 4
main()
int x[REGIONS][PRODUCTS] ;
printf( “Enter the Sales data region wise( row wise) one at a time \n”);
112
regtot[i] =0;
prodtot[j] =0;
}
113
}
114
will initialize the first elements of the first row by the value 1 and the second
row will get the values 2. The initialization is done row wise. It can also be done
by explicitly mentioning each rows within the braces as follows
the above statement will have the same effect as the first one.
The array can also be initialized by mentioning the values in the matrix format as
follows
int x[2][3] = {
{ 1,1,1},
{2,2,2}
};
115
6. Allow the user to enter 10 numbers, store them in an array and print them.
8. Allow the user to enter 15 numbers and print them in the reverse order.
9. Allow the user to enter 10 numbers, find the average and then print those
numbers that are above average.
10. Input ten distinct numbers and store them in an array, find the Highest and
Lowest.
11. Input the list of marks scored by a class of 50 students and generate a
frequency table with intervals 0‐10,11‐20,21‐30 and so on up to 91‐100.
12. Input the Roll number and marks of 10 students and store them in two
separate arrays. Ask the user to enter a particular roll number and display
the marks of that student.
13. Input the Roll number and marks of 10 students and store them in two
separate arrays. Display the Roll number and marks of the student getting
highest marks.
116
7
SORTING AND MERGING
Unit Structure
7.0 Objectives
7.1 Introduction
7.2 Bubble Sort
7.3 Selection Sort
7.4 Insertion Sort
7.5 Heap Sort
7.6 Merge Sort
7.7 Algorithmic Efficiency
7.8 Analyzing Algorithms
7.9 Algorithmic Efficiency ‐‐ Various Orders And Examples
7.0 OBJECTIVES
7.1 INTRODUCTION
Sorting in general refers to the process of arranging or ordering things
based on criteria (numerical, chronological, alphabetical, hierarchical etc.).
In computer applications sorting is of immense importance and is one of
the most extensively researched subjects. One of the main reasons for data
to be sorted is to allow fast access of the data and given the huge volume
of data it becomes inevitable to have fast machines and fast access to the
data. A simple requirement such as finding somebody’s name in a
telephone directory requires that the telephone directory be sorted in
alphabetical order for easy access. It is one of the most fundamental
algorithmic problems as the data involved can increase and the
programmer has to look at various alternatives to improve speed and better
memory utilization. Sorting is also fundamental to many other
fundamental algorithmic problems such as search algorithms, merge
algorithms etc. It is estimated that around 25% of all CPU cycles are used
to sort data. There are many approaches to sorting data and each has its
117
own merits and demerits. In this section we discuss some of the common
sorting algorithms.
Examine the following table. (Note that each pass represents the status of
the array after the completion of the inner for loop, except for pass 0,
which represents the array as it was passed to the function for sorting)
The above tabulation clearly depicts how each bubble sort works. Note that
each pass results in one number being bubbled to the end of the list. Bubble
sorting is a simple sorting technique in sorting algorithm. In bubble sorting
algorithm, we arrange the elements of the list by forming pairs of adjacent
elements. It means we repeatedly step through the list which we want to sort,
compare two items at a time and swap them if they are not in the right order.
Another way to visualize the bubble sort algorithm is as its name, the smaller
element bubble to the top.
temp = a[i];
119
a[i] = a[min];
a[min] = temp;
}
}
Consider the following table. (Note that each pass represents the status of
the array after the completion of the inner for loop, except for pass 0,
which represents the array as it was passed to the function for sorting)
8 6 1 0 3 1 2 5 4 } pass 0
1 6 1 0 3 8 2 5 4 } pass 1
1 2 1 0 3 8 6 5 4 } pass 2
1 2 3 1 0 8 6 5 4 } pass 3
1 2 3 4 8 6 5 1 0 } pass 4
1 2 3 4 5 6 8 1 0 } pass 5
1 2 3 4 5 6 8 1 0 } pass 6
1 2 3 4 5 6 8 1 0 } pass 7
a[j] = index;
120
}
}
Examine the following table. (Note that each pass represents the status of
the array after the completion of the inner for loop, except for pass 0,
which represents the array as it was passed to the function for sorting)
8 6 10 3 1 2 5 4 } pass 0
6 8 10 3 1 2 5 4 } pass 1
6 8 10 3 1 2 5 4 } pass 2
3 6 8 10 1 2 5 4 } pass 3
1 3 6 8 10 2 5 4 } pass 4
1 2 3 6 8 10 5 4 } pass 5
1 2 3 5 6 8 10 4 }pass6
1 2 3 4 5 6 8 10 } pass 7
The pass 0 is only to show the state of the unsorted array before it is given
to the loop for sorting. Now try out the deck-of-cards-sorting algorithm
with this list and see if it matches with the tabulated data. For example,
you start from 8 and the next card you see is 6. Hence you remove 6 from
its current position and "insert" it back to the top. That constituted pass 1.
Repeat the same process and you'll do the same thing for 3 which is
inserted at the top. Observe in pass 5 that 2 is moved from position 5 to
position 1 since its < (6,8,10) but > 1. As you carry on till you reach the
end of the list you'll find that the list has been sorted.
Note : This section needs the prior knowledge of trees and nodes and
should be taken up after the pre-requisites are learnt.
A semi‐heap is a binary tree in which all the nodes except the root possess the
heap property.
If N be the number of a node, then its left child is 2*N and the right child 2*N+1.
The root node of a Heap, by definition, is the maximum of all the elements
in the set of data, constituting the binary tree. Hence the sorting process
basically consists of extracting the root node and reheaping the remaining
121
set of elements to obtain the next largest element till there are no more
elements left to heap. Elementary implementations usually employ two
arrays, one for the heap and the other to store the sorted data. But it is
possible to use the same array to heap the unordered list and compile the
sorted list. This is usually done by swapping the root of the heap with the
end of the array and then excluding that element from any subsequent
reheaping.
root = maxchild;
}
}
122
In the above function, both root and bottom are indices into the array. Note
that, theoretically speaking, we generally express the indices of the nodes
starting from 1 through size of the array. But in C, we know that array
indexing begins at 0; and so the left child is
child = root * 2 + 1
/* so, for eg., if root = 0, child = 1 (not 0) */
In the function, what basically happens is that, starting from root each loop
performs a check for the heap property of root and does whatever
necessary to make it conform to it. If it does already conform to it, the
loop breaks and the function returns to caller. Note that the function
assumes that the tree constituted by the root and all its descendants is a
Semi-Heap.
Note that, before the actual sorting of data takes place, the list is heaped in
the for loop starting from the mid element (which is the parent of the right
most leaf of the tree) of the list.
Following this is the loop which actually performs the extraction of the
root and creating the sorted list. Notice the swapping of the ith element
with the root followed by a reheaping of the list.
The following are some snapshots of the array during the sorting process.
The unordered list –
8 6 10 3 1 2 5 4
After the initial heaping done by the first for loop.
10 6 8 4 1 2 5 3
Second loop which extracts root and reheaps.
8 6 5 4 1 2 3 10 } pass 1
6 4 5 3 1 2 8 10 } pass 2
5 4 2 3 1 6 8 10 } pass 3
4 3 2 1 5 6 8 10 } pass 4
3 1 2 4 5 6 8 10 } pass 5
2 1 3 4 5 6 8 10 } pass 6
1 2 3 4 5 6 8 10 } pass 7
1 2 3 4 5 6 8 10 } pass 8
Heap sort is one of the preferred sorting algorithms when the number of
data items is large. Its efficiency in general is considered to be poorer than
quick sort and merge sort.
mergesort(a,mid+1,high);
merge(a,low,high,mid);
}
→ return 0;
In computer science, often the question is not how to solve a problem, but
how to solve a problem well. For instance, take the problem of sorting.
Many sorting algorithms which we have discussed above are well-known;
the problem is of all the different ways to sort how to decide which is
appropriate and how to decide which algorithm will be efficient in a given
situation. In this section we understand how to compare the relative
efficiency of algorithms and why it's important to do so.
Now that we can describe any algorithm's efficiency in terms of its input
length (assuming we know how to compute the efficiency), we can
compare algorithms based on their "order". Here, "order" refers to the
mathematical method used to compare the efficiency -- for instance, n^2 is
"order of n squared," and n! is "order of n factorial." It should be obvious
that an order of n^2 algorithm is much less efficient than an algorithm of
127
order n. But not all orders are polynomial -- we've already seen n!, and
some are order of log n, or order 2^n.
int min = x;
if(array[y]<array[min])
min=y;
array[x] = array[min];
array[min] = temp;
We compute that the order of the outer loop (for(int x = 0; ..)) is O(n); then, we
compute that the order of the inner loop is roughly O(n). Note that even though
its efficiency varies based on the value of x, the average efficiency is n/2, and we
ignore the constant, so it's O(n). After multiplying together the order of the
outer and the inner loop, we have O(n^2).
In order to use this approach effectively, you have to be able to deduce the
order of the various steps of the algorithm. And you won't always have a piece
of code to look at; sometimes you may want to just discuss a concept and
determine its order. Some efficiencies are more important than others in
computer science, and on the next section, you'll see a list of the most
important and useful orders of efficiency, along with examples of algorithms
having that efficiency.
129
Below, we will list some of the common orders and give example algorithms.
O(1) An algorithm that runs the same no matter what the input. For instance, an
algorithm that always returns the same value regardless of input could be
considered an algorithm of efficiency O(1).
O(logn)
Algorithms based on binary trees are often O(logn). This is because a perfectly
balanced binary search tree has logn layers, and to search for any element in a
binary search tree requires traversing a single node on each layer.
O(n)
Algorithms of efficiency n require only one pass over an entire input. For
instance, a linear search algorithm, which searches an array by checking each
element in turn, is O(n). Often, accessing an element in a linked list is O(n)
because linked lists do not support random access.
O(nlogn)
Often, good sorting algorithms are roughly O(nlogn). An example of an
algorithm with this efficiency is merge sort, which breaks up an array into two
halves, sorts those two halves by recursively calling itself on them, and then
merging the result back into a single array. Because it splits the array in half
each time, the outer loop has an efficiency of logn, and for each "level" of the
array that has been split up (when the array is in two halves, then in quarters,
and so forth), it will have to merge together all of the elements, an operations
that has order of n.
130
O(n^2)
A fairly reasonable efficiency, still in the polynomial time range, the typical
examples for this order come from sorting algorithms, such as the selection sort
example on the previous page.
O(2^n)
The most important non‐polynomial efficiency is this exponential time increase.
Many important problems can only be solved by algorithms with this (or worse)
efficiency. One example is factoring large numbers expressed in binary; the only
known way is by trial and error, and a naive approach would involve dividing
every number less than the number being factored into that number until one
divided in evenly. For every increase of a single digit, it would require twice as
many tests.
There are a number of other issues of efficiency of algorithm which are not
directly related to performance but are nonetheless very important for
designing good software.
• modularity,
• correctness,
• maintainability,
• security,
• functionality,
• robustness,
• user‐friendliness,
• programmer's time,
• simplicity,
• extensibility,
• reliability, and
• scalability.
131
3. Given the roll numbers and names of students in two parallel arrays sort
them in ascending order of roll numbers using any of the algorithms.
6. Given two integers x and n write a C program to find the value of x to the
power of n. Can you think of solving it in any alternative ways so that the
efficiency of the algorithm is improved.
7. Write a program to store the roll numbers and names of 20 students in two
different arrays. Allow the user to enter a roll number and search for the
name and display it. Review the logic of your program for efficiency.
132
8
FUNCTION AN MACROS
Unit Structure
8.0 Objectives
8.1 Introduction
8.2 Function Characteristics
8.3 Function Basics
8.4 Function Prototypes
8.5 Function Philosophy
8.6 Macro Definition And Substitution
8.7 Scope Of Function Variables
8.8 Recursive Functions
8.9 Unit End Exercises
8.0 OBJECTIVES
After going through this chapter you will be able to
1. Understand what Functions are and why are they needed.
2. Be able to define a Function in terms of its arguments and return
values
3. Understand when and how to use Functions
4. Understand what are Macros and why they are needed
5. Explain how Macros are different from functions?
6. Understand what is Recursion?
7. Explain the Advantages of Recursion
8. Write programs for some standard situations for recursive
functions such as Fibonacci Sequence and Towers of Hanoi
9. Be able to understand situations where recursion is needed
8.1 INTRODUCTION
By this Chapter it must have been obvious to the learner that every
statement in C language is in reality a function. You must have also learnt
that every program must have a special function called main. While it is
possible to write the entire program with just one function called main one
realizes that with tasks which are complex the program written with just
one main function will start becoming larger and larger due to repetition of
code and debugging and maintaining the code will become a tedious task.
For this reason it becomes inevitable to break the program into
independent functional parts which can then be coded independently and
then combined into one single unit. This also allows teams of
programmers to function independently and then consolidate the program.
Such independently coded programs are called subroutines or subprograms
and every computer language has a way of incorporating them to solve
complex programming tasks. In C such subprograms are called Functions.
133
Since the rest of the program doesn't have to know the details of how the
function is implemented, the rest of the program doesn't care if the function is
reimplemented later, in some different way (as long as it continues to perform
its same task, of course!). This means that one part of the program can be
rewritten, to improve performance or add a new feature (or simply to fix a bug),
without having to rewrite the rest of the program.
134
Functions are the most important way to deal with the issue of software
complexity. We will learn when it's appropriate to break the main program
into functions, and how to set up function interfaces to best achieve the
qualities mentioned above: reusability, information hiding, clarity, and
maintainability.
In this section we will learn how to define a function. It has a name that
you call it by, and a list of zero or more arguments or parameters that you
forms the input to it for it to act on; it has a body containing the actual
instructions (statements) for carrying out the task the function is supposed
to perform; and it may give you back a return value, of a particular type. It
is not necessary that a function must return a value and it is also possible
that a function may have more than one return statements.
Here is a very simple function, which accepts one argument, multiplies it
by 2, and hands that value back:
int multbytwo(int x)
{
int retval;
retval = x * 2;
return retval;
}
On the first line we see the return type of the function (int), the name of the
function (multbytwo), and a list of the function's arguments, enclosed in
parentheses. Each argument has both a name and a type; multbytwo accepts
one argument, of type int, named x. The name x is arbitrary, and is used only
within the definition of multbytwo. The caller of this function only needs to
know that a single argument of type int is expected; the caller does not need to
know what name the function will use internally to refer to that argument. (In
particular, the caller does not have to pass the value of a variable named x.)
Next we see, surrounded by the familiar braces, the body of the function
itself. This function consists of one declaration (of a local variable
retval) and two statements. The first statement is a conventional
expression statement, which computes and assigns a value to retval, and
the second statement is a return statement, which causes the function to
return to its caller, and also specifies the value which the function returns
to its caller.
The return statement can return the value of any expression, so we don't
really need the local retval variable; the function could be condensed to
int multbytwo(int x)
{
return x * 2;
}
135
How do we call a function? We've been doing so informally since day one,
but now we have a chance to call one that we've written, in full detail.
Here is a tiny skeletal program to call multby2:
#include <stdio.h>
The presence of the function prototype declaration lets the compiler know
that we intend to call this function, multbytwo. The information in the
prototype lets the compiler generate the correct code for calling the
function, and also enables the compiler to check up on our code (by
making sure, for example, that we pass the correct number of arguments to
each function we call).
Down in the body of main, the action of the function call should be
obvious: the line
j = multbytwo(i);
calls multbytwo, passing it the value of i as its argument. When multbytwo
returns, the return value is assigned to the variable j. (Notice that the value of
main's local variable i will become the value of multbytwo's parameter x)
And the variable j isn't really needed, either, since we could just as well call
printf("%d\n", multbytwo(3));
Here, the call to multbytwo is a sub‐expression which serves as the second
argument to printf. The value returned by multbytwo is passed immediately to
printf. (It is interesting to see the flexibility and generality of expressions in C.
An argument passed to a function may be an arbitrarily complex sub‐expression,
and a function call is itself an expression which may be embedded as a sub‐
expression within arbitrarily complicated surrounding expressions.)
j = multbytwo(i);
When our implementation of multbytwo changes the value of x, does that
change the value of i up in the caller? The answer is no. x receives a copy of i's
value, so when we change x we don't change i.
However, there is an exception to this rule. When the argument you pass
to a function is not a single variable, but is rather an array, the function
does not receive a copy of the array, and it therefore can modify the array
in the caller. The reason is that it might be too expensive to copy the entire
array, and furthermore, it can be useful for the function to write into the
caller's array, as a way of handing back more data than would fit in the
function's single return value. We'll see an example of an array argument
(which the function deliberately writes into) in the next section.
main()
{
float highest(float x[], int n);
float a[5] = {3.6,7.8,2.3,1.2,5.6};
printf( “%f\n”, highest(a,5);
}
If prototypes are a good idea, and if we're going to get in the habit of
writing function prototype declarations for functions we call that we've
written (such as multbytwo), what happens for library functions such as
printf? Where are their prototypes? The answer is in that boilerplate line
#include <stdio.h>
138
we've been including at the top of all of our programs. stdio.h is conceptually a
file full of external declarations and other information pertaining to the
``Standard I/O'' library functions, including printf. The #include directive (which
we'll meet formally in a later chapter) arranges that all of the declarations within
stdio.h are considered by the compiler, rather as if we'd typed them all in
ourselves. Somewhere within these declarations is an external function
prototype declaration for printf, which satisfies the rule that there should be a
prototype for each function we call. (For other standard library functions we
call, there will be other ``header files'' to include.) Finally, one more thing about
external function prototype declarations. We've said that the distinction
between external declarations and defining instances of normal variables hinges
on the presence or absence of the keyword extern. The situation is a little bit
different for functions. The ``defining instance'' of a function is the function,
including its body (that is, the brace‐enclosed list of declarations and statements
implementing the function). An external declaration of a function, even without
the keyword extern, looks nothing like a function declaration. Therefore, the
keyword extern is optional in function prototype declarations. If you wish, you
can write
int multbytwo(int);
and this is just as good an external function prototype declaration as
Two obvious reasons for moving code down into a function are because:
These two reasons are important, and they represent significant benefits of
well-chosen functions, but they are not sufficient to automatically identify
a good function. As we've been suggesting, a good function has at least
these two additional attributes:
function's original interface anticipated the hard parts, you won't have to
rewrite the rest of the program when you fix the function.
It is to be noted that functions are important for far more important reasons
than just saving typing. Sometimes, we'll write a function which we only
call once, just because breaking it out into a function makes things clearer
and easier.
If you find that difficulties pervade a program, that the hard parts can't be
buried inside black-box functions and then forgotten about; if you find that
there are hard parts which involve complicated interactions among
multiple functions, then the program probably needs redesigning.
For the purposes of explanation, we've been seeming to talk so far only
about ``main programs'' and the functions they call and the rationale
behind moving some piece of code down out of a ``main program'' into a
function. But in reality, there's obviously no need to restrict ourselves to a
two-tier scheme. Any function we find ourself writing will often be
appropriately written in terms of sub-functions, sub-sub-functions, etc.
Furthermore, the ``main program,'' main(), is itself just a function.
The most common use for macros is to propagate various constants around
and to make them more self-documenting. We've been saying things like
char line[100];
...
getline(line, 100);
but this is neither readable nor reliable; it's not necessarily obvious what all
those 100's scattered around the program are, and if we ever decide that 100 is
too small for the size of the array to hold lines, we'll have to remember to
change the number in two (or more) places. A much better solution is to use a
macro:
Now, if we ever want to change the size, we only have to do it in one place, and
it's more obvious what the words MAXLINE through the program mean than
the magic numbers 100 did.
#define A 2
#define B 3
#define C A + B
int x = C * 2;
If A, B, and C were ordinary variables, you'd expect x to end up with the value
10. But let's see what happens.
The preprocessor always substitutes text for macros exactly as you have
written it. So it first substitutes the replacement text for the macro C,
resulting in
int x = A + B * 2;
Then it substitutes the macros A and B, resulting in
int x = 2 + 3 * 2;
Only when the preprocessor is done doing all this substituting does the compiler
get into the act. But when it evaluates that expression (using the normal
precedence of multiplication over addition), it ends up initializing x with the
value 8!
int x = (2 + 3) * 2;
and x would be initialized to 10, as we probably expected.
Notice that there does not have to be (and in fact there usually is not) a
semicolon at the end of a #define line. (This is just one of the ways that
the syntax of the preprocessor is different from the rest of C.) If you
accidentally type
#define MAXLINE 100;
/* WRONG */
then when you later declare
char line[MAXLINE];
the preprocessor will expand it to
which is a syntax error. This is what we mean when we say that the
preprocessor doesn't know anything about the syntax of C‐‐in this last example,
the value or replacement text for the macro MAXLINE were the 4 characters
143
Simple macros like MAXLINE act sort of like little variables, whose values
are constant (or constant expressions). It's also possible to have macros
which look like little functions (that is, you invoke them with what looks
like function call syntax, and they expand to replacement text which is a
function of the actual arguments they are invoked with).
Local variables are declared within a function. They are created anew each
time the function is called, and destroyed on return from the function.
Values passed to the function as arguments can also be treated like local
variables.
Static variables are slightly different, they don't die on return from the
function. Instead their last value is retained, and it becomes available when
the function is called again.
case 1:
return(1);
break;
default: /* Including recursive calls */
return(fib(num - 1) + fib(num - 2));
break;
}
}
We met another function earlier called power. Here is an alternative
recursive version.
Notice that each of these definitions incorporate a test. Where an input value
gives a trivial result, it is returned directly, otherwise the function calls itself,
passing a changed version of the input values. Care must be taken to define
functions which will not call themselves indefinitely, otherwise your program
will never finish.
The towers of Hanoi is a well known game for children. It has three poles and a
number of different sized discs. Each disc has a hole at the center allowing it to
be stacked on any of the three poles. To begin with the disc are all stacked on
the first or the leftmost pole in the order of decreasing size. i.e. smallest on top
and the largest disc at the bottom.
The aim of the game is to transfer the discs from the leftmost pole to the
rightmost pole without ever placing a larger disc on a smaller disc. Only one disc
maybe moved at a time and each disc must always be placed around one of the
poles. The general approach is to consider one of the poles to be the origin and
another to be the destination. The third pole is used for intermediate storage
purposes allowing the movement of discs in such a way as to avoid placing the
larger disc on a smaller one.
Assuming there are n discs numbered from the smallest to the largest, if the
discs are initially stacked on the left pole, the problem of moving n discs to the
right pole can be stated recursively as follows.
1. Move the top n-1 discs from the left pole to the center pole.
2. Move the nth (largest) disc to the right pole.
3. Move the n-1 discs from the center pole to the right pole.
146
In order to program this game we first assign labels to the poles. The leftmost
pole is labeled as ‘L’ , the center or intermediary pole as ‘C’ and the rightmost or
the destination pole as ‘R’. It is now possible to construct a recursive function
called shift that will shift n disc from one pole to the another. We refer to the
individual poles with the char type variables from, to, and temp indicating origin,
destination and intermediary poles. Given this information we now write the C
code to implement the towers of Hanoi solution.
#include <stdio.h>
#include <conio.h>
void transfer(int n, char from, char to, char temp); /* Function prototype*/
main()
int n;
scanf(“%d”,&n);
printf(\n”);
transfer(n,’L’,’R’,’C’);
147
if (n>0)
return ;
It is to be noted that the function transfer receives a different set of values for
its arguments each time it is called. These set of values will be pushed onto the
stack independently of one another and then popped from the stack at he
proper time during execution. It is this ability to store and retrieve these
independent sets of values that allows for recursion to work.
#include<stdio.h>
int main(){
int num,rev;
scanf("%d",&num);
rev=reverse(num);
→ return 0;
int sum=0,r;
reverse(int num){
if(num){
r=num%10;
sum=sum*10+r;
reverse(num/10);
else
return sum;
}
149
Although some of the problems here can be solved without using recursive
functions the learner may try to solve them using recursion.
9
THE C STORAGE CLASSES,
SCOPE AND MEMORY
Unit Structure
9.0 Objectives
9.1 Introduction
9.0 OBJECTIVES
9.1 INTRODUCTION
Variables in C language typically have two characteristics. One is the data type
such as int, float etc which we have covered earlier and which indicate the type
of data that the variable can store and the other is the storage class which
determines the part of memory where storage is allocated for variables and
functions, and how long the storage allocation continues to exist.
1. Automatic Variables
2. External Variables
3. Static Variables
4. Register Variables
For all the above storage classes we will observe three factors. The scope,
visibility and durability or longevity. The scope of a variable refers over what
part of the program the variable continues to be available to that program.
Durability or longevity refers to the time period during which the variable
retains the value of the variable. The visibility refers to the accessibility of the
variable from the memory.
Variables are also categorized depending on where in the program they are
declared.
Internal variables are declared within the body of a function and external
variables are declared outside the body of the function. Depending on where
they are declared has implications on their accessibility and durability. It is also
impotant to know that depending on the storage classes the values of these
variables are physically placed in different parts of the memory of a computer
152
A beginner programmer may wonder why C allows for storage classes, however
it is important to understand that storage classes determine the speed and
execution of a program and more specifically programs which have multiple
functions.
They are declared at the start of a program’s block such as in the curly braces ( {
} ). Memory is allocated automatically upon entry to a block and freed
automatically upon exit from the block. The scope of automatic variables is local
to the block in which they are declared, including any blocks nested within that
block. For these reasons, they are also called local variables. No block outside
the defining block may have direct access to automatic variables (by variable
name) but, they may be accessed indirectly by other blocks and/or functions
using pointers. Automatic variables may be specified upon declaration to be of
storage class auto. However, it is not required to use the keyword auto because
by default, storage class within a block is auto. Automatic variables declared
with initializers are initialized every time the block in which they are declared is
entered or accessed.
Example
main()
int x;
- -------;
- -------;
}
153
The same program could also be rewritten using the word auto.
main()
auto int x;
- -------;
- -------;
}
We now show an example to illustrate how the values of auto variables remain
unaffected in a program despite the same variable being used in various
functions.
In the following example there is a program with two functions func1 and func2.
There is a variable x which has been declared and initialized to different values
in each of three functions. The main, func1 and func2. x has the value 6 in the
main function , and 2 and 4 in the functions func1 and func2.
When executed the main function calls upon the function func2 which in turn
calls the function func1. When the main function is active the value of x is 6 but
when main calls func2 its value is temporarily put on hold and a new variable x
with value 4 gets created in function func2. When func2 calls func1 its value ( 4)
is kept on hold and a third variable also bearing the same name x is created with
value 2 and becomes active. When the execution of func1 gets over the value of
x ( 2) gets printed and func2 takes over and the value 4 gets printed and finally
the control returns to main function and the value 6 gets printed.
154
The learner should go through the code carefully to understand how the values
get created and printed.
void func1(void);
void func2(void);
main()
int x = 6;
func2();
printf(‘%d \n”,x);
void func1(void)
int x = 2;
printf(‘%d \n”,x);
void func2(void)
int x = 4;
func1();
printf(‘%d \n”,x);
155
Output is
The important thing to note here is that the auto variables local to main()
remain alive throughout the execution of the program but active only in main. In
the subsequent functions these variables get created and destroyed locally
whenever the sub program or sub function execution gets completed.
All variables we have seen so far have had limited scope (the block in which they
are declared) and limited lifetimes (as for automatic variables). However, in
some applications it may be useful to have data which is accessible from within
any block and/or which remains in existence for the entire execution of the
program. Such variables are called global variables, and the C language provides
storage classes which can meet these requirements; namely, the external
(extern) and static (static) classes.
External variables may be declared outside any function block in a source code
file the same way any other variable is declared; by specifying its type and name
(extern keyword may be omitted). Typically if declared and defined at the
beginning of a source file, the extern keyword can be omitted. If the program is
in several source files, and a variable is defined in let say file1.c and used in
file2.c and file3.c then the extern keyword must be used in file2.c and file3.c.
156
The scope of external variables is global, i.e. the entire source code in the file
following the declarations. All functions following the declaration may access
the external variable by using its name. However, if a local variable having the
same name is declared within a function, references to the name will access the
local variable cell.
#include <stdio.h>
void funct1(void);
void funct2(void);
int main()
/* external variable */
157
globvar = 20;
funct1();
funct2();
→ return 0;
void funct1(void)
/* auto variable, scope local to funct1() and funct1() cannot access the external
globvar */
char globvar;
globvar = 'A';
/* external variable */
globvar2 = 40;
158
void funct2(void)
/* auto variable, scope local to funct2(), and funct2() cannot access the external
globvar2 */
double globvar2;
/* external variable */
globvar = 50;
globvar2 = 1.234;
}
159
As we have seen, external variables have global scope across the entire program
(provided extern declarations are used in files other than where the variable is
defined), and have a lifetime over the entire program run.
Similarly, static storage class provides a lifetime over the entire program,
however; it provides a way to limit the scope of such variables, and static
storage class is declared with the keyword static as the class specifier when the
variable is defined.
160
These variables are automatically initialized to zero upon memory allocation just
as external variables are. Static storage class can be specified for automatic as
well as external variables such as:
Static automatic variables continue to exist even after the block in which they
are defined terminates. Thus, the value of a static variable in a function is
retained between repeated function calls to the same function.
#include <stdio.h>
#define MAXNUM 3
void sum_up(void);
int main()
int count;
printf("\n*****static storage*****\n");
161
sum_up();
printf("\n*****COMPLETED*****\n");
→ return 0;
void sum_up(void)
int num;
scanf("%d", &num);
sum += num;
*****static storage*****
Enter a number: 10
Enter a number: 10
Enter a number: 11
The student must note that the function sum_up() in the above example is
called three times as it appears in the loop. Every time it is called the variable
sum is accumulating the value of the newly typed in number as it retains the
value ( due to it being declared as static ) between the function calls.
The static class is used sometimes to control the scope of a function. For
example if we would like a particular function to be accessible only to the
functions defined in a file and not to other functions in other files then this can
be accomplished by defining that function with the storage class static.
Automatic variables are allocated storage in the main memory of the computer;
however, for most computers, accessing data in memory is considerably slower
than processing directly in the CPU.
Registers are memory located within the CPU itself where data can be stored
and accessed quickly. Normally, the compiler determines what data is to be
stored in the registers of the CPU at what times.
However, the C language provides the storage class register so that the
programmer can suggest to the compiler that particular automatic variables
163
should be allocated to CPU registers, if possible and it is not an obligation for the
CPU to do this.
Variables which are used repeatedly or whose access times are critical may be
declared to be of storage class register.
By making the above statement we are requesting the compiler that a variable
called var of type int be assigned a place in the register area of the CPU.
Although most ANSI standards place no restriction on what type of variable can
be assigned to the register type most compilers restrict them to be of either int
or character type. It must be noted that only a few variables can be assigned to
be of the register storage class due to the inherent limitations on the space of
the registers. If this limit is reached register storage variables then get treated as
non register variables. The following table summarizes the different storage
classes and their scope, visibility and durability for ready reference of a
programmer. It also allows the student to compare the different storage classes
at a glance.
164
Up until now, we have been using the term declaration rather loosely
when referring to variables. In this section, we will ``tighten'' the definition
of this term. So far when we have ``declared'' a variable, we have meant
that we have told the compiler about the variable; i.e. its type and its
name, as well as allocated a memory cell for the variable (either locally or
globally). This latter action of the compiler, allocation of storage, is more
properly called the definition of the variable. The stricter definition of
declaration is simply to describe information ``about'' the variable.
Here are some examples of declarations of external variables that are not
definitions:
These declarations tell the compiler that the variables stack[] and stkptr
are defined elsewhere, usually in some other file. If the keyword extern
were omitted, the variables would be considered to be new ones and
memory would be allocated for them. Remember, access to the same
external variable defined in another file is possible only if the keyword
extern is used in the declaration.
166
void stat(void);
main()
{ int i;
for (i=1;i<=3;i++)
stat();
void stat(void)
x=x+1;
printf(“x= %d\n”,x);
2. What would its output be if the word static is omitted in the function
definition.
3. In the exercises given in earlier chapters what variables would you declare
as register and why?
10
STRUCTURES AND UNIONS
Unit Structure
10.0Objectives
10.1 Introduction
10.2 Why Use Structures
10.3 Defining A Structure
10.4 Declaring Structure Variables
10.5 Accessing Structure Variables
10.6 Arrays Of Structures
10.7 Arrays Within Structures
10.8 Structures Within Structures
10.9 Structures And Functions
10.10 Unions
10.11 Unit End Exercise
10.0 OBJECTIVES
After learning this chapter the student should be able to
1. Understand what are structures and why they are needed
2. Be able to define a structure
3. Be able to read and assign values to elements in a structure
4. Be able to understand the relationship between arrays and
structures
5. Be able to define structures within structures
6. Be able to understand the relationship between structures and
functions
7. Be able to understand what are unions
8. Write programs involving the use of structures
10.1 INTRODUCTION
In the chapters that we have learned so far we have used different data
types such as int, float and char. However when we look at real life data, it
rarely comes in such atomized forms, rather we come across entities that
are collections of things, each thing having its own attributes, just as the
entity we call a ‘bill’ is a collection of things such as bill number, bill date,
item no, item description, quantity purchased, amount and so on. As you
can see all this data is dissimilar, for example bill number is a number,
whereas item description is a string and so on. For dealing with such
collections, C provides a data type called ‘structure’. A structure gathers
together, different individual pieces of information that makes up a given
entity. A structure can be looked upon as a tool to handle a group of
logically related data. From this point on we can look at the individual
pieces of information or we can look at the whole composite entity. We
168
will see why it is an advantage to be able to view the data in this manner
and how it simplifies coding and improves performance.
(a) Construct individual arrays, one for storing names, another for storing
prices and still another for storing number of pages.
main( )
{
char name[3] ;
float price[3] ;
int pages[3], i ;
printf ( "\nEnter names, prices and no. of pages of 3 books\n" ) ;
for ( i = 0 ; i <= 2 ; i++ )
scanf ( "%c %f %d", &name[i], &price[i], &pages[i] );
printf ( "\nAnd this is what you entered\n" ) ;
for ( i = 0 ; i <= 2 ; i++ )
printf ( "%c %f %d\n", name[i], price[i], pages[i] );
}
And here is the sample run...
Enter names, prices and no. of pages of 3 books
A 100.00 354
C 256.50 682
F 233.70 512
And this is the output
A 100.000000 354
C 256.500000 682
F 233.700000 512
This approach allows you to store names, prices and number of pages. But
as you must have realized, it is an unwieldy approach that obscures the
fact that you are dealing with a group of characteristics related to a single
entity—the book.
169
These data types may or may not be of the same type. The following
example illustrates the use of this data type.
main( )
{
struct book
{
char name ;
float price ;
int pages ;
} ; struct book b1, b2, b3 ;
printf ( "\nEnter names, prices & no. of pages of 3 books\n" ) ;
scanf ( "%c %f %d", &b1.name, &b1.price, &b1.pages ) ;
scanf ( "%c %f %d", &b2.name, &b2.price, &b2.pages ) ;
scanf ( "%c %f %d", &b3.name, &b3.price, &b3.pages ) ;
printf ( "\nAnd this is what you entered" ) ;
printf ( "\n%c %f %d", b1.name, b1.price, b1.pages ) ;
printf ( "\n%c %f %d", b2.name, b2.price, b2.pages ) ;
printf ( "\n%c %f %d", b3.name, b3.price, b3.pages ) ;
}
A 100.00 354
C 256.50 682
F 233.70 512
Unlike arrays which can be declared and used straight away structures
have to be defined first and declared later. We consider an example to
illustrate how a structure can be defined and variables created.
struct book
{
char title[20];
char author[10];
float price;
}
The keyword struct declares a structure and specifies that it will hold the
data fields title, author, pages and price. These fields are called structure
elements or members. The name of the structure is book and is also called
as the structure tag. Each member in the structure may be of different
data type. The tag name is used later to declare variables which will have
the same structure. It is important to note that the above definition doesn’t
create any variables and that at this stage no memory has been utilized. It
can be seen more as a template or a blue print.
struct tag-name
{
datatype element1;
datatype element2;
………….
};
pages and price as mentioned in the book structure at the time when it was
defined. It is important for a student to understand that the members of the
structure are not the variables and they do not occupy any memory space
until they are associated with a particular variable. It is only when the
compiler comes across the declaration statement that specific memory
For example
struct book
{
char title[20];
char author[10];
int pages;
float price;
} book1,book2,book3;
Values can also be read into these variables with the help of the scanf
statement
scanf(“%f”,&book1.price)
Define a structure type called student that will contain the roll number,
name of the student and marks in three subjects. Using this structure write
a program that will input the details of the student and print it out on the
screen.
struct student
{
int rollno;
char name[15];
int m1,m2,m3;
};
main()
{
173
printf(“%d %s %d %d %d”,
student.rollno,
student.name,
student.m1,
student.m2,
student.m3);
}
The above program accepts the values entered into the structure student
through an input statement and prints it out by accessing the structure
variables.
struct student
{
int rollno;
char name[15];
int m1,m2,m3;
} student1 = { 1,”AJAY”,67,78,89};
For e.g. student1 = student2 will assign all the values of student2 to
student1.
struct student
{
int rollno;
char name[15];
int m1,m2,m3;
};
main()
{
struct student student1 ={1,”AJAY”, 67,78,89};
struct student student2 ={2,”GEETA”, 57,88,90};
struct student student3;
student3 = student2;
if ( student3.rollno == student2.rollno)
printf(“The students roll numbers are the same”);
else
printf(“The students roll numbers are not the
same”);
An individual member gets treated exactly the same way as other variables
in C language once the dot operator specifies the parent structure variable
to which the member belongs.
All the operators and expressions which are valid for other variables in C
language also remain valid for structure variable members once the dot or
member operator is used.
struct result
{ int m1;
int m2;
int m3;
};
Having defined the result structure, we can give values using an array as
follows
as student[0].m1 = 35
student[0].m2 = 55
student[0].m3 = 75
student[1].m1 = 45 and so on .
struct marks
{
int m1;
int m2;
int m3;
};
main()
{
int i, tm1,tm2,tm3;
struct marks student[3] = { { 35,55,75},{45,65,76},{76,43,65}};
for (i=0,i<=2;i++)
{
tm1 = tm1 +student[i].m1
struct marks
{
int rollno;
int m[3];
} student[2];
struct marks
{
int m[3];
};
main()
{
int i, tm[3]={0,0,0};
struct marks student[3] = { { 35,55,75},{45,65,76},{76,43,65}};
for (i=0,i<=2;i++)
{
for (j=0;j<=2;j++)
{
tm[i] =tm[i] + student[j].m[i];
for (i=0,i<=2;i++)
printf(“Total of subject %d is equal to %d\n”,i,tm[i];
struct student
{
int rollno;
char name[10];
char addr1[10];
char addr2[10];
struct
{
int m1;
int m2;
int m3;
}mark;
} student;
Here the main structure gives the details of the student other than the
marks in three subjects which have been separately grouped as a logical
entity.
178
The student structure contains the member called mark which itself is a
structure with members m1,m2 and m3.
The members contained in the inner structure namely mark can be referred
to by referring to the main structure as follows
student.mark.m1
mark and not the structure student, and the immediate structure mark
cannot be omitted.
111 AJAY 90
It must be noted that in the declaration of the structure, name has been
declared as array. Therefore, when we call the function display( ) using,
179
we are passing the base address of the array name , but the values stored
in rollno and m1. Thus, this is a mixed call—a call by reference as well as
a call by value.
It can be immediately realized that to pass individual elements would
become more tedious as the number of structure elements go on
increasing. A better way would be to pass the entire structure variable at a
time.
struct student
{
int rollno ;
char name[25] ;
int m1;
};
main( )
{
struct student st = { 111, “AJAY”, 90 } ;
display ( st ) ;
}
display ( struct book b )
{
printf ( "\n%d %s %d", b.rollno, b.name, b.m1 ) ;
}
111 AJAY 90
It should also be noted here that the structure has been defined outside the
main() function so that it is available to all functions defined within the
main() function.
180
10.10 UNIONS
Unions have a great deal of similarities with structures however there is
one main difference and that is in the manner of data storage that they
implement. In structures each member has its own storage location, in
unions all members share a common storage location. The consequence of
this is even if a uion may contain many members of different data type it
can handle only one member at a time. Like structures unions can also be
declared with the keyword union.
main() {
union data
{
int integer;
char character;
} data_t;
data_t input;
input.integer = 2;
printf("input.integer = %d\n",input.integer);
input.character = 'A';
printf("input.character = %c\n",input.character);
The above example declares a variable data_t of type union data. The
union contains two members namely integer and character. Note however
that only one of them can be used at a time. This is due to the fact that
only one location is allocated for a union variable. The compiler allocates
space large enough to hold the largest member in the union. To access a
union member the same syntax as structures is used.
Pointers
11
Unit Structure
11.0 Objectives
11.1 Introduction
11.1.1 Fundamentals
11.7.1 malloc()
11.7.2 free()
11.7.4 calloc()
11.7.5 realloc()
11.8 Summary
11.0 Objectives
11.1 Introduction:
0th Byte
1st Byte
Operating 2nd Byte
System .
.
Program
Code .
Data
100 4000th Byte(int a)
.
Free Area
Nth Byte(Max.)
Main Memory
Fig. 11.1: Main Memory with address
184
When you declare the above variable ‘a’ it means occupy a space in memory for
storing integer value 100 and give the name as ‘a’ to this space. Here it is stored
at 4000th location is just imaginary address. It will be different in actual!
Definition of Pointer:
float *pqr;
185
Just observe the syntax carefully, first you will find data_type, which is the type
of data to which you point using pointer variable, followed by *, denotes it is a
pointer variable and name of the pointer variable. When you declare a pointer
variable as integer, that doesn’t mean pointer variables type is int, it means it
can point to an integer or it has ability to store the address of integer variable. In
above examples the pointer variable ‘abc’ can store the address of an integer
variable and ‘pqr’ can store the address of a float variable. Then what’s the type
of a pointer variable? It’s ‘Positive Integer’. Yes! Pointers variables are always
type of positive integers. As it stores the address of another variable, it can not
be of type float or character, only positive integers. To understand this concept
clearly, take a glance at computer memory in fig. 11.2 and forget about
Operating System and Code part from the memory. Concentrate only on data
part.
float pi = 3.14;
pointer
a_ptr 4000 7000
variable
integer
a 100 4000
variable
pointer
pi_ptr 5000 8000
variable
float
pi 3.14 5000
bl name value address
In above example, variable a content value 100 and its address is 4000, which is
stored in pointer variable a_ptr. Similarly pi content value 3.14 and its address is
5000 which is stored in pointer variable pi_ptr. Here you will find one more new
operator, i.e. &(ampersand). It is known as address operator. The &
(ampersand) operator gives the address of a variable. E.g ‘&a’ will return the
address of variable ‘a’.
#include<stdio.h>
#include<conio.h>
void main()
clrscr();
getch();
}
187
Using * operator, you can access the value stored at that location. In example
11.1, observe last two printf statements. You can get the value stored in a using
a_ptr. When you write *a_ptr, that means you want to access value stored at
address in a_ptr. As a_ptr contains 65524, which is address of variable a, so
*a_ptr will return you value 100 and a_ptr will return you 65524. It’s not
necessary that it will always return you 65524; it can be different in your system.
#include<stdio.h>
#include<conio.h>
void main()
int num=400;
int *p;
clrscr();
getch();
}
188
In above example, initially value 400 is stored in variable num. Then the address
of num is stored in pointer variable p using & operator. Then, value of variable
num is changed from 400 to 100 and finally, value of num is incremented by 100
using pointer p.
#include<stdio.h>
#include<conio.h>
void main()
int i=50;
int *ptr;
int **ptr2ptr;
int ***ptr2ptr2ptr;
clrscr();
ptr = &i;
ptr2ptr = &ptr;
ptr2ptr2ptr = &ptr2ptr;
getch();
}
190
i
50 ptr
fff4 fff4 ptr2ptr
fff2 fff2 ptr2ptr2ptr
fff0 fff0
ffee
#include<stdio.h>
#include<conio.h>
void main()
clrscr();
a_ptr=&a;
In the above example, as you have seen, pointer can be incremented; similarly it
can be decremented also.
In above example, there are 5 elements in the array. Array index always starts
from 0th position. Therefore we have array indexes from a[0] …. a[4]. The
starting memory address of an array is known as base address. In above case, it
is 2020 and as each element takes 2 bytes to store, next address is 2022 and so
on.
You can access element of arrays using Pointers. If you increments the value of
Pointer then it points to next location i.e next element of an array. If you have a
pointer which points to base address i.e. 1st element a[0], after incrementing the
pointer it will point to the next element a[1].
193
#include<stdio.h>
#include<conio.h>
void main()
int *a_ptr,i;
clrscr();
a_ptr = &a[0];
for(i=0;i<=4;i++)
a_ptr++;
getch();
}
194
In the above example, the base of the 0th element has been assigned to the
pointer. The same thing, you can achieve in a different way. You can access the
base address i.e address of 0th element by specifying just array name. That
means, in above case, ‘&a[0]’ is same as ‘a’.
#include<stdio.h>
#include<conio.h>
void main()
int i;
clrscr();
for(i=0;i<=4;i++)
getch();
}
195
M u m b a i \0
1004 1005 1006 1007 1008 1009 1010
In above example, ‘city’ is array of characters. You can access the base address
using variable ‘city’. After base address, each location is shifted by one byte.
Because, you know now, “character requires one byte to store it in memory”.
The following program will demonstrate how we can use the strings.
196
#include<stdio.h>
#include<conio.h>
void main()
char name[]="Vishal";
int i=0;
clrscr();
printf("Address\t Contents");
printf("\n%u\t\t %c %c %c",(name+i),*(name+i),i[name],name[i]);
i++;
getch();
}
197
Here, first you will find address of each character, which is printed using
‘name+i' (base address + value of i). Next is character itself using value at the
operator, ‘*(name+i)’. Again the same character is printed twice using ‘i[name]’
and ‘name[i]’. Yes, both statements will result same. Because compiler will
interpret is as ‘*(i+name)’ and ‘*(name+i)’.
There are some commonly used library functions to work on string. Most of
them manipulate string using pointers. Some of them are: strlen(), strcat(),
strcpy() and strcmp().
strlen() function:
This function is used to find out the length of the string. You need to pass the
base address of the string to this function. It will return the length of the string.
strcat() function:
Thus function is used to concatenate two strings. That means, if you have one
string as “Hello” and another string as “brother”, this function will generate new
string as “hellobrother”.
strcpy() function:
This function is used to copy one string to the other string. It requires two
arguments, pointer to the first character of destination string and pointer to the
first character of source string.
strcmp() function:
This function is used to compare two strings, whether they are equal or not. If
both the strings are equal, it will return an integer value 0. The function
compares two strings character by character. If it founds the different
characters, it will return the difference between ASCII values of these
characters.
198
The following program will demonstrate, how above functions can be used with
strings?
#include<stdio.h>
#include<conio.h>
void main()
int len,d;
clrscr();
scanf("%s",str1);
getch();
}
199
.
a_ptr .
6030 4024 a_ptr[0] 4024 10 a[0]
6032 4026 a_ptr[1] 4026 20 a[1]
6034 4028 a_ptr[2] 4028 30 a[2]
6036 4030 a_ptr[3] 4030 40 a[3]
.
.
In fig.11.5, there are two arrays. One is array of integers and the other one is
array of Pointers. In array ‘a’, we have stored 4 integer numbers (10, 20, 30, 40).
In array of pointers, we have store addresses of each element of an integer
array. This can be illustrated using next program.
#include<stdio.h>
#include<conio.h>
void main()
int a[]={10,20,30,40,50};
int *a_ptr[5],i;
clrscr();
for(i=0;i<5;i++)
a_ptr[i]=&a[i];
for(i=0;i<5;i++)
for(i=0;i<5;i++)
getch();
}
201
1. Call by Value
2. Call by Reference
However, if you use the first method, the actual arguments in the calling
method will be copied into formal arguments of the called function. That means,
it will crate one more copy of each argument in memory for called function.
Therefore, if you make changes into formal arguments of called method, it will
not affect to the actual arguments in calling method.
#include<stdio.h>
#include<conio.h>
void main()
clrscr();
ifun(w,h);
getch();
ifun(int w,int h)
w++; h++;
}
202
If you have executed above program, you can easily find that we are passing two
variables w and h to the function ‘ifun’. We have incremented values of w and h
in function ‘ifun’. But as C compiler, creates two new copies of these variable for
function ‘ifun’, the changes made in function ‘ifun’ won’t be reflected in values
of calling function ‘main’. Therefore we get same result after calling ‘ifun’
function.
To solve above problem, we can use the second method. i.e. Call By Reference.
In this method, instead of passing actual values of arguments, we send
reference (addresses) of arguments. Now, whatever changes made in called
function, gets reflected in the calling function also. It can illustrated using
following program.
#include<stdio.h>
#include<conio.h>
void main()
clrscr();
ifun(&w,&h);
getch();
*w=*w+1;
*h=*h+1;
In this program, we are passing addresses of variable w and h to the function
}
‘ifun’, which are collected into the pointer variables w and h in the function
‘ifun’. Then we have incremented the value of w and h using *(value at
203
operator). Therefore the changes also gets reflected into the function ‘main’
#include<stdio.h>
#include<conio.h>
void main()
int *p;
clrscr();
p=ifun();
getch();
int* ifun()
int num=10;
return(&num);
}
204
So, what to do for accessing functions using pointers? Nothing much! To get the
address of a function, you need to mention only name of the function.
Declaration of the pointer variable is as follows:
Here, ‘return type’ is the data type, which is returned by the function to which it
points. The next program will illustrate this:
#include<stdio.h>
#include<conio.h>
void main()
int num;
int sum();
int (*funptr)();
clrscr();
getch();
int sum()
int a=10,b=30;
return(a+b);
}
205
e.g.
struct employee
int eno;
char ename[20];
int age;
};
employee e1;
We have seen, how to point to an int or float, can we point to structures also.
Yes. Same as normal data type we can point to structure as well. But the
accessing of structure elements using pointers is different. To access structure
element using structure variable we use ‘.’(dot) operator. While to access
structure element using pointer variable you have to use ‘→’ operator. That
means, on the left hand side of ‘.’(dot), it should structure variable and on the
left hand side of ‘→’, it should be pointer to the structure.
206
#include<stdio.h>
#include<conio.h>
void main()
struct employee
int eno;
char ename[20];
int age;
};
clrscr();
printf("\n\tEmp.No.\tName\tAge");
printf("\n\n\t%d\t%s\t%d",e1.eno,e1.ename,e1.age);//using structure
printf("\n\n\t%d\t%s\t%d",p‐>eno,p‐>ename,p‐>age);//using pointer
getch();
}
207
#include<stdio.h>
#include<conio.h>
void main()
struct emp
int eno;
int *p;
};
int sal=30000;
clrscr();
e1.eno=100;
e1.p=&sal;
getch();
}
208
Consider you are developing a C program for payroll system. You have declared
an array of 50 integers to store employee numbers. C compiler will
automatically reserve 100 bytes of memory. Because data type is integer and
one integer takes 2 bytes. But at the runtime of program, you have stored
information for 30 employees only. That means, you are wasting 40 bytes of
memory. Or, it may happen, you want to store information for more than 50
employees. In that case, you can not increase the array size at run time. Don’t
worry. C has solution for this problem also – Dynamic memory allocation! You
can allocate memory space at the runtime from free space. There are some
standard library functions in C, which allows you to allocate and de‐allocate
memory space, malloc(), calloc(), realloc() and free() are important ones. We will
how these functions work in details:
11.7.1 malloc()
The function malloc() is used to allocate a block of memory from the free space.
It request for a block to the operation system. If it gets the block of memory, it
returns the starting pointer of the block; otherwise it returns the value NULL.
Prototype: void *malloc(size_t size);
11.7.2 free()
Whenever you allocate memory using ‘malloc()’ or ‘calloc()’ function, C compiler
reserves that much space for you. After using this space, you should de‐allocate
this space for future use. It can be achieved using free() function.
Prototype: void free(void *ptr);
The following program will illustrate use of malloc(), free() functions and sizeof()
operator.
209
#include<stdio.h>
#include<conio.h>
#include<stdlib.h>
void main()
char *str;
int i,len;
clrscr();
str = (char*)malloc(sizeof(char)*len);
if(str==NULL)
exit(0);
for(i=0;i<len;i++)
str[i] = rand()%26+'a';
str[i]='\0';
free(str);
getch();
}
210
11.7.4 calloc()
The function ‘calloc()’ is same as the function ‘malloc()’ but it requires two
argument. First is number of variables and second is size required for each
variable.
Prototype : void *calloc(size_t num, size_t size);
11.7.4 realloc()
If you allocate memory using function malloc() or calloc(), you can reallocate this
size using realloc() function. That means you can expand or reduce the amount
of bytes allocated using realloc() function.
11.8 Summary
A pointer is a variable which can store memory address.
A pointer can store address of all primitive data types and user defined
data types.
A pointer can store address of anther pointer variable.
An array of character is known as string.
12
FILE HANDLING
Unit Structure
12.0 Objectives
12.1 Introduction
12.3.1 fgetc()
12.3.2 fputc()
12.3.3 fgets()
12.3.4 fputs()
12.3.5 fscanf()
12.3.6 getw()
12.3.7 putw()
12.3.8 fread()
12.3.9 fwrite()
12.3.10 fseek()
12.4 Summary
12.0 OBJECTIVES
12.1 INTRODUCTION
Whenever you run your program, where does C stores your data? Definitely, in
Main Memory(RAM). But, as soon as you close the program, the entire data will
be erased from Main Memory. Then, what to do for storing this data
permanently? Is there any solution for this? Yes! You can store this data on hard
drive using Files. C provides very efficient method to store your data into files.
There are numerous standard library functions in C for file handling. They are
easy to use and efficient to handle data.
Data
Program
C compiler deals with two types of I/O function, Console I/O and File I/O. In
general, Console I/O functions refer to read from the keyboard and write to the
display screen. The main difference between these two types of I/O function is,
program can read/write in the same file.
To store data into files, any C program has to follow the sequence:
If you want to perform any file I/O operation, you must open the file using
function ‘fopen()’. The function takes two parameters as arguments. One is
name of the file and the second is ‘mode’ in which you want to open the file.
‘Mode’ specifies whether you want to open file as ‘Read‐Only’, ‘Write‐Only’,
’Both Read and Write’, ‘Append’, ‘Overwrite’.
For example:
FILE *fp;
fp = fopen(“test.txt”,”w”);
The file structure is defined in the file stdio.h file. The first line in the example is
declaration of the pointer to the file structure. The function fopen() will open
the file ‘test.txt’ in write mode. If it opens the file successfully, it returns Pointer
the given file. This Pointer will be used for other operations on file such as
reading or writing.
Once you have opened file, you are ready to do operations such as reading or
writing. To write into file, ‘fprintf()’ function is used. It requires two parameters.
First is pointer to file structure and second is the message or text that you want
to write.
For example:
fprintf(fp,”Hello World!”);
214
Once you finished your work (reading/writing) with files, you should close the
file. The standard C library has provided ‘fclose()’ function for closing the file.
For example:
fclose(fp);
#include<stdio.h>
void main()
FILE *fp;
fp = fopen("new.txt","w");
if(fp==NULL)
printf("Unable to Open...");
exit();
fclose(fp);
}
215
If you are using DOS system, it supports two types of file modes‐ text and binary.
The default is text mode. When you specify the file type as binary, the file I/O
functions do not interpret files content but in case of text files, it has to
interpret it.
• When you read from a text file, it considers the ASCII ‘0x1a’ as the end of
file character.
• In DOS systems, the sequence 0x0d 0x0a on the disk is stored as the
newline character for text files.
Accessing files in binary mode is faster, compared to text mode. You can see the
files exactly as it is on disk, bytes by bytes.
12.3.1 fgetc()
The function fgetc() returns the next characters in the file stream. At the end of
file, it returns EOF. The syntax of the function is as follows:
12.3.2 futc()
The function fputc() is used to write a character in the file. Syntax is:
12.3.3 fgets()
The function fgets() is used to read entire line(string) from a file. Its syntax is:
#include <stdio.h>
void main()
{
FILE * fp;
char mystring [100];
fp = fopen ("test.txt" , "r");
if (fp == NULL) perror ("Error opening file");
else {
fgets (mystring , 100 , fp);
puts (mystring);
fclose (fp);
}
}
12.3.4 fputs()
The function fputs() is used to write an entire line(string) into the file. The syntax
is:
12.3.5 fscanf()
Using fscanf() function, you can read the data into a specific format. Its syntax is:
For Example:
int num;
fscanf(fp,”%d”,&num);
217
12.3.6 getw()
The function getw() returns the next integer in the file. Avoid use of this function
with text files. The syntax is:
12.3.7 putw()
The function is used to write an integer into the file. The syntax is:
12.3.8 fread()
The function fread() is used to read an array of elements of specified size. The
syntax is:
12.3.9 fwrite()
This function is used to write an array of elements to the file. Syntax is as
follows:
size_t fwrite(const void * ptr, size_t size, size_t count, FILE *fp);
12.3.10 fseek()
The function is used to set the pointer of file to the specified position. The
syntax of the function is:
Here ‘offset’ refers to the number of bytes from origin. The following
constants are defined for the origin.
218
For Example
#include <stdio.h>
int main ()
{
FILE * fp;
fp = fopen ( "test.txt" , "w" );
fputs ( "Hello World." , fp);
fseek ( fp , 9 , SEEK_SET );
fputs ( " Sagar" , fp);
fclose ( fp );
return 0;
}
12.4 SUMMARY
• DOS systems can handle two types of file, text and binary.
• Working with binary files is faster than text files.
• Functions like fgetc(), fgets(), fread() etc. are used to read data from the
file.
• Functions like fputc(), fputs(), fwrite() etc. are used to write data to the file.
• What is a file?
• What are three steps that are followed while accessing a file?
• Write a program to convert the case of alphabets in a text file. Uppercase
letters should be converted to lower case and vice versa.
220
221
13
LINEAR LINK LIST
Unit Structure
13.1 Objectives
13.2 Introduction
13.6 Summary
13.7.1 Questions
13.1 OBJECTIVES
After going through this chapter you will be able to
13.2 INTRODUCTION
Linked lists are some of the most fundamental data structures. Linked lists
consist of a number of elements grouped, or linked, together in a specific order.
They are useful in maintaining collections of data, similar to the way arrays are
often used. However, linked lists offer important advantages over arrays in
many cases. Specifically, linked lists are considerably more efficient in
performing insertions and deletions. Linked lists also make use of dynamically
allocated storage, which is storage at runtime. Since in many applications the
size of data is not known at compile time, this can be a nice attribute as well.
void ArrayTest()
int scores[100];
scores[0] = 1;
scores[1] = 2;
scores[2] = 3;
Here is a drawing of how the scores array might look like in memory. The key
point is that the entire array is allocated as one block of memory. Each element
in the array gets its own space in the array. Any element can be accessed
directly using the [ ] syntax.
224
1) The size of the array is fixed — 100 elements in this case. Most often this size
is specified at compile time with a simple declaration such as in the example
above. With a little extra effort, the size of the array can be deferred until the
array is created at runtime, but after that it remains fixed. You can go to the
trouble of dynamically allocating an array in the heap and then dynamically
resizing it with realloc(), but that requires some real programmer effort.
As we will see, linked lists allocate memory for each element separately and
only when necessary.
225
An array allocates memory for all its elements lumped together as one block of
memory. In contrast, a linked list allocates space for each element separately in
its own block of memory called a "linked list element" or "node". The list gets is
overall structure by using pointers to connect all its nodes together like the links
in a chain.
Each node contains two fields: a "data" field to store whatever element type the
list holds for its client, and a "next" field which is a pointer used to link one node
to the next node. The next pointer of the last element is set to NULL, a
convenient sentinel marking at the end of the list. The element at the start of
the list is its head; the element at the end of the list is its tail (see Figure 13‐1).
Each node is allocated in the heap with a call to malloc(), so the node memory
continues to exist until it is explicitly deallocated with a call to free(). The front
of the list is a pointer to the first node. To access an element in a linked list, we
start at the head of the list and use the next pointers of successive elements to
move from element to element until the desired element is reached. A linear
linked list can be traversed in only one direction – from head to tail – because
each element contains no link to its predecessor. Therefore, if we start at head
and move to some element, and then wish to access an element preceding it,
we must start over at the head.
head
tail
Figure 13‐2. Elements of a linked list linked but scattered about an address
space.
The basic operations performed when using a linear link list are
node = list.firstNode
count = 0
display node.data
count++
node = node.next
227
return count
In the above algorithm, we initialize 2 variables, node and count, node points to
the first node in the list and count is the counter variable used to count the
number of nodes in the list. The while loop tests for the end of the list condition.
If the list is empty then the value of node will be NULL on the first iteration and
the while loop will exit just before the first iteration. If the list is not empty it will
display the value in the data field and increment the counter variable. At the
bottom of the while loop, node = node.next advances the node pointer to the
next node in the list till it reaches the value NULL.
node = list.firstNode
info = 13
if(info == node.data)
display node
exit
node = node.next
}
228
In the while loop, we check for the data value we are searching (in this case 13).
When found we display the node and exit the loop.
newNode.next = node.next
node.next = newNode
A new node can also be inserted at the end of the list using the above function.
However, inserting a node at the beginning of the link list requires changing the
head of the current node to the new node.
newNode.next = list.firstNode
list.firstNode = newNode
obsoleteNode = node.next
node.next = node.next.next
destroy obsoleteNode
{
230
obsoleteNode = list.firstNode
destroy obsoleteNode
Notice that removeBeginning() will set the list.firstNode to null when removing
the last node in the list.
Kindly note, any of the special cases of linked list operations, like
insertBeginning() and removeBeginning(), can be eliminated by including a
dummy element at the front of the list. This ensures that there are no special
cases for the beginning of the list and renders both insertBeginning() and
removeBeginning() unnecessary. In this case, the first useful data in the list will
be found at list.firstNode.next. A dummy node is one that does not contain any
data. The front pointer in a linked list will point to the dummy node. It is easier
to code the insertion and deletion operations if the first node on the list
contains no data. Otherwise, the code to insert a new first node or to delete the
current first node must be different from the code to handle the normal cases.
The solution to this problem is to allow nodes that are dynamic, rather than
static. That is, when a node is needed, storage is reserved for it, and when it is
no longer needed, the storage is released. Thus the storage for nodes that are
no longer in use is available for another purpose. Also, no predefined limit on
the number of nodes is established. As long as sufficient storage space is
available to the job as a whole, part of that storage can be reserved for use as a
node.
Bad Pointer: A pointer which does not have an assigned pointee is "bad" and
should not be dereferenced. In C and C++, a dereference on a bad
sometimes crashes immediately at the dereference and sometimes
randomly corrupts the memory of the running program, causing a crash or
incorrect computation later. That sort of random bug is difficult to track
down. In C and C++, all pointers start out with bad values, so it is easy to
use bad pointer accidentally. Correct code sets each pointer to have a good
value before using it. Accidentally using a pointer when it is bad is the most
common bug in pointer code. In Java and other runtime oriented languages,
pointers automatically start out with the NULL value, so dereferencing one
is detected immediately. Java programs are much easier to debug for this
reason.
int *p;
The statements
dynamically create the integer variable *pi and the float variable *pr. These
variables are called dynamic variables. In executing these statements, the
operator sizeof returns the size, in bytes, of its operand. malloc can then create
an object of that size. Thus malloc(sizeof(int)) allocates storage for an integer,
whereas malloc(sizeof(float)) allocates storage for a floating‐point number.
malloc also returns a pointer to the storage it allocates. This pointer is to the
first byte of that storage and is of the type char *. To coerce this pointer so that
it points to an integer or real, we use the cast operator (int *) or (float *).
233
free(p);
makes any future references to the variable *p illegal (unless, of course, a new
value is assigned to p by an assignment statement or by a call to malloc). Calling
free(p) makes the storage occupied by *p available for reuse, if necessary.
void *data;
}LLIST;
Creating a new node is simple. The memory needed to store the node is
allocated, and the pointers are set up.
LLIST *node;
node‐>data=data;
node‐>next=NULL;
234
return node;
LLIST *newnode;
newnode=list_create(data);
newnode‐>next = node‐>next;
node‐>next = newnode;
return newnode;
The above code cannot insert at the beginning of the list, so we have a separate
function for this. This code creates and inserts the new node and returns the
new head of the list:
LLIST *newnode;
newnode=list_create(data);
newnode‐>next = list;
return newnode;
}
235
list=list‐>next;
if(list‐>next)
list‐>next=node‐>next;
free(node);
return 0;
while(node)
node=node‐>next;
236
return 0;
The above function tests to see if the value of a given node matches some
string.
The searching function will return the first node to which the supplied function
pointer returns a positive number.
while(node)
node=node‐>next;
return NULL;
237
Example 13‐1. Header for the Linear Link List Abstract Datatype
/*
* File: llist.h
* ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
*/
#ifndef LLIST_H
#define LLIST_H
/*
* Type: LLIST
* ‐‐‐‐‐‐‐‐‐‐‐‐‐‐
* This is the type for a linear link list, i.e., it is a type that
*/
void *data;
}LLIST;
/*
* Function: list_create
* Usage: list_create(&data);
* ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
* A new link list is created and the data field of the head node is
* given.
*/
* ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
*/
/* Function: list_remove
* ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
239
*/
/*
* ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
*/
/*
* File: llist.c
* ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
240
*/
#include <stdio.h>
#include "llist.h"
LLIST *node;
node‐>data=data;
node‐>next=NULL;
return node;
LLIST *newnode;
newnode=list_create(data);
newnode‐>next = node‐>next;
node‐>next = newnode;
return newnode;
}
241
LLIST *newnode;
newnode=list_create(data);
newnode‐>next = list;
return newnode;
list=list‐>next;
if(list‐>next)
list‐>next=node‐>next;
free(node);
return 0;
while(node)
node=node‐>next;
242
return 0;
while(node)
node=node‐>next;
return NULL;
}
243
13.6 SUMMARY
Linked lists consist of a number of elements grouped, or linked, together in a
specific order. Each element is called a node.
Each node contains two fields: a "data" field to store information, and a
"next" field which is a pointer to the next node.
The next pointer of the last element is set to NULL, to mark the end of the
list.
The element at the start of the list is its head; the element at the end of the
list is its tail.
Each node is dynamically allocated using malloc(), so the node memory
continues to exist until it is explicitly deallocated with a call to free().
A linear linked list can be traversed in only one direction – from head to tail
– because each element contains no link to its predecessor.
To traverse a linked list, you start at head and then go from link to link, using
each link’s next field to find the next link.
To search an element in a linked list, we start at the head of the list and use
the next pointers of successive elements to move from element to element
until the desired element is reached.
A node can be inserted either at the beginning of the link list or after a
particular node.
Inserting an item at the beginning of a linked list involves changing the new
link’s next field to point to the old first link and changing first to point to the
new item.
A node can be removed from the beginning of the list or after a given node.
Deleting an item at the beginning of a list involves setting first to point to
first.next.
13.7.1 Questions
These questions are intended as a self‐test for readers.
2. Write separate C routines for removing a node from the link list,
removing the head node from the link list and destroying the entire link
list.
3. Write a menu driven program that allows the user to create a list, insert
a node, delete a node, count the number of nodes, display the list. The
program should display the menu as follows:
=========================================
1. Create
2. Insert Node at Beginning
3. Insert Node in Middle
4. Deleting a Node
5. Counting the Number of Nodes
6. Displaying the list
7. Exit
Once the list has been created and if the user selects the menu of creating
the list again, it should mention that a list has been created and append the
list.
• 14
• STACK
Unit Structure
14.1 Objectives
14.2 Introduction
14.3.1 Abstraction
14.7 Summary
14.1 OBJECTIVES
14.2 INTRODUCTION
14.3.1 Abstraction
Now, let's think about a stack in an abstract way i.e., it doesn't hold any
particular kind of thing (like tennis balls) and we aren't restricting ourselves to
any particular programming language or any particular implementation of a
stack.
Stacks hold objects, usually all of the same type. Most stacks support
just a simple set of operations; and thus, the main property of a stack is that
objects go on and come off of the top of the stack.
Here are the minimal operations we'd need for an abstract stack (and
their typical names):
Pop: Removes an object from the top of the stack and produces that object.
‐‐‐‐‐
stack
‐‐‐‐‐
| A | <‐‐ top
‐‐‐‐‐
stack
‐‐‐‐‐
| B | <‐‐ top
‐‐‐‐‐
|A|
‐‐‐‐‐
stack
‐‐‐‐‐
| C | <‐‐ top
‐‐‐‐‐
|B|
‐‐‐‐‐
|A|
‐‐‐‐‐
stack
250
‐‐‐‐‐ ‐‐‐‐‐
‐‐‐‐‐ ‐‐‐‐‐
|A| letter
‐‐‐‐‐
Stack
‐‐‐‐‐ ‐‐‐‐‐
| A | <‐‐ top | B |
‐‐‐‐‐ ‐‐‐‐‐
stack letter
‐‐‐‐‐
| D | <‐‐ top
‐‐‐‐‐
|A|
‐‐‐‐‐
stack
You'll notice that the stack enforces a certain order to the use of its
contents, i.e., Last In First Out. Thus, we say that a stack enforces LIFO order.
251
Consider an example:
‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ ‐‐‐‐‐
|A|B| | | |1|
‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ ‐‐‐‐‐
0 1 2 3 top
contents
252
Since B is at the top of the stack, the value top stores the index of B in the array
(i.e., 1).
‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ ‐‐‐‐‐
|A|B|C| | |2|
‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ ‐‐‐‐‐
0 1 2 3 top
contents
(Note that both the contents and top part have to change.)
letter = Pop(stack)
0 1 2 3 top letter
contents
tter = Pop(stack)
0 1 2 3 top letter
contents
253
letter = Pop(stack)
| | | | | | ‐1| |A|
0 1 2 3 top letter
contents
so that you can see what value top should have when it is empty, i.e., -1.
if (empty(s))
return TRUE
else
i = pop(s)
print (i)
endif
254
We can write a function to implement the operation empty(s) that returns TRUE
if the stack is empty and FALSE if it is not empty, as follows
empty(ps)
return(TRUE);
else
return(FALSE);
} /* end empty */
Once this function exists, a test for the empty stack is implemented by the
statement
if (empty(&s))
/* stack is empty */
else
Note the difference between the syntax of the call to empty in the
algorithm and in the program segment above. In the algorithm, s represents a
stack and the call to empty was expressed as empty(s).
convention that we pass the address of the stack structure rather than the stack
itself. (This restriction has been omitted from many newer compilers.)
This operation begins by checking whether or not the stack is empty. If the
stack is empty, a warning message stating that the stack is empty is printed and
the execution is stopped. If the stack is not empty, the top element on the stack
is removed and returned to the calling program. We also need to decrement the
value of the top of the stack by 1 as we have removed the top element and
returned it to the calling program.
BEGIN
if (top = = ‐1)
print(stack underflow)
else
top=top‐1
end if
END
256
We assume that the stack consists of integers, so that the pop operation
can be implemented as a function. However, if a stack consists of a more
complex structure (for example, a structure or a union), the pop operation
would either be implemented as returning a pointer to a data element of the
proper type (rather than the data element itself), or the operation would be
implemented with the popped value as a parameter (in which case the address
of the parameter would be passed rather than the parameter, so that the pop
function could modify the actual argument).
pop(ps)
if (empty(ps))
print(“Stack Underflow”);
exit(1);
} /* end if */
return(ps‐>items[ps‐>top‐‐]);
} /* end pop */
Let us look at the pop function more closely. If the stack is not empty,
the top element of the stack is retained as the return value. This element is then
removed from the stack by the expression ps ‐> top‐‐.
This function makes room for the new element to be pushed onto the stack
by incrementing the top by 1 and then inserts the new element into the array.
Begin
if (top = = max ‐ 1)
exit
else
top = top + 1
endif
End
push(ps, x)
int x;
259
if (ps‐>top = = STACKSIZE‐1)
printf(“Stack Overflow”);
exit(1);
else
ps‐>items[++(ps‐>top)] = x;
return;
} /* end push */
You should again note that if and when overflow is detected in push,
execution halts immediately after an error message is printed.
struct stack
int top;
260
int items[MAXSIZE];
} stackT;
Here we assume that the elements of the stack stackT contained in the
array stackT.items are integers and that the stack will at no time contain more
than 100 integers.
Stack_Push()
Stack_Pop()
Stack_IsEmpty()
We'll use the convention of placing the data structure name at the
beginning of the function name (e.g., Stack_IsEmpty). That will help us down
the line. For example, suppose we use 2 different data structures in a program,
both with IsEmpty operations‐‐our naming convention will prevent the 2
different IsEmpty functions from conflicting.
Stack_Init()
Stack_Destroy()
261
They are not part of the abstract concept of a stack, but they are necessary for
setup and cleanup when writing the stack in C.
Finally, while the array that holds the contents of the stack will be
dynamically‐allocated, it still has a maximum size. So, this stack is unlike the
abstract stack in that it can get full. We should add something to be able to test
for this state:
Stack_IsFull()
Stack_Push()
Stack_Pop()
Stack_IsEmpty()
Stack_Init()
Stack_Destroy()
Stack_IsFull()
/*
* File: stack.h
* ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
*/
#ifndef _STACK_H
#define _STACK_H
/*
* Type: stackElementT
262
* ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
* the stack.
*/
/*
* Type: stackT
* ‐‐‐‐‐‐‐‐‐‐‐‐‐‐
*/
typedef struct
stackElementT *contents;
int maxSize;
int top;
} stackT;
263
/*
* Function: Stack_Init
* ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
* stack.
*/
/* Function: Stack_Destroy
* Usage: Stack_Destroy(&stack);
* ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
*/
/*
* ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
* the top of the stack and remove an element from the top of the
* stack.
*/
/*
* ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
* or full (respectively).
*/
Stack_Init():
The first function we'll implement is Stack_Init(). It will need to set up a
stackT structure so that it represents an empty stack.
It needs to change the stack passed to it, so the stack is passed by reference
(stackT *). It also needs to know what the maximum size of the stack will be
(i.e., maxSize).
stackElementT *newContents;
if (newContents == NULL)
stackP‐>contents = newContents;
266
stackP‐>maxSize = maxSize;
Note how we make sure that space was allocated (by testing the pointer against
NULL). Also, note that if the stack was not passed by reference, we could not
have changed its fields.
Stack_Destroy():
The next function we'll consider is the one that cleans up a stack when we are
done with it. It should get rid of any dynamically‐allocated memory and set the
stack to some reasonable state.
and should reset all the fields set by the initialize function:
free(stackP‐>contents);
stackP‐>contents = NULL;
stackP‐>maxSize = 0;
However, then some of the stack functions would take pointers (e.g., we need
them for StackInit(), etc.) and some would not. It is more consistent to just
pass stacks by reference (with a pointer) all the time. Furthermore, if the struct
stackT is large, passing a pointer is more efficient (since it won't have to copy a
big struct).
Emptiness
Now, testing for emptyness is an easy operation. We've said that the top field is
‐1 when the stack is empty. Here's a simple implementation of the function.
Fullness
Testing for fullness is only slightly more complicated. Let's look at an example
stack.
‐‐‐‐‐ ‐‐‐‐‐
|A| |0|
‐‐‐‐‐ ‐‐‐‐‐
0 top
contents
We can see from this example that when the top is equal to the maximum size
minus 1 (e.g., 0 = 1 ‐ 1), then it is full. Thus, our fullness function is.
This illustrates the importance of keeping the maximum size around in a field
like maxSize.
Stack_Push():
Now, pushing onto the stack requires the stack itself as well as something to
push. So, its prototype will look like:
The function should place an element at the correct position in the contents
array and update the top. However, before the element is placed in the array,
we should make sure the array is not already full...Here is the body of the
function:
269
if (Stack_IsFull(stackP))
stackP‐>contents[++stackP‐>top] = element;
Note how we used the prefix ++ operator. It increments the top index before it
is used as an index in the array (i.e., where to place the new element).
Also note how we just reuse the StackIsFull() function to test for fullness.
Stack_Pop():
Finally, popping from a stack only requires a stack parameter, but the value
popped is typically returned. So, its prototype will look like:
The function should return the element at the top and update the top. Again,
before an element is removed, we should make sure the array is not empty.
Here is the body of the function:
270
if (Stack_IsEmpty(stackP))
return stackP‐>contents[stackP‐>top‐‐];
Note how we had the sticky problem that we had to update the top before the
function returns, but we need the current value of top to return the correct
array element. This is accomplished easily using the postfix ‐‐ operator, which
allows us to use the current value of top before it is decremented.
/*
* File: stack.c
* ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
*/
#include <stdio.h>
#include "stack.h"
271
stackElementT *newContents;
if (newContents == NULL)
stackP‐>contents = newContents;
stackP‐>maxSize = maxSize;
free(stackP‐>contents);
272
stackP‐>contents = NULL;
stackP‐>maxSize = 0;
if (Stack_IsFull(stackP))
stackP‐>contents[++stackP‐>top] = element;
if (Stack_IsEmpty(stackP))
return stackP‐>contents[stackP‐>top‐‐];
273
14.7 SUMMARY
The important stack operations are pushing (inserting) an item onto the top
of the stack and popping (removing) the item that’s on the top.
You can access any element in an array, which is not true for a stack, since
you can only deal with the element at its top.
1. Questions
These questions are intended as a self‐test for readers.
1. Suppose you push 10, 20, 30, and 40 onto the stack. Then you pop three
items. Which one is left on the stack?
2. Programming Projects
Writing programs that solve the Programming Projects helps to solidify your
understanding of the material and demonstrates how the chapter’s concepts
are applied.
(Hint: Use unions and use switch case when popping and pushing
elements)
15
QUEUES
Unit Structure
15.1 Objectives
15.2 Introduction
15.4 Abstraction
15.7 Summary
15.1 OBJECTIVES
15.2 INTRODUCTION
The word queue is British for line (the kind you wait in). In Britain, to “queue up”
means to get in line. In computer science a queue is a data structure that is
somewhat like a stack, except that in a queue the first item inserted is the first
to be removed (First‐In‐First‐Out, FIFO), while in a stack, as we’ve seen, the last
item inserted is the first to be removed (LIFO). A queue works like the line at the
movies: The first person to join the rear of the line is the first person to reach
the front of the line and buy a ticket. The last person to line up is the last person
to buy a ticket (or—if the show is sold out—to fail to buy a ticket). The figure
15.1 shows how such a queue looks.
A queue can be defined as an ordered collection of items from which items may
be deleted at one end (called the front of the queue) and into which items may
be inserted at the other end (called the rear of the queue). Queues are efficient
data structures useful for storing and retrieving data in a first‐in, first‐out, or
FIFO, order. This allows us to retrieve data in the same order as it was stored.
In an array, any item can be accessed, either immediately (if its index number is
known) or by searching through a sequence of cells until it’s found. In a queue,
however, access is restricted: only one item can be read or removed at a given
time. The interface of a queue is designed to enforce this restricted access.
Access to other items is (in theory) not allowed.
280
Queues (like stacks) are more abstract entities than arrays and many other data
storage structures. They’re defined primarily by their interface: the permissible
operations that can be carried out on them. The underlying mechanism used to
implement them is typically not visible to their user.
The underlying mechanism for a queue, for example, can be an array, or it can
be a linked list. The underlying mechanism for a linked list can be an array or a
special kind of tree called a heap.
Queues also used to model real‐world situations such as people waiting in line
at a bank, airplanes waiting to take off, or data packets waiting to be
transmitted over the Internet. There are various queues quietly doing their job
in your computer’s (or the network’s) operating system. There’s a printer queue
where print jobs wait for the printer to be available. A queue also stores
keystroke data as you type at the keyboard. This way, if you’re using a word
processor but the computer is briefly doing something else when you hit a key,
the keystroke won’t be lost; it waits in the queue until the word processor has
time to read it. Using a queue guarantees the keystrokes stay in order until they
can be processed.
In figure 15.3 two elements have been deleted from the queue. Since elements
may be deleted only from the front of the queue and one element at a time, 59
is removed first and 26 is now at the front. When 26 is deleted from the queue,
94 is now at the front.
15.4 ABSTRACTION
Now, let's think about a queue in an abstract way i.e., it doesn't hold any
particular kind of thing and we aren't restricting ourselves to any particular
programming language or any particular implementation of a queue.
Queues hold objects, usually all of the same type. Most queues support just a
simple set of operations; and thus, the main property of a queue is that objects
are inserted at the rear and removed from the front of the queue.
Here are the minimal operations we'd need for an abstract queue (and their
typical names):
282
Remove: Removes an object from the front of the queue and produces that
object.
As we are thinking about a queue in an abstract way, the insert operation can
always be performed, since there is no limit to the number of elements a queue
may contain. The remove operation, however, can be applied only if the queue
is nonempty; there is no way to remove an element from a queue containing no
elements. The result of an illegal attempt to remove an element from an empty
queue is called underflow. The empty operation is, of course, always applicable.
Let us try to understand the three primitive operations that can be applied to a
queue without restricting ourselves to any programming language or any
particular implementation of the queue.
We also need to remember that if the queue contains only a single element
then after removing that element we must set the rear to NULL.
/*
* File: queue.h
* ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
*/
/*
285
* File: queue.c
* ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
*/
#include <stdio.h>
#include <stdlib.h>
#include "queue.h"
*front = *rear = 0;
/* insert an element
*/
q[(*rear)++] = element;
/* remove an element
286
*/
return q[(*front)++];
*/
*/
/*
* File: testqueue.c
* ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
*/
#include <stdio.h>
#include <stdlib.h>
#include "queue.h"
#define size 10
void main()
int front,rear,element;
int queue[size];
// initialize queue
init(&front,&rear);
// insert elements
while(full(rear,size) != 1)
element = rand();
insert(queue,&rear,element);
printf("front=%d,rear=%d\n",front,rear);
getchar();
}
288
printf("queue is full\n");
while(!empty(front,rear))
element = remove(queue,&front);
getchar();
printf("queue is empty\n");
getchar();}
At a stage when we have inserted 8 elements and removed 3, the queue will
look like as shown in the figure below (Figure 15.4). Unlike the situation in a
stack, the items in a queue don’t always extend all the way down to index 0 in
the array. After some items are removed, front will point at a higher index as
shown in figure 15.4. It is possible to reach the absurd situation where the
queue is empty, yet no new element can be inserted (see if you can come up
with a sequence of insertions and deletions to reach that situation). Clearly, the
array representation outlined in the foregoing is unacceptable.
289
One solution is to modify the remove operation so that when an item is deleted,
the entire queue is shifted to the beginning of the array. The remove operation
would then be modified to
x = q.items[0];
q.items[i] = q.items[i+1];
q.rear‐‐;
The queue need no longer contain a front field, since the element at position 0
of the array is always at the front of the queue.
This method, however, is too inefficient. Each deletion involves moving every
remaining element of the queue. If a queue contains 500 or 1000 elements, this
is clearly a high price to pay. Further, the operation of removing an element
from the queue logically involves manipulation of only one element: the one
currently at the front of the queue. The implementation of that operation
should reflect this and should not involve a host of extraneous operations (see
exercise 15.8.2 problem 4 for more efficient alternative).
290
Another solution is to view the array that holds the queue as a circle rather than
as a straight line. That is, we imagine the first element of the array (that is, the
element at position 0) as immediately following its last element. This implies
that even if the last element is occupied, a new value can be inserted behind it
in the first element of the array as long as that first element is empty. This can
be clearly understood in the figure given below (figure 15.5).
In a straight queue, we can check for emptiness by the evaluating the condition
whether rear is less than front (.i.e. q.rear < q.front). Unfortunately, it is difficult
under this representation to determine when the queue is empty, as the
mentioned condition is no longer valid. Figure 15.5 illustrates this situation.
One way of solving this problem is to establish the convention that the value of
q.front is the array index immediately preceding the first element rather than
the index of the first element itself. Thus since q.rear is the index of the last
element, the condition q.front = = q.rear implies that the queue is empty.
291
struct queue
int items[MAXQUEUE];
};
struct queue q;
Since q.rear = q.front, the queue is initially empty. The empty function can be
coded as
empty(pq)
} /* end empty */
remove(pq)
if (empty(pq))
{
292
printf(“queue underflow”);
exit(1);
} /* end if */
if (pq‐>front == MAXQUEUE – 1)
pq‐>front = 0;
else
(pq‐>front)++;
return (pq‐>items[pq‐>front]);
} /* end remove */
The insert operation involves testing for overflow, which occurs when the entire
array is occupied by items of the queue and an attempt is made to insert yet
another element into the queue. When an array is full, this situation is indicated
by the fact that q.front equals q.rear, which is precisely the indication for
underflow (.i.e. an empty queue). It seems that there is no way to distinguish
between the empty queue and the full queue under this implementation. Such a
situation is clearly unsatisfactory.
One solution is to sacrifice one element of the array and to allow a queue to
grow only as large as one less than the size of the array. Thus, in an array of 100
elements can only contain 99 elements. An attempt to insert a hundredth
element causes an overflow.
293
insert(pq, x)
int x;
if (pq‐>rear = = MAXQUEUE – 1)
pq‐>rear = 0;
else
(pq‐>rear)++;
if (pq‐>rear = = pq‐>front)
printf(“queue overflow”);
exit(1);
} /* end if */
pq‐>items[pq‐>rear] = x;
return;
} /* end insert */
Note that the test for overflow in the insert operation occurs after pq‐>rear has
been adjusted, whereas the test for underflow in the remove operation occurs
immediately upon entering the routine, before pq‐>front is updated.
294
In a queue items are deleted from the front of a queue and inserted at the rear.
Let a pointer to the first element of a list represent the front of the queue.
Another pointer to the last element of the list represents the rear of the queue,
as shown in the figure 15.6.
Under the list representation, a queue consists of a list and two pointers, q.front
and q.rear. The algorithms for remove operation and insert operation are given
below.
if (empty(q))
printf(“queue underflow”);
exit(1);
p = q.front;
x = info(p);
295
q.front = next(p);
if (q.front == null)
q.rear = null;
freenode(p);
return(x);
p = getnode();
info(p) = x;
next(p) = null;
if (q.rear == null)
q.front = p;
else
next(q.rear) = p;
q.rear = p;
Another disadvantage is the additional time spent in managing the available list.
Each addition and deletion of an element from a queue involves a
corresponding deletion or addition to the available list.
An advantage of using linked list is that there are no restrictions on the number
of insertions as in the case of arrays where the number of elements is pre‐
defined. Also storage is reserved as and when required, thus, enabling memory
to be used for other purposes if required.
struct queue
}q;
In the above declaration, front and rear are pointers to the first and last nodes
of a queue represented as a list. The empty queue is represented by front and
rear both equalising the null pointer. The function empty need check only one of
these pointers, since in a nonempty queue neither front nor rear will be NULL.
empty(pq)
} /* end empty */
insert(pq, x)
int x;
NODEPTR p;
p = getnode();
p‐>info = x;
p‐>next = NULL;
if (pq‐>rear == NULL)
pq‐>front = p;
else
(pq‐>rear)‐>next = p;
pq‐>rear = p;
} /* end insert */
The function remove deletes the first element from the queue and returns its
value:
remove(pq)
NODEPTR p;
int x;
if (empty(pq))
printf(“queue underflow”);
exit(1);
}
298
p = pq‐>front;
x = pq‐>info;
pq‐>front = p‐>next;
if (pq‐>front == NULL)
pq‐>rear == NULL;
freenode(p);
return(x);
} /* end remove */
15.7 SUMMARY
Questions
These questions are intended as a self‐test for readers.
Programming Projects
Writing programs that solve the Programming Projects helps to solidify your
understanding of the material and demonstrates how the chapter’s concepts
are applied.
8. If an array holding a queue is not considered circular, the text suggests that
each remove operation must shift down every remaining element of a
queue. An alternative method is to postpone shifting until rear equals the
last index of the array. When that situation occurs an attempt is made to
insert an element into the queue, the entire queue is shifted down, so that
300
the first element of the queue is in position 0 of the array. What are the
advantages and disadvantages of this method over performing a shift at
each operation? Rewrite the routines remove, insert and empty using this
method.