Block 2
Block 2
Block
2
PROGRAMMING IN C
UNIT 6
Control Structures-II 5
UNIT 7
Pointers and Arrays 37
UNIT 8
Functions 63
UNIT 9
Functions-II 93
UNIT 10
Files and Structs, Unions and Bit-fields 119
Curriculum Design Committee
Dr. B.D. Acharya Prof. O.P. Gupta Prof. C. Musili
Dept. of Science & Technology Dept. of Financial Studies Dept. of Mathematics and Statistics
New Delhi University of Delhi University of Hyderabad
Prof. Adimurthi Prof. S.D. Joshi Prof. Sankar Pal
School of Mathematics Dept. of Electrical Engineering ISI, Kolkata
TIFR, Bangalore IIT, Delhi Prof. A.P. Singh
Prof. Archana Aggarwal Dr. R. K. Khanna PG Dept. of Mathematics
CESP, School of Social Sciences Scientific Analysis Group University of Jammu
JNU, New Delhi DRDO, Delhi
Faculty Members
Prof. R. B. Bapat Prof. Susheel Kumar School of Sciences, IGNOU
Indian Statistical Institute, New Delhi Dept. of Management Studies Dr. Deepika
Prof. M.C. Bhandari IIT, Delhi Prof. Poornima Mital
Dept. of Mathematics Prof. Veni Madhavan Dr. Atul Razdan
IIT, Kanpur Scientific Analysis Group Prof. Parvin Sinclair
DRDO, Delhi Prof. Sujatha Varma
Prof. R. Bhatia
Dr. S. Venkataraman
Indian Statistical Institute, New Delhi Prof. J.C. Mishra
Prof. A. D. Dharmadhikari Dept. of Mathematics
Dept. of Statistics IIT, Kharagpur
University of Pune
December 2007
©Indira Gandhi National Open University, 2007
ISBN-978-81-266-3245-2
All rights reserved. No part of this work may be reproduced in any form, by mimeograph or any other means without written permission from
the Indira Gandhi National Open University.
Further information on the Indira Gandhi National Open University courses may be obtained from the University’s office at Maidan Garhi,
New Delhi-110 068.
Printed and Published on behalf of Indira Gandhi National Open University, New Delhi, by Director, School of Sciences.
PROGRAMMING IN C
In this Block, in continuation with the first block, we study some more control
structures. As you know, control structures control the program flow. In the first Unit,
we will discuss two more control structures, the for(;;) and the binary if-then-else
structure.
We will discuss pointers and arrays in the second Unit of this block. Pointers are
closely related to arrays. So, it is not surprising that they are useful in working with
arrays. How pointers are not only useful in manipulation of arrays; we will use them in
the next block where we discuss data structures like linked lists, stacks and queues.
Functions in C allow us to design our programs in a modular fashion. We will devote
two units in this block for the discussion of functions. In these two units, we will see
how to declare and use functions. We will also discuss recursive functions, a feature
that enables us to develop elegant programs for many problems that are recursive in
nature. We will also discuss the scope of variables in functions.
We will also discuss arrays in greater detail in the third and fourth units. We will see
how they are convenient for storing homogeneous data like marks of students, etc. In
mathematics, we use arrays for storing matrices, vectors, coefficients of polynomials,
etc.
Real world problems can lead to large programs. Since it is difficult to maintain such
programs in a large single file, the program is usually split up into many files. To be
able to do so, we should be able to control the scope of variables in different files; we
have to decide, for example, which variables in file A should be accessible in file B and
which should not be accessible and vice-versa. This is discussed in third unit of this
block. We will also see how to allocate memory dynamically when the program is
running; this will avoid problems arising from allocating too much or too little memory
for data.
In scientific computing, we often generate data for analysis. But, we will be able to
analyse data only if we can save the data in a file. In the last Unit of the block, we will
discuss some functions of C that enable us to work with files. We will also discuss
struct and union, two data structures that allow us to work with heterogeneous, but
related data in a single variable. We will also discuss bit manipulation in this Unit.
Such techniques are especially useful in graphics programming.
UNIT 6 CONTROL STRUCTURES-II
Structure Page No.
6.1 Introduction 5
Objectives
6.2 The for (;;) Loop 5
6.3 Unidimensional Arrays 16
6.4 The Initialisation of Arrays and the sizeof Operator 21
6.5 Storage Classes and Scope 24
6.6 Summary 29
6.7 Solutions/Answers 29
6.1 INTRODUCTION
There are two ways in which the linear flow of control may be altered in computer
programs: one involves alternate paths provided by if ()-else or switch statements; the
other is through the repetitive execution of a set of statements. The first mechanism is
called a branch, the second a loop. We’ve seen examples of both in the last unit. In this
unit we will tour through the most common applications of loops: iterations in
mathematical calculations, and the processing of groups of related data items called
arrays. We will introduce you to the for loop in Sec. 6.2. In Sec. 6.3, we will then
discuss the simplest kind of arrays, the unidimensional arrays. Unidimensional arrays
are similar to vectors. Indeed, we will use them to represent vectors. If we have a
simple variable, we saw that we can declare and initialise it in one statement. However,
initialising arrays is not that simple. In Sec. 6.4, we will see how to initialise arrays.
We have seen that C program variables fall into four types; in addition, they have an
associated attribute called storage class, which for any variable determines two things:
1) When the variable comes into existence in an executing program and when it
ceases to exit;
2) The blocks (and, where relevant, files) of code in which the variable may be
referenced.
This “domain of visibility” is called the scope of the variable. This unit ends with
examples of the application of scope rules.
Objectives
After studying this unit, you should be able to
• write programs using the for (;;) loop;
• create and make use of one dimensional arrays;
• use the sizeof operator; and
• apply the register , auto and static storage class rules.
In this section, we introduce you to the for (;;) loop. As we mentioned in the
introduction, one of the most common applications of the for (;;) loop is for
processing arrays. What is an array? Vectors and Matrices are examples of arrays. The 5
Programming in C array concept enables a group of data to share a common name. Each datum in the
group–called an array element–is distinguished from other data by the assignment of a
unique number, called the array subscript, to each datum. The compiler assigns a block
of memory to store (at consecutive locations) the elements of the array. To access a
particular element, we can use its subscript. To illustrate how this could be useful, let us
take the following situation: A company has 8000 employees and it has to pay a bonus
of 10% to each of its employees. We can declare an array of 8000 elements called
employees, with each element of the array holding the wage of an employee. The wage
of each employee can be accessed using the subscript of the data. The task of finding
10% of the wages of employees is a repetitive task. We have to repeat the same set of
statements to find 10% of a number on each of the 8000 elements. So, we use a loop,
with the subscript of the array elements as loop index. In each iteration of the loop, we
access the wage of one employee and process it to find the bonus using the same set of
statements.
For processing an array, we will see that it is convenient to use a for loop although we
can manage quite often with a while loop that we discussed in Unit 5.
The for (;;) loop is at once the most versatile and the most popular of the three loop
structures of C, because it contains information about loop entry, control and re-entry
all in one place. The for (;;) statement evaluates three expressions, which are
separated by two semicolons in the parentheses following the keyword for . The
semicolons are a syntactical requirement for the for loop; the three expressions are not;
any or all of them may be absent. The general for (;;) loop has therefore the
following appearance:
for(;;)
for (first_expr; second_expr; third_expr)
the loop body, a simple or compound statement;
The statements within the loop’s body are executed repetitively when the loop is
entered.
Each component expression of the for (;;) statement has a particular (but not
essential) purpose. Most often, first_expr is an assignment expression that is used
to initialise the loop index: first_expr is evaluated before the loop is entered.
Though it is customary to initialise the loop index via first_expr it is not a
requirement: as in the while() loop, the loop index may be initialised by a preceding
statement; in any case, first_expr, if it is present, is evaluated first, before the loop
is entered.
Entry into the loop is determined by the truth or falsity of second_expr, which is a
Boolean. The loop is entered only if the Boolean is true, else it’s skipped. In either
case, whether the loop is entered or not, first_expr is computed. (If
second_expr is absent, it’s regarded as true, and the loop body is executed
anyway.)
For example:
f o r (i = 3; ;)
printf("%d\n", i);
does this just as well. In each case, the printf() statement is executed forever,
because an absent Boolean(the second_expression) is regarded as true. You
would have noticed that the third_expression is also missing. C syntax does not
require that third_expr should be present. Note well that the for (;;) loop
repeatedly executes the single (simple or compound) statement which follows it; on
occasion that statement may be the null statement; your logic may be such as to require
it. But, more often, placing a semicolon immediately after a for (;;) or a while ()
may be plain carelessness. The outcome of this may not be what you want. You have
been warned! Be on your guard!
The second of the expressions in a for (;;) statement is a Boolean; the loop is entered
if the Boolean evaluates to true; not otherwise. If first_expr assigns such a value
to the loop index that second_exp is false, the loop is not entered.
Since i is given the value 3 before the loop is entered, and since the condition for entry
is i = 5, which is false, the loop is skipped, and i is not printed. However, the
printf () in the next loop will be printed infinitely often:
f o r (i = 3; i = 1;)
printf("%d\n", i);
The third expression within a for (;;) is evaluated after each execution of the loop’s
body. If the loop condition, namely second_expr is still found to be true after its
last iteration, the loop’s statements are executed again; third_expr re-initialises the
loop index after each iteration; generally it increments the variable initialised by
first_expr; but it may change (increase or decrease) the index by any amount.
second_expr determines whether this is new value of the loop index is such as to
allow a further iteration of the loop statement.
Initially, i is 3. The Boolean i < 5 is true; the loop is entered, and the current value
of i is printed. Then the loop index i is re-initialised, so that it gets the value 4. The
Boolean is found to be still true(i < 5); the loop body is executed once more.
Next the third_expr in the for (;;) statement sets i to 5; the Boolean becomes
false; the loop is not entered again.
Since first_expr is evaluated only once, it’s often used to print an introductory
remark before loop entry. See the program in Listing 4 on page 9. In the program
in Listing 1 on the next page we use the for (;;) loop to sum the integers from 1 to
100; initially, i is assigned the value 1, and sum is 0; the Boolean
i < 101
However, almost every program that we write using for (;;) can be written using
while(). You will find this out if you try exercise 7 of this Unit. If we want to use a 7
Programming in C /* Program 6.1; File name: unit6-prog1.c */
# i n c l u d e <stdio.h>
i n t main()
{
i n t i, sum = 0;
f o r (i = 1; i < 101; i++)
sum += i;
printf("The sum of the numbers from 1 to %d is %d\n",
i - 1, sum);
r e t u r n (0);
}
Listing 1: Example of a for (;;) loop.
while() instead of for (;;) we can do so by putting the Boolean condition in the round
brackets after while and carry out incrementation within the body of the while loop. We
can put the initialisation statements before the body of the while loop.
In the program in Listing 2 below, we use the for (;;) property that the loop
re-initialisation is performed after each execution of the loop body, which in this case
consists of the null statement: Program in Listing 3 depends on the for (;;) property
that the Boolean is evaluated each time before the loop is entered.
/* Program 6.3; File name: unit6-prog3.c */
# i n c l u d e <stdio.h>
i n t main()
{
i n t i, sum = 0;
f o r (i = 1; i < 101 && (sum += i++););
printf("The sum of the numbers from 1 to %d\
is %d\n", i - 1, sum);
r e t u r n (0);
}
Listing 3: for (;;) loop with empty third_expression.
E1) Consider the following fragment we saw earlier. What will it print?
f o r (i = 3; i = 1;)
printf("%d\n", i);
E2) State the output of the programs in Listing 4, Listing 5, and Listing 6 on the facing
page.
E3) Replace the for (;;) statement in the program in Listing 1 above by:
8 f o r (i = 1, sum = 0; i < 101; i ++)
Control Structures-II
1 /* Program 6.4; File name:unit6-prog4.c */
2 # i n c l u d e <stdio.h>
3 i n t main()
4 {
5 i n t j = 5;
6 f o r (printf("Will this loop be entered?\n"); j < 2; j = 1)
7 printf("Print this, if the loop is entered...");
8 printf("Was the loop entered?\n");
9 r e t u r n (0);
10 }
Listing 4: Exercise 2.
E4) In the program in Listing 3 on page 8, can the && operator be replaced by the ||
operator? Can it be replaced by the comma operator? Are the parentheses around
sum += i ++ required?
E5) The program in Listing 7 on the previous page uses the if - then - else
operator to sum the odd and even integers from 1 through 100 separately. Why are
the parentheses in the if - then - else operator required?
E6) Create, compile and execute programs in Listing 1—Listing 7 above, replacing
for (;;) statements by while() statements.
E7) Rewrite the programs for the Russian Peasant Multiplication Algorithm and the
Collatz problem using for (;;) loops.
Let’s work through the program Listing 8 below to quickly recapitulate what we’ve
learnt about the for (;;) loop.
/* Program 6.8; File name: unit6-prog8.c */
# i n c l u d e <stdio.h>
i n t main()
{
i n t a, b, c;
f o r (b = 0; b++ < 2; c = b++)
a = b;
printf("a = %d, b = %d, c = %d\n", a, b, c);
f o r (a = b = 0, c = 6561; a += b, c /= 3; b++, c /= 3)
b++;
printf("a = %d, b = %d, c = %d\n", a, b, c);
f o r (a = b = c = 0; a = (b++ < 5) && c++ <= 6; a = b++, c++)
printf("a = %d, b = %d, c = %d\n", a, b, c);
f o r (a = 0, b = 2; ++b < 20 - (c = b / 2 > 5 ? -a : ++a);)
b += a;
printf("a = %d, b = %d, c = %d\n", a, b, c);
r e t u r n (0);
}
Listing 8: A quick recap.
In the first for (;;) loop b is initialised to 0. The Boolean b ++ < 2 is true, the
loop statement is executed so that a gets the current value of b, 1. In the
re-initialisation, b is post-incremented to 2, so c gets the value 1. In the second round
of execution, the evaluation of the Boolean post-increments b to 3. The Boolean itself
is now false, so the loop body is not again executed. a, b and c are assigned the
values 1, 3 and 1 respectively.
In the next for (;;) loop, note that a and b are each 0 to begin with, while c is 38 . In
both the testing as well as the re-initialisation phase the value of c is decreased by a
factor of 3; and the loop is exited when c becomes 0. Before the loop is first entered, c
is reduced to 37 ; b is post-incremented to 1 in this iteration of the loop; then in the
re-initialisation, it’s again post-incremented, while c is reduced to 36 . Before the loop
is re-entered, a gets the value 2, and c becomes 35 . The process continues, until b has
the value 8, and a the value 20.
In the third for (;;) loop, the Boolean post-increments b and c each to 1, and is itself
true, so a gets the value 1, and the loop is entered. The printf() prints the current
10 values of a, b and c. Before the Boolean is tested for the next round of execution, b
and c are each post-incremented to 2, while a remains at 1. The Boolean is still true, Control Structures-II
but b and c are post-incremented to 3 and a is reassigned the value 1. In the next
reinitialisation, a gets the value 3, while b and c are each post-incremented to 4. When
the Boolean is tested with these values of b and c, a is reset at 1, while b and c are
post incremented to 5. These values are printed. The re-initialisation of b and c makes
the Boolean false for the next execution of the loop’s body.
It is left as an exercise for you to show that on execution of the last loop a = 3, b = 17
and c = 3.
In the next few examples we shall apply the loop and other control structures to digging
out some interesting properties about prime numbers. How can you tell whether a
number is prime or not? Pretty simple: divide it by 2, 3, ... and if it’s divisible by any,
it’s composite (that’s what mathematicians call numbers that are not prime). Of course,
after you’ve tested it for divisibility by 2, you need use only odd numbers for further
divisors. (Why?) And what’s the largest divisor that you need try before you can
conclude that the given number was a prime? The logic of Program 5.11 teaches us that
if a number is composite, it has at least one divisor no greater than its square root
(why?): so, after having verified that the given number is not even (because if it’s even
it’s certainly composite, unless it’s 2), we need merely use the series of divisors
√
3, 5, 7, · · · (int) n
to determine if it is a prime. The program below does just that:
All prime numbers after 5 must end in one of the four digits 1, 3, 7 and 9. Are such
types of prime number equally common? Let’s find out for the primes less than the
limit of unsigned int. Listing 10 on the next page is a modified version of Listing 9; 11
Programming in C the switch statement traps each kind of prime.
/* Program 6.10; File name:unit6-prog10.c */
# i n c l u d e <stdio.h>
i n t main()
{
unsigned limit, number, factor, endigit1 = 0;
unsigned endigit3 = 0, endigit7 = 0, endigit9 = 0;
printf("Enter number upto which test will run:\n");
do {
printf("Value entered must be greater \
than 7, not greater than 65533:");
scanf("%u", &limit);
}
w h i l e ((limit < 7) || (limit > 65533)); /*end do */
f o r (number = 7; number <= limit; number += 2)
f o r (factor = 3;; factor += 2)
i f (number % factor == 0)
break ;
e l s e i f (factor * factor > number) {
switch (number % 10) {
case 1:
endigit1++;
break ;
case 3:
endigit3++;
break ;
case 7:
endigit7++;
break ;
case 9:
endigit9++;
break ;
}
break ;
}
printf("Primes upto %u ending with 1 = %u\n",
limit, endigit1);
printf("Primes upto %u ending with 3 = %u\n",
limit, endigit3);
printf("Primes upto %u ending with 7 = %u\n",
limit, endigit7);
printf("Primes upto %u ending with 9 = %u\n",
limit, endigit9);
r e t u r n (0);
}
Listing 10: Program to find the number of primes ending 3, 5 and 7.
Here is the output of the program for all primes less than or equal to 65533:
A problem related to that of determining if a given number is prime is that of listing its
12 prime factors. Program 6.11 reads an int value from the keyboard, and factorises it into
primes. Let’s recollect some common sense about decomposing a number into its Control Structures-II
factors before delving into its logic:
A number is a factor of another if it divides the latter without remainder, that is:
if (number % factor == 0)
then search has been successful;
The same factor may be repeated several times within a number. If a factor is found, it
should be tested over and over again to see if it divides the last quotient found:
while (search f o r a factor has been successful)
test i f factor divides last quotient;
The only even prime factor that a number may have can be 2; after casting out all
factors of 2, an odd quotient must be the result. This quotient can have only odd factors,
if any. therefore, beginning with 3, other factors, if any, can only be generated by:
factor += 2 /* if last factor was odd */
With these preliminaries out of the way let’s look at Listing 11:
1 /* Program 6.11; File name: unit6-prog11.c */
2 # i n c l u d e <stdio.h>
3 # i n c l u d e <stdlib.h>
4 i n t main()
5 {
6 i n t number, quotient, factor, no_pfactors = 1;
7 printf("Enter a number, I’ll print it\’s prime factors:");
8 scanf("%d", &number);
9 quotient = number;
10 i f (number == 2) {
11 printf("2 is a prime. The factors are 1, 2.");
12 exit(0);
13 }
14 printf("Prime factors with multiplicity are: 1");
15 /* cast out factors of 2, if any */
16 w h i l e (quotient % 2 == 0) {
17 no_pfactors++;
18 printf(", %d", 2);
19 quotient /= 2;
20 }
21 /* only odd factors can remain, if any */
22 f o r (factor = 3; factor <= quotient; factor += 2)
23 w h i l e (quotient % factor == 0) {
24 no_pfactors++;
25 printf(", %d", factor);
26 quotient /= factor;
27 };
28 i f (no_pfactors == 2)
29 printf("\n The number is prime.");
30 printf("\n");
31 r e t u r n (0);
32 }
Listing 11: Program to find all the prime factors of a number.
Let us now discuss the program in detail. The lines 10 to 13 dispose the case of 2.
Lines 16 to 20 check if the number is divisible by 2. If it is, it removes 2 from the
factors, keeping track of the prime factors removed via the statement no_pfactors++.
Then, lines 22 to 27 remove the odd prime factors if any, again keeping track of number
factors using the statement no_pfactors++. Lines 28 and 29 check if the number of
prime factors is 2 and prints the output ‘The number is a prime.’ if it is.
Try the following exercises now to check your understanding of our discussion. 13
Programming in C
E8) Execute Program 6.11 for various values of input.
E9) There are two shortcomings in the program in Listing 11 on the preceding page.
One is, if the number is prime, the program will divide by all the numbers up to
the number. Another is, if the power of the largest prime factor q dividing the
number is one, the program will check all the factors up to q while it should be
√
enough to check all the factors up to q. For example, if the number is
142 = 2 × 71, the loop will check all the factors up to 71 while it is enough to
check till 9. Fix these two shortcomings.
One of the most important uses of loops in computer programs is in what is known as
iteration, which is the repeated execution of a set of statements aimed at generating
successively “better” values of the quantity that is sought.
For a simple example of the method, let’s solve the quadratic equation:
x2 + 6.3x − 2.7 = 0
or
2.7
x=
(x + 6.3)
Writing the unknown x in terms of itself as we have done may not seem like a very
clever thing to do, but you will agree that the method can become interesting if we write
2.7
xn+1 =
xn + 6.3
Setting a first guess for x0 , we can determine x1 ; having found x1 , we can substitute it
back in the formula to find x2 , and the process may be carried on until the absolute
value of the difference between successive values of x has become as small as we want;
but the process need not always converge; what if the equation had complex roots?
Therefore it’s a good idea to set a limit on the number of iterations to be performed, so
that you don’t get “caught in loop”, going round and round in circles looking for a root
that isn’t there! The program in Listing 12 on the next page below uses the mathematics
library <math.h> function fabs(), which returns the absolute value of a floating
point expression. Control breaks out of the for (;;) loop if the difference between
two successive values found for x is less than TOL, or if the number of iterations has
exceeded 10. The file <math.h> comes with the C compiler, #include it whenever
you need to use any of the functions listed in Table 6.1. (Your compiler may provide a
few more). Here is the output of Program 6.12
Convergence to the desired root may not always quite so rapid as in the last example,
and in some cases you may be misled into believing, after having performed a large
number of iterations that a real root does not exist, when in fact there may be one.
14 x = F(x)
/* Program 6.12; File name:unit6-prog12.c */ Control Structures-II
# i n c l u d e <stdio.h>
# i n c l u d e <math.h>
# d e f i n e TOL .00001
i n t main()
{
i n t i;
double x_zero = 1.0, x_one;
f o r (i = 0;; i++) {
x_one = 2.7 / (x_zero + 6.3);
i f (fabs(x_one - x_zero) <= TOL) {
printf("One root of eqn. is: %10.5f\n", x_one);
printf("convergence achieved after %d \
iterations.\n", i);
break ;
} e l s e i f (i > 9) {
printf("No convergence after 10 iterations.\n");
break ;
}
x_zero = x_one;
}
r e t u r n (0);
}
Listing 12: An iterative method to find the roots of an equation.
will converge: it will, provided the absolute value of the first derivative of F(x) in the
vicinity of the desired root is less than 1.
Here are some exercises. Try them to see if you have understood the use of loops.
E10) Modify the program in Listing 9 on page 11 to verify the claim that the numbers
generated by the formula:
number = n2 − n + 41
E11) Execute the program in Listing 11 on page 13 for various inputs and verify that it
works satisfactorily. Now modify it to print all the factors of a number input to it,
instead of only its prime factors. For example, with 28 as input, it should list the
factor set as 1, 2, 4, 7, and 14. 28 is an example of a perfect number, one in which
the sum of all the factors of the number equals the number itself. A number is
called abundant if the sum of its factors exceeds the number, deficient if this sum
is less than the number. Modify you program to determine if a number is
abundant, perfect or deficient.
E12) Write a C program to determine how many zeros there are at the end of the
number 1000! = 1 × 2 × 3 × · · · × 1000
E13) Verify that nearly 1300 iterations are required to find one root of the equation:
x2 − 6.0x + 8.99999 = 0
that would reserve 40 consecutive storage locations en bloc, each location containing
16 one student’s marks? Indeed it is: such a data structure is called an array, and the
individual values in the consecutive locations which comprise it are called elements of Control Structures-II
the array. The number of elements in an array is called its dimension. Each of the set
of forty marks can be assigned to array elements, one student’s marks per element, so
that there’s a one-one correspondence between array elements and marks.
The marks of the ith student will then be accessible in the int marks [i], where the
index (or subscript) i–an integer–begins at 0 for the first location, and has consecutive
values for consecutive array elements. In C, in contrast to FORTRAN, array indices
begin at 0. Incrementing the index i changes the object of reference from the current
element to the next. By ranging over all values in the interval [0 - 39], the index i can
enable access to any of the 40 elements of the array. So that in a program for the sort,
instead of comparing two independent, unrelated int variables, say mrks_7 against
mrks_8, one could compare the value of marks [i] against marks [j], for any
i and any j.
As you will have realised, the array data structure is a powerful concept, useful
wherever a collection of similar data items are to be stored or manipulated.
The C declaration
i n t marks [40];
reserves 40 consecutive storage locations to hold 40 variables of type int . See the
illustration below. Though the addresses of the array elements are only illustrative, note
that they increase in steps of four bytes, the width of int in all the 32-bit machines.
Program in Listing 13 on the next page illustrates how values are read in into the array
marks []. These marks are then averaged (avg), and the numbers of students who
received marks greater than or equal to the average mark (abv_avg) is printed, as well
as the number of those who obtained below average marks (bel_avg). Finally the
array is sorted to arrange the entries in descending order. The declaratory statement of
Program 6.13
i n t marks [40];
declares marks[]; to be an int array of dimension 40. The for (;;) loop which
follows immediately is used to read in a value into each of these elements. The variable
i begins at 0 (since array subscripts start from 0) and runs up to 39 to cover all the forty
student’s marks. But because we are more used to counting from 1 onwards, the
printf() is written to print student’s numbers from 1 through 40. Note in the
scanf() that the ampersand is prefixed to the array element name just as it is in the
case of scalar variables. In the next unit we shall exploit the connexion between arrays
and pointers to write this expression more elegantly.
The interchange sort is the simplest of all sorting algorithms. It uses two for (;;)
loops. The first picks out, turn by turn, each of the forty elements of marks []. In the 17
Programming in C /* Program 6.13; File name:unit6-prog13.c */
# i n c l u d e <stdio.h>
i n t main()
{
i n t marks[40];
i n t i, j, temp;
f l o a t total = 0.0, avg;
i n t abv_avg = 0, bel_avg = 0;
f o r (i = 0; i < 40; i++) {
printf("Enter marks of student No. %d : ", i + 1);
scanf("%d", &marks[i]);
};
f o r (i = 0; i < 40; i++)
total += marks[i];
avg = total / 40;
f o r (i = 0; i < 40; i++)
marks[i] >= avg ? abv_avg++ : bel_avg++;
f o r (i = 0; i < 40; i++)
f o r (j = i+1; j < 40; j++)
i f (marks[i] >= marks[j])
continue ;
else {
temp = marks[i];
marks[i] = marks[j];
marks[j] = temp;
}
printf("\n\nIn descending order marks are:\n\n");
f o r (i = 0; i < 40; i++)
printf("%d\n", marks[i]);
printf("\n\nThe average mark is %f\n", avg);
printf("\n\n%d students obtained above average\
marks.\n", abv_avg);
printf("%d students obtained below average\
marks.\n", bel_avg);
r e t u r n (0);
}
Listing 13: An example using array.
second loop, the element picked out is compared with each of the elements that follow
it in the array. If a larger element is found, their values are interchanged.
As has been remarked before, a good design principle to bear in mind is to use variable
names such as num_students to hold quantities likely to change in different runs of
the program (instead of constants such as 40 as we have introduced in Program 6.13).
That way, if the number of students is different from 40, as it may be next year, the
change will have to be made in only one place: where the value of the variable is
assigned. Your programs will then also be more readable: your readers will not wonder
where the 40 came from! In Program 6.13 the number 40 appears in 8 different places!
So you might think it good policy to declare:
i n t num_students = 40;
i n t marks [num_students]; /* WRONG */
But the second declaration will not work: the compiler will revolt. On the other hand, a
macro definition of num_students, which is processed before the compiler begins
to allocate storage:
# d e f i n e NUM_STUDENTS 40
followed by
18 i n t marks [NUM_STUDENTS];
will be quite acceptable. Control Structures-II
Consider again our example of generating primes up to hundred million of the form
k2 + 1 given in Listing 14. As these primes are generated, we’d want to store them in
consecutive long words of RAM. So in this case it would be nice to have a set of wider
predeclared storage locations than we had before, an array of type long int, to pile the
primes in:
long primes [1000]; /* assuming there are 1000 such primes */
After the ith prime has been generated and assigned to the array element primes
[i], the next prime generated must naturally be placed in the next longword,
primes[i+1]. So this time we’d want that incrementing the index i should lead us
from the current longword to the next longword. C provides us this convenience: we
don’t have to worry about whether array elements are int s or longs: incrementation or
decrementation of the index directly leads us to the next or the previous element of the
array, no matter that the elements are chars or int s or float s or doubles, or of a user
defined data type.
This is a powerful feature of arrays: incrementing the index in the array marks [],
where each student’s mark occupies an int , enables one to move from one item of type
int to the next. But incrementing the index in the array primes[], where each object
is four bytes wide, still lets you get to the next element! Once the type of an array is
known to C, it lets you navigate to any element of the array, no matter how large (or
small) the size of individual items in it may be, merely by setting the value of the
subscript i as desired. Arrays bestow the power of random access to their elements,
through the subscript i.
/* Program 6.14; File name: unit6-prog14.c */
# d e f i n e TRUE 1
# d e f i n e FALSE 0
# i n c l u d e <stdio.h>
i n t main()
{
long i n t primes[1000], number;
i n t i = 0, factor, k, isprime;
primes[0] = 2L;
f o r (k = 2; k <= 10000; k += 2) {
number = k * k + 1;
isprime = TRUE;
f o r (factor = 3; factor * factor <= number; factor += 2)
i f (number % factor == 0) {
isprime = FALSE;
break ;
}
i f (isprime) {
primes[++i] = number;
printf("k = %d,%ld\n", k, primes[i]);
}
}
r e t u r n (0);
}
Listing 14: A program to list primes of the form k2 + 1 .
Note that in the last example we dimensioned the array primes[] at 1000, fondly
hoping that there’ll be fewer than 1000 primes to store. But if there were more? Well,
they would overflow the area reserved for them, and C wouldn’t whisper a word of
warning, as other languages considerately do, it would let you overwrite potentially
valuable instructions or data, and you would find your program gracelessly terminated!
On the other hand if there happened to be fewer than 1000 primes, (the actual number is 19
Programming in C /* Program 6.15; File name: unit6-prog15.c */
# i n c l u d e <stdio.h>
i n t main()
{
char result[1001];
i n t numerator, denominator, integer_part, next_num, i;
printf("This program evaluates fractions to a\
thousand decimal places.\n");
printf("Enter rational fraction in the form a/b: ");
scanf("%d/%d", &numerator, &denominator);
integer_part = numerator / denominator;
next_num = numerator % denominator;
result[0] = ’ ’;
f o r (i = 1; i < 1001; i++) {
next_num *= 10;
result[i] = next_num / denominator + ’0’;
next_num %= denominator;
}
printf("%d", integer_part);
f o r (i = 0; i < 1001; i++)
putchar(result[i]);
r e t u r n (0);
}
Listing 15: Decimal expansion of fractions.
840, and the largest is 99800101; Check this.) we’d be wasting valuable memory by our
declaration. This example highlights the disadvantage of reserving memory by
fixed-sized arrays: you lose by declaring too little, you lose by declaring too much; C
offers the alternative of dynamic memory allocation. by which a program can request
for memory during execution, as and when required. Memory allocating functions are
described in a later unit.
For an interesting application of char arrays, consider the problem of storing and
manipulating long sequences of digits, such as would arise in the evaluation of a
rational fraction, such as 97/113; naturally such sequences cannot be stored as numbers
with arithmetical significance; the best that we can do is to store them as a char array.
In the program in Listing 15, as each digit is generated, it is converted to char by noting
that in the ASCII code the collating order of the digits is their natural order: ’0’, ’1’,
’2’, etc. (so e.g. ’2’ is obtained by adding 2 to ’0’).
Think now of a ticket-issuing program for seats on a train with a seating capacity for a
thousand passengers. Minimally the program should be able to book seats as long as
they are available, as well as cancel (and make available for other travellers)
reservations previously made. Now the status of a seat can be either “available” (A) or
“booked” (B). It is clear that this time we must set up a correspondence of each seat on
the train by a single byte of RAM. If a seat is available, the contents of the
corresponding byte are set to the char ‘A’; but when that seat is booked, the value in
the byte is change to ‘B’; and changed back to ‘A’ again if the reservation is later
cancelled. Now, if each seat in the train is numbered from 1 upwards, then its status
will be mirrored by the contents of the byte which corresponds to it, an ‘A’ or a ‘B’
(except for the minor detail that the physical seat number will be one greater than the
subscript of the array element corresponding to it). This time we make the declaration:
char seats [1000];
It sets aside 1000 bytes to store the seats of the train. Each element in the array is
accessible by a different value of the subscript. As before, the index begins at 0, and
20 runs to one less than the number of seats in the train. Booking seat #7 would imply an
assignment of the form: Control Structures-II
seats [6] = ‘B’;
E15) Write a program for finding the mean, median and standard deviation of data. You
program should prompt for the number of datum, scan them in one by one and
then print the answer.
In the next section we will discuss arrays and the sizeof operator. We will see that
sizeof operator is useful for dynamic memory allocation and in creating and using data
structures like linked lists and queues.
If you were to count the chars in the string above, you would find only 34, including
the spaces and the full-stop, (‘.’), and you might feel cheated. But the fact is that C
inserts the null character (‘\´) at the end of every string, as an aid to knowing when the
end of the string has been reached. So the zeroth element of this array is ‘T’, and its
element with index value 34 (that is element number 35) is ‘\0’.
To assign this string to char array called example [], the appropriate C statement is:
s t a t i c char example [] = "This string array has 35 elements.";
In most pre-ANSI Cs the keyword static is necessary, but we’ll wait a bit before we
go on to learn its significance. (As we’ve seen, C variables have a type. They are also
distinguished by storage classes–one of which is static –which determine the duration
of time a variable exists, and the blocks, functions or files from which it can be
accessed. Storage classes are discussed in the next section.) For now, note the very
important fact that while assigning the string above to example[] we did not
explicitly specify the dimension of example [], i.e. how many elements to set aside
for it. There’s nothing written inside the square braces: C “dimensions” such arrays
appropriately by itself, relieving you of the drudgery of having to count each character
in the string, and adding an extra to the total for the null character. As remarked above
example [0] has the value ‘T’, example [1] is ‘h’, example [33] is
‘.’ and example [34] is ‘\0’.
ANSI C
Note: Because in the initialisation of arrays the static declaration is not
required by compilers conforming to ANSI C, the declaration
char example [] = ”This string array has 35 elements.”;
works for ANSI C compliant compilers.
We have seen that if a static array is to be initialised to non-zero values, each element of
it must be explicitly define. This can sometimes be a lot of work. Going back to the
example of booking train seats, initially, before any reservations are done the status of
each seat must be “available”. The corresponding array elements must each be set to
‘A’:
s t a t i c char seats [1000]} ={
‘A’, ‘A’, ‘A’,...
/* 1000 values */
...‘A’, ‘A’, ‘A’};
Unfortunately there isn’t a “repeat factor” in C for initialising array elements, as there is
in FORTRAN; but one can escape the chore of writing in a thousand ‘A’s by assigning
the value ‘A’ to seats [i] for all values of i, in a loop.
We have seen that it is not necessary to dimension an array that is initialised. One can
determine the size in bytes of an undimensioned array, and of a variety of other objects,
using the sizeof operator. This is a unary operator that associates from right to left and
which returns the size (as an int –unsigned int in ANSI C–number of bytes), of the
datum type, or variable (which may be an aggregate variable such as a struct (Unit 10)
or an array), or expression, named on its right, for example:
sizeof (int)
It is often important for a program to know the sizes of the fundamental data types on
the machine on which it is being executed. For example, suppose that memory must be
allocated to store a number n of int s, as they are generated during an executing
program; and suppose also that the numbern is not known in advance, but is computed
at run time. Typically n could be the number of goldbachian decompositions of an even
integer, and storage may be required to hold the n pairs of primes adding up to the
integer.
Note
Goldbach conjecture (1742) says that any even number greater than 4 can be
decomposed into the sum of two odd primes, thus:
14 = 3 + 11 = 7 + 7
16 = 3 + 13 = 5 + 11
30 = 7 + 23 = 11 + 19 = 13 + 17 etc.
Though no counter example has been found so far, a proof is still awaited.
Nor do we know precisely how many such decompositions are possible for an
arbitrary even integer. Interestingly enough, numbers divisible by 15 seem to
have significantly more partitions into two primes than other, approximately
equal, numbers.
22 The number of bytes to be set aside will then be 2 × 2 × n on a machine on which the
size of int is two bytes (two bytes per prime), but 2 × 4 × n on one in which the size of Control Structures-II
int is 4 bytes (a VAX or Macintosh). (Of course, most of the modern PCs are 32 bit
machines which have int s of size of 4 bytes. Now a days 64 bit PCs which have an int
size of 64 are becoming more and more common.) Neither of these expressions is
portable. But 2 * sizeof (int) * n can be used without modification on any system.
The program in Listing 16 illustrates the syntax of the sizeof operator. Note carefully
the parentheses around the basic types. Observe that the program yields the expected
sizes of dimensioned well as undimensioned arrays.
/* Program 6.16; File name:unit6-prog16.c */
# d e f i n e STRING "What isthe number of bytes in this string?"
# i n c l u d e <stdio.h>
i n t main()
{
char a = ’b’;
s t a t i c char vowels[] = { ’a’, ’e’, ’i’, ’o’, ’u’ };
s t a t i c char vowel_string[] = "aeiou";
s t a t i c i n t five_primes[] = { 2, 3, 5, 7, 11 };
s t a t i c double tenbignums[10] = { 3816.54729, 1654.72938 };
printf("The size of char on this system \
is: %d\n", s i z e o f ( char ));
printf("Therefore the size of char a is: %d\n", s i z e o f a);
printf("However, the size of\’b\’ is: %d\n", s i z e o f ’b’);
printf("The size of short on this system \
is: %d\n", s i z e o f ( s h o r t ));
printf("The size of int on this system \
is: %d\n", s i z e o f ( i n t ));
printf("The size of long on this system \
is: %d\n", s i z e o f ( long ));
printf("The size of float on this system \
is: %d\n", s i z e o f ( f l o a t ));
printf("The size of double on this system \
is: %d\n", s i z e o f ( double ));
printf("The number of bytes in vowels [] \
is: %d\n", s i z e o f vowels);
printf("The number of bytes in vowel_string [] \
is: %d\n", s i z e o f vowel_string);
printf("The number of bytes in five_primes [] \
is: %d\n", s i z e o f five_primes);
printf("The number of bytes in tenbignums [] \
is: %d\n", s i z e o f tenbignums);
printf("The number of bytes in STRING is: \
%d\n", s i z e o f STRING);
r e t u r n (0);
}
Listing 16: The sizeof operator.
Carefully study the output and try the related exercises given below.
E17) Count carefully the number of bytes in vowel_string []. Why is there a
discrepancy between the number of bytes you counted, and the number in the
tenth line of output?
E18) Modify the above program to prove that the null string is precisely one byte long.
We have already different elementary data types into which we can classify variables in
C. There is another way of classifying variables. This is based on storage classes. We
discuss various storage classes of variables and the scope of the variable(i.e. the portion
of the program where the values of the variable are available) in the next section.
Consider a program to find a few values of n such that the sum of the first n primes is
itself a prime number. One design for the program logically divides it into two parts: in
the first part the program creates an array of say a thousand primes; in the second, it
adds the elements of the array to a variable called sum_n, and tests each value of sum_n
as it is generated for primeness. The program in Listing 17 on the next page is based on
such a logical division. It consists of two blocks, the first of which contains code to
generate the array primes [1000]; while the second tests every other value of sum_n
(because half the values of sum_n are even, anyway, and therefore composite) and lists
those that are prime. (In the next unit, where functions are introduced, we shall learn to
24 encode each separate activity as a function, as a more appropriate division of labour.)
The second block of the program in Listing 17 declares and defines two new variables, Control Structures-II
n and sum_n; these variables, and the variables number, factor, primes [1000]
and i from the previous block, in fact all program variables that we’ve encountered thus
far are examples of automatic variables.
Automatic variables are so called because they are automatically created when control
enters the block (or function) in which they are defined, and destroyed when control
leaves that function. Thus, when a program begins executing at main() its variables
spring to life, their values are known and can be modified and displayed, but pouf!
they’re blown out like a candle so soon as the program finishes execution! Similarly,
when control departs from a function, we shall find in the next unit that the automatic
variables “local” to the function cease to exits. Their values are lost forever. You can no 25
Programming in C further refer to them in the program. And if control reenters their declaring function, no
memory is retained of their former values.
However, variables defined inside a nested block don’t quite die away when control
exits their defining block: they merely become dormant, hibernating until control
should perchance reenter their block: when they are rejuvenated to their former values.
Variables local to a block cannot be referred to outside that block; thus in the program
in Listing 17 on the previous page n and sum_n will not be available beyond the
confines of their defining block; but a variable defined previously, in an enclosing
block, is available in the enclosed block: the elements of primes[] can be added to
sum_n. Variables defined externally to a block are accessible inside it, and inside any
nested blocks. (That is why our #defines have been written outside of main() in the
preceding programs: the tokens are available in all the code that follows.) In fact, you
may declare program variables, if you wish, before main(): then they will be accessible
in all the code that follows it. Changes made to them in one place will be known in
every other place where they are referenced. Such externally defined variables are by
default initialised to zero. See Program 6.19.
But there is one important distinction between a macro token and an externally defined
variable: a variable inside a nested block may be christened with the same name as a
variable outside it: if such be the case, the local value has precedence over the global.
Programmers who use duplicated names for automatic variables in functions or blocks
often use the optional keyword auto in their declarations, thus:
auto i n t i;
to remind their readers (and themselves) that the local value of the variable is to be used
in the present instance. auto has been the default class for all the scalar variables we
have used thus far. Study the program in Listing 18 on the facing page and its output to
become familiar with the properties of automatic variables.
Akin to the auto storage class is the register class: when the register storage
specification ( register , too is a C keyword) is prefixed to a variable’s declaration, thus:
r e g i s t e r i n t i;
then the compiler may try to place the variable i in a machine register, if a register is
available; but it is by no means bound to oblige you. Programmers may want to load
frequently accessed variables such as loop indices inside registers for greater efficiency;
but most compilers take care of such things automatically anyway. Nor can one assign
large or aggregate data types, such as doubles or arrays to registers. Finally, the \&
operator cannot be applied to register variables.
Another c storage declarator is static . The keyword static (which has nothing to do
with electricity!) is used to declare variables which must not lose their storage locations
or their values when control leaves the functions or blocks wherein they are defined. In
C jargon, static variables retain their values between invocations. The initial value
assigned to a static variable must be a constant, or an expression involving constants.
But static variables are by default initialised to 0, in contrast to autos.
Suppose that the variables i and j in the inner block of Program 6.18 had been
declared thus:
s t a t i c i n t i = 1, j;
Then necessarily j would have been set at 0 when control entered the inner block the
first time; and i would have retained its last value, 10, when control had entered the
inner block for the third time.
Classic C requires the static declaration for arrays which must be initialised.
E23) Change the declarations in the inner block of Program 6.18, execute it and verify
that static variables behave as expected.
We now end this Unit by summarising our discussion in the next section.
6.6 SUMMARY
1. The properties of the for (;;) loop, and its application to a variety of situations.
The for (;;) statement contains three expressions, each playing a particular
though not indispensable role: the first expression initialise the loop variable; the
second determines whether the loop be entered; and the third re-initialises the
loop variable after a round of execution to present it again to the second
expression for its scrutiny. These expressions are separated by semicolons, which
must be present whether or not the expressions are. If the second expression is
missing, it is regarded as true.
2. We studied arrays of one dimension, which are used to hold a group of variables
of the same type. Each element of the array is identified by its subscript or
position in the array. Array subscripts begin at 0 for the first element. The array
concept makes possible random access to any element. In Classic C arrays which
must be initialised must be declared as static .
3. The sizeof operator is a unary operator which, associating from right to left,
yields as an unsigned int the number of bytes in its operand. This may be a
basic type, a variable or an array.
4. C has four storage classes, three of which: auto, register and static have been
studied in this Unit. The storage class of a variable determines the longevity and
visibility of the variable. Auto and register variable may take arbitrary values if
they are not initialised: externally defined variables or static variables are by
default initialised to zero. A variable external to a block is accessible inside the
block.
6.7 SOLUTIONS/ANSWERS
E1) The first_expression sets the value of i to 1. Next, the boolean expression
is checked. Recall that the expression i = 1 has value 1, i.e. non-zero value. So,
the loop will be entered and it will print 1, the present value of i. Since the
third_expression is missing, it will evaluate the second expression, whose
value is true as we saw before. The program will print 1 again and again. 29
Programming in C E2) Program 6.4: The variable j is declared and initialised to 5 in line 5. The
printf() in the first_expression in line 6 is executed. Since the loop
condition is not satisfied, for (;;) loop is exited. So, the third_expression
is not executed. The next printf() statement in line 8 is executed.
Will this loop be entered?
Was the loop entered?
Program 6.5: The variable j is declared and initialised to 5 in line 5. Next, the
printf() in line 6 is executed. Then, the loop is entered because the condition j
< 6 is true(j = 5). The printf() statement in lines 7 and 8 in the body of the
for (;;) is executed. Then, the statement j = 7 in third_expression of the
for (;;) loop is executed. Since the Boolean condition in
second_expression is no longer satisfied, loop is exited and the printf()
statement in line 9 is executed. The if -then-else in line 10, returns the value
“No,” since the value of j is 7. The printf() statement in line 11 is printed.
Will this loop be entered?
Print this, if the loop is entered...j = 5
Is the Boolean j 6 still true ?
No,because j is now 7.
Program 6.6: Try this one yourself. The output is given below:
The for (;;) loop is really quite easy to use.
I’ve said this once.
The for (;;) loop is really quite easy to use.
I’ve said this twice.
What I say 2 times must be true!
E4) If the && operator is replaced by the || operator, since the second expression
(sum += i ++) has a positive value, the expression
i < 101 && (sum += i ++) will always have a positive value. So, the loop
will not terminate at all even after i crosses the value 1.
It cannot be replaced by the comma operator either, because in this case the value
of the expression is the value of the second expression (sum += i ++) and
again the loop will not terminate and the printf() after the for (;;) loop will
not be printed.
The parentheses around (sum += i ++) is necessary. Since && has higher
priority than +=, this expression is interpreted as
(i < 101 && sum) =+ i++
Since the LHS of the above expression is not a lvalue and += can be applied only
to an lvalue, the compiler displays an error message.
E7) See Listing 22 for Russian peasant algorithm using for (;;) and Listing 23 for the
program for checking the Collatz conjecture.
/* File name:unit6-ans-ex-7-1.c */
/* Russian peasant multiplication using for()*/
# i n c l u d e <stdio.h>
i n t main()
{
i n t val_1, val_2, lesser, greater, result = 0;
printf("Russian Peasant Multiplication Algorithm\n");
printf("\nEnter multiplier: ");
scanf("%d", &val_1);
printf("\nEnter multiplicand: ");
scanf("%d", &val_2);
greater = (lesser = val_1 < val_2 ? val_1 : val_2)
== val_1 ? val_2 : val_1;
f o r (; lesser; lesser /= 2, greater *= 2) {
result += lesser % 2 ? greater : 0;
}
printf("%d\n", result);
r e t u r n (0);
}
Listing 22: Russian Peasant Algorithm using for (;;)
/* File name:unit6-ans-ex-7-2.c */
/* Collatz problem using for() loop*/
# i n c l u d e <stdio.h>
i n t main()
{
i n t input, cycle_length = 0;
do { 31
Programming in C printf("Enter a positive trial number:");
scanf("%d", &input);
}
w h i l e (input <= 0); /*End of do */
f o r (; input - 1; cycle_length++) {
i f (input % 2 == 1) /* input was odd */
input = input * 3 + 1;
else
input /= 2;
} /*End for */
printf("1 appeared as the terminating digit \
after %d iteration(s)", cycle_length);
r e t u r n (0);
}
Listing 23: Collatz problem using for (;;).
E12) We proceed as follows: We loop through 1 to 1000. In each iteration of the loop,
we find the power of 2 and the power of 5 dividing the loop variable. Since we do
not want the loop variable to be modified, we use a dummy variable. The program
is in Listing 27.
/*Answer to exercise 12, unit6; File name unit6-ans-ex-12.c*/
# i n c l u d e <stdio.h>
i n t main()
{
i n t i, po_two = 0, po_five = 0, dummy;
f o r (i = 1; i <= 1000; i++) {
dummy = i;
w h i l e (dummy % 2 == 0) {
po_two++;
dummy /= 2;
}
w h i l e (dummy % 5 == 0) {
po_five++;
dummy /= 5;
}
}
printf("Power of 2 = %d, Power of 5 = %d\n",
po_two, po_five);
printf("The number of zeros in 10000! is %d",
po_five < po_two ? po_five : po_two);
r e t u r n (0);
}
Listing 27: Solution to exercise 12.
E15) The program is in Listing 30. We read in the data using a for (;;) loop. We then
find the mean and standard deviation using for (;;) loops. Note the use of pow()
for squaring. We sort the data using insertion sort and find the median for the data.
/*Program to find the mean, median and mode.
File name; unit6-ans-ex-15.c*/
# i n c l u d e <stdio.h>
# i n c l u d e <math.h>
i n t main()
{
f l o a t mean, median, temp, sum, data[40];
double sd = 0;
i n t i, j, n;
printf("Enter the number of data elements:\n");
scanf("%d", &n);
f o r (i = 0; i < n; i++) {
printf("Enter data %d\n:", i + 1);
scanf("%f", &data[i]);
}
f o r (i = 0; i < n; i++)
sum += data[i];
mean = sum / n;
f o r (i = 0; i < n; i++) 35
Programming in C sd += pow((data[i] - mean), 2);
sd = sqrt(sd / n);
printf("The mean is %f\n", mean);
printf("The standard deviation is %+6.3f\n", sd);
/* We sort the data using interchange sort.*/
f o r (i = 0; i < n; i++)
f o r (j = i + 1; j < n; j++)
i f (data[i] <= data[j])
continue ;
else {
temp = data[i];
data[i] = data[j];
data[j] = temp;
}
/*We find the median.*/
i f (n % 2 == 0) {
median = (data[(n - 2) / 2] + data[n / 2]) / 2;
} else
median = data[(n - 1) / 2];
printf("Median is %f", median);
r e t u r n (0);
}
Listing 30: Solution to exercise 15.
E16) While a is a character variable, and is stored in 1 byte, ‘b’ is equivalent to its
numerical value and is considered as int .
E17) This is because the vowel_string contains the terminating null as the sixth
character.
E18) Change the definition of STRING to "", compile and run again.
E20) It is not a string because it does not have the terminating null.
E21) Yes, it is a string. Because of the static declaration, hello[5] is assigned the
value \0. The numerical value of hello[5] is zero.
E22) An assignment can be made anywhere in a program, but initialisation is done only
at the time of the declaration of the variable.
36
UNIT 7 POINTERS AND ARRAYS
Structure Page No.
7.1 Introduction 37
Objectives
7.2 Pointer Variables and Pointer Arithmetic 38
7.3 Pointers, Arrays and the Subscript Operator 44
7.4 A Digression on scanf () 48
7.5 Multidimensional Arrays 54
7.6 Summary 58
7.7 Solutions/Answers 60
7.1 INTRODUCTION
This unit introduces “hard core” C: pointers and their manipulation. Pointer are
conceptually quite simple: they’re variables that hold the memory addresses of other
variables.
To concretise concepts, think of an array the elements of which, as you know, are placed
in consecutive locations of storage, at regularly increasing addresses. It is apparent that
the address of any element is immediately computable relative to that of the zeroth: for,
if the address of the zeroth element of the array be x, say, and if each element of it be w
bytes wide, then, relatively to its beginning, the address of the kth element must be
x + k * w, k = 1, 2, ... ,
etc. You can now see that pointers, which are variables to store memory addresses,
should be very closely allied to arrays. In an array, the address of any element is one
“unit” greater than that of the preceding element. Different types of “units”–chars,
float s or doubles, or user-defined types–would of course have differing numbers of
bytes inside them. so going from one array element to the next (by incrementing its
subscript) is equivalent to “incrementing” the pointer to the current element;
incrementation here meaning increasing the value of the pointer by the width, in bytes,
of the object it was pointing at. So pointers can provide an elegant, and often faster,
alternative to array manipulation.
At this point there is one question that might occur to you: why are pointer variables
necessary at all? Many languages such as FORTRAN and COBOL get by quite happily
without them.
Quite apart from providing a faster alternative to array manipulation, pointers are used
in C to dynamically allocate memory, i.e. while a program is executing. This aspect, as
we have hinted before, is important in situations where it is impossible to know in
advance the amount of storage required to store the data be generated, or which will be
input. In Program 6.14 we did not know in advance how much space to set aside for
primes up to hundred million of the type k2 + 1. In a sense we gambled: we chose,
without any evidence, the array dimension to be 1000. Declaring too large an array may
waste memory; but too small an array might cause the program to run haywire or be
aborted. Dynamical allocation gets around this problem by enabling as much memory
to be created as required, when required. If memory can’t be found, the allocating
function can inform the program accordingly.
In Sec. 7.2, we discuss and the operations that can be carried out using pointers. In
Sec. 7.3, we discuss how pointers can be used to manipulate arrays. 37
Programming in C scanf() is extremely versatile function that C uses for keyboard input. In Sec. 7.4 of
this unit, we will explore some of its properties further. And finally, in Sec 7.5, we shall
look at multi-dimensional arrays where more than one subscript is required to locate an
element; matrices and magic squares are common examples of two-dimensional arrays.
Objectives
After studying this unit, you should be able to
• explain the purpose of pointers and use them;
• explain the underlying unity of pointers and arrays;
• use the subscript operator;
• exploit the versatility of scanf();
• use the string I/O functions puts() and gets(); and
• handle multidimensional arrays.
One of the most powerful features of C, and one that makes it quite close to assembly
language, is its ability to refer to the addresses of program variables. In C one can in
fact declare variables to be pointers, that is variables which will hold the memory
addresses of other variables.
The declaration
char *x;
The statements
i n t *x, *y, z = 10;
long *p, q;
f l o a t *s;
double *t;
declare
i) x and y to be pointer variables of type int . That is to say, x and y can hold the
addresses of int like variables, such as z;
ii) p is a pointer to longs. It can store the address of a long int, for instance of q;
iii) s can store the addresses of float variables;
iv) t is a pointer to double.
The rvalues of pointer variables are memory addresses.
How does one extract the memory address of a program variable? And, conversely,
given a memory address, how does one determine the contents at that address? To
answer these question, let’s first recall our picture of memory: a sequence of boxes,
each identified by a unique positive number or address, with the addresses written to the
left of the boxes. A C variable has precisely these two parts associated with it: its lvalue
38 or address, fixed once and for all after compilation and linkage, and its rvalue, the
contents of the box, which may vary as the program executes. Given a variable, C Pointers and Arrays
allows its address to be extracted; conversely, given an address, it is possible to obtain
the contents at that address.
The C “address of” operator is the ampersand, &. It’s a unary operator, that, applied to
a variable (strictly, to an lvalue), yields its memory address:
address of z == &z
&z is the address at which the computer stores the int variable z, and this remains fixed
throughout program execution. Attempting to change it in any way is an error.
As remarked above, the “address of” operator can only be applied to lvalues. Like all
unary operators it has a priority just below the parentheses operator, and groups from
right to left. The “address of” operator is not unfamiliar; the scanf() function has an
argument list of pointers to memory locations.
marks x holds the address of int z. *x gives the contents at the address that’s held in
x; the output from the printf () below will therefore be the rvalue of z, 10:
printf("The contents of the address held in x are:\t %d\n", *x) ;
To clarify these concepts further, let’s assume that the address of the int variable z is
38790, and that the contents at this address are 10. See Fig.I below. In the assignment:
x = &z; 39
Programming in C x gets the value “address of z”, i.e. 38790. So, if the address of x itself is 56780, the
contents at 56780 will be 38790.
address contents of address
z, located at 38790 10
other program variables
"
"
x, located at 56780 38790
Figure I
changes the value of the int object whose address is held in x, namely z, to 20. Here’s
the picture:
Let’s now look at the program in Listing 2 on the next page. c1, c2, c3, ..., c9
are char variables that have been assigned the initial values ‘A’, ‘b’, ‘h’, etc;
pointer is a pointer to char. The first printf() outputs the current values of
c1, c2, c3, ..., c9. Then pointer is assigned the address of c2.
pointer = & c2;
In the next assignment the contents currently resident at pointer are altered to ‘p’:
* pointer = ‘p’;
c2 therefore now has the value ‘p’. One could of course replace the two assignments:
pointer = & c2, * pointer = ‘p’;
but that doesn’t show the pointers at the back of it. Pointers do the same, albeit
indirectly - manipulating values, so to speak, from a distance,the way puppeteer dances
her puppets. In the program in Listing 2 on the facing page, pointer is made to point
successively at c2, c3, ..., c9 and to make new assignments to them.
In the program in Listing 3 on the next page the pointers to int p, q and r are
assigned the address of the int x, y and z, respectively. Then the contents of the
addresses in p and q are set to 5 and 3, which become the respective values of x and y.
Post-incrementation of x and post-decrementation of y change them to 6 and 2; these
are the new values of *p and *q. Therefore the expression:
* r = * p * *q + * p / * q
40 makes the contents at the address held in r to be 15. This is the value that z gets.
/* Program 7.2; File name:unit7-prog2.c */ Pointers and Arrays
# i n c l u d e <stdio.h>
i n t main ()
{
char c1 = ’A’, c2 = ’b’, c3 = ’h’, c4 = ’i’,
c5 = ’s’, c6 = ’h’, c7 = ’e’, c8 = ’k’, c9 = ’ ’;
char *pointer;
printf("%c%c%c%c%c%c%c%c%c",
c1, c2, c3, c4, c5, c6, c7, c8, c9);
pointer = &c2;
*pointer = ’p’;
pointer = &c3;
*pointer = ’a’;
pointer = &c4;
*pointer = ’r’;
pointer = &c5;
*pointer = ’a’;
pointer = &c6;
*pointer = ’j’;
pointer = &c7;
*pointer = ’i’;
pointer = &c8;
*pointer = ’t’;
pointer = &c9;
*pointer = ’a’;
printf("%c%c%c%c%c%c%c%c%c",
c1, c2, c3, c4, c5, c6, c7, c8, c9);
printf("\n");
r e t u r n (0);
}
Listing 2: Pointer operation.
If you think for a moment about how pointer variables are declared, with separate
declarations for each type, a question that may occur to you is the following: addresses,
whether they be of char, int, float or double variables, must each be very like one
another. An address is an address is an address. Can the address of a char be
qualitatively different from that of an int , or of a double from that of a long? Then why
must pointer declarations distinguish between addresses of variables of differing data 41
Programming in C types? In other words, why couldn’t the inventor of C do with just one declaration for
pointer variable, such as for example:
pointer c, x, y, p, s, t;
The fact is that there is a subtle difference between pointers which refer to variables of
different types. By informing the compiler about the type of object being referenced by
the pointer, the pointer can be “scaled” appropriately to point to the next object of the
same type. Recall that in the IBM PC for example, a char value sits in a single byte of
memory, an int in two bytes, a float in four and a double in eight. So when a char
pointer is “dereferenced”, it should give the contents of the single byte whose address it
holds. But when an int pointer is dereference, it must give the contents of two
consecutive bytes (regarded as an integral unit, a word), because an int is two bytes big.
Similarly, when a pointer to float is dereferenced, it must yield the contents of the set
of four bytes (two words) which hold that float variable. Thus, by associating a type
with a pointer variable it helps to dereference the value of the referenced variable
properly in accordance with its size.
There is another reason why it makes sense to distinguish between pointers to different
data types. We have seen that there is a close relationship between pointers and arrays
in C. Stepping from one array element to the next directly translates to incrementing a
pointer which holds the address of the first element of the array, as shown in Figure II
below:
element [i] element [i + 1]
pointer_value pointer_value + 1
Figure II
Now, a string array is a collection of individual bytes. The address of any one byte is
one greater than the address of the preceding. So in going from one character of the
string to the next, you increment the pointer to that byte. But if you have an array of
doubles, then, in going from one element of the array to the next, again incrementing
the pointer that stores the address of that element, you would actually increase the
pointer’s value by eight, because doubles are eight bytes wide! Adding one to a
pointer produces different results if the pointer is a char or int or float or of any other
type. So pointers are not all alike. Neither are they integers in the sense of int .
Incrementing an int increases its value by one. Incrementing a pointer increases its
value by the width of the object it points to. For a 32 bit PC, incrementing a pointer to
int increases its value by 4! See Fig.II., and the output of Program 7.4, which you
should study carefully. Because pointers are not ints, C does not allow many of the
operations on pointers, that are possible with int s: for example, you can’t multiply two
pointers together; nor should you want to: pointers are addresses, much like the house
numbers on a street. what could one possibly want to multiply house numbers for? The
only valid operations on pointers are:
42 (i) assignment of pointers of similar type, using a cast operator if need be;
(ii) adding an integer to, or subtracting an integer from, a pointer; Pointers and Arrays
(iii) subtracting or comparing two pointers that point to elements of the same array; and
(iv) assigning or comparing a pointer to null.
A null pointer points nowhere.
In the program in Listing 4 four pointers are assigned the respective addresses of four
variables of the basic types. The output shows that adding 1 to each pointer increased it
by the width of the type it pointed at.
/* Program 7.4; File name:unit7-prog4.c */
# i n c l u d e <stdio.h>
i n t main ()
{
char *char_pointer, char_variable;
i n t *int_pointer, int_variable;
f l o a t *float_pointer, float_variable;
double *double_pointer, double_variable;
char_pointer = &char_variable;
int_pointer = &int_variable;
float_pointer = &float_variable;
double_pointer = &double_variable;
printf("Address of char_variable: %u, Address + 1: %u\n",
char_pointer, char_pointer + 1);
printf("Address of int_variable: %u, Address + 1: %u\n",
int_pointer, int_pointer + 1);
printf("Address of float_variable: %u, Address + 1: %u\n",
float_pointer, float_pointer + 1);
printf("Address of double_variable: %u, Address + 1: %u\n",
double_pointer, double_pointer + 1);
printf("\nMoral: Adding 1 to a pointer does not necessarily\n");
printf("increase it by 1. Pointers are not ints.\n");
r e t u r n (0);
}
Listing 4: Effect on incrementation of pointers of different types.
Here is the output of Program 7.4 above on my computer. Very probably it will be
different on yours.
E1) Create, compile and execute Program 7.1, 7.2, 7.3 and 7.4. 43
Programming in C E2) Pointers hold the addresses of program variables. In turn, they have addresses too.
Modify Program 7.1 to determine the address at which the pointer x is stored.
In the introduction, we mentioned the close relationship between the pointers and
arrays. In the next section, we will discuss this relationship in detail.
The name primes is itself a pointer! Its value is the address of primes [0].
primes + 1 is the address of the next element of the array, primes + i is address of
its (i + 1)th element. Once can use this property in the following way: suppose you
wish to scan values into the elements of an array int marks [40]. Then the following
for (;;) loop will do:
f o r (i = 0; i < 40; i ++)
scanf("%d", (marks + i));
This statement works because the arguments of scanf() must be pointers, and
marks + i is a pointer to the ith element after the zeroth of marks. Compare this
statement with the corresponding one in Program 6.13:
f o r (i = 0; i < 40; i ++)
scanf("%d", & marks [i])
The program in Listing 5 uses the pointer notation to store the first 1000 primes in an
array, int primes [1000].
/* Program 7.5; File name: unit7-prog5.c */
# d e f i n e TRUE 1
# d e f i n e FALSE 0
# i n c l u d e <stdio.h>
i n t main()
{
i n t primes[1000], number;
i n t i = 0, factor, isprime;
*primes = 2;
f o r (number = 3;; number += 2) {
isprime = TRUE;
f o r (factor = 3; factor * factor <= number;
factor += 2)
i f (number % factor == 0)
isprime = FALSE;
i f (isprime) {
*(primes + ++i) = number;
i f (i == 1000)
break ;
}
}
r e t u r n (0);
}
Listing 5: Computing first 1000 primes.
44
The declaration: Pointers and Arrays
i n t primes [1000];
creates space for 1000 primes beginning at the memory address primes; primes is a
pointer, as is (primes + ++ i). But the alternative declaration:
i n t * primes;
in this program wouldn’t have worked; because it merely defines a pointer to int ; it
does not create storage to pile the 1000 primes in, as they are generated. If you do
attempt to store them heedlessly, using a statement like:
* (primes + ++i) = number;
you would be writing them in places where the compiler does not expect them. In turn,
it will give you unexpected results!
The commonest types of char arrays in C are strings. Strings are very convenient to
manipulate for an especial reason: A C sting is a pointer to itself! In plain words, the
string:
"I am a pointer."
is a pointer: it points to the byte which contains the first character of the string, namely
the letter ‘I’. As in all strings, the last byte here is of course the null character. To prove
that this string is in fact a pointer pointing to the byte containing ‘I’, execute the
following simple program:
/* Program 7.6; File name:unit7-prog6c. */
# i n c l u d e <stdio.h>
i n t main()
{
i n t i = 0;
f o r (; *("I am a pointer." + i) != ’\0’; i++)
putchar(*("I am a pointer." + i));
r e t u r n (0);
}
Listing 6: Program showing relationship between pointers and strings.
The operation:
* ("I am a pointer." + i)
extracts the ith character of the string. For some value of i this will become the null
character. At that time the conditional expression of the for (;;) loop will have become
false, and the loop will be exited.
makes sense. fact now points to the first byte of the string. But a subsequent
assignment to fact:
fact = "We will always be the best.";
will cause it to point to a different address of memory, the address where this new string
begins. It is important for you to realise that the declaration of fact allocates enough
memory to hold not merely the pointer variable fact but also the entire length of the
string. The output of the program in Listing 8 should convince you of this truth. It prints
the address of fact, and the addresses of each byte of the string to which it points.
/* Program 7.8; File name:unit7-prog8.c */
# i n c l u d e <stdio.h>
i n t main ()
{
char * fact = "We will always be the best.";
printf("Bytes allocated for fact: %d\n", s i z e o f fact);
printf("Memory address of fact: %u\n", & fact);
f o r (printf("String cntents:\n");
* fact ! = ’\0’; fact ++)
printf("\t\t%c is at %u\n", * fact, fact);
r e t u r n (0);
}
Listing 8: Example to illustrate memory allocation for strings.
Here the address string_array is fixed when the program has been compiled and
linked. The size of string_array [] is just big enough to hold each byte in the
string, including its invisible terminator. This means that though each element of
string_array [] may be altered, the pointer string_array cannot be assigned
any other value. So the incrementation:
string_array ++; /* {WRONG}
because string_array is a fixed address */
in Listing 5 on page 44 and Listing 6 on the previous page in which fact was declared
as a pointer to a string; but it could be assigned the address of any char.
string_array is a pointer to the array of that name, sitting at a fixed address.
defines a pointer variable x, and allocates memory to store the string, as well as the
pointer to it. x is a variable. It may be given other values as the program executes, the
address of any other char. On the other hand, the definition:
s t a t i c char x[] = "We will always be the best";
Given that strings are pointers, it must now be clear that wherever printf() is
deployed to print a string, its string argument may be replaced by a pointer to the string.
Conversely because strings are arrays, whenever scanf() reads a string, a character
array must be in place to hold the characters entered. Consider Program 7.10:
/* Program 7.10; File name: unit7-prog10.c */
# i n c l u d e <stdio.h>
i n t main()
{
char tree_1[20], tree_2[20], tree_3[20], tree_4[20];
printf("Name your favourite tree:");
scanf("%s", tree_1);
printf("Name another tree that you like:");
scanf("%s", tree_2);
printf("Namea third:");
scanf("%s", tree_3);
printf("Name a fourth tree that you appreciate:");
scanf("%s", tree_4);
printf("These are your favourite trees: %s, %s, %s and %s\n",
tree_1, tree_2, tree_3, tree_4);
printf("You\’re a good person!!!\n");
r e t u r n (0);
}
E4) Give the output of the programs in Listing 10 and Listing 11 on the next page.
When scanf() (with the % format conversion character) examines the stream of
characters arriving from the input, it ignores all leading white space character–blanks,
tabs, newlines or formfeeds; so in Program 7.10 if you responded with:
/* Program 7.11; File name: unit7-prog11.c */
# i n c l u d e <stdio.h> 47
Programming in C i n t main()
{
i n t i, *j;
s t a t i c i n t primes[] = {
2, 3, 5, 7, 11
};
j = primes;
f o r (i = 0; i < s i z e o f primes / s i z e o f ( i n t ); i++)
printf("%d\n", *j++);
f o r (j = &primes[4]; j >= &primes[0];)
printf("%d\n", -- *j --);
r e t u r n (0);
}
Listing 10: Program for exercise 4 on the previous page
/* Program 7.12; File name:unit7-prog12.c */
# i n c l u d e <stdio.h>
i n t main()
{
i n t i, *j;
s t a t i c i n t primes[] = {
2, 3, 5, 7, 11
};
j = primes + 4;
f o r (; j >= primes + 1; j--)
printf("%d\n", j[0]);
f o r (j = primes, i = 0; i < 4;)
printf("%d\n", j[++i]);
j = primes + 4;
f o r (i = 0; i <= 4; i++)
printf("%d\n", j[-i]);
r e t u r n (0);
}
Listing 11: Program for exercise 4 on the previous page
Executing Program 7.10 teaches us that leading white space characters in the input to
scanf() with %s are ignored, while embedded white space characters cause it to stop
reading further input; but there are two noteworthy cases, which we’ll examine in turn.
The first arises when %c is used as the format conversion specifier. Then every single
48 character–including any white space characters–in the input is read. Consider:
scanf("%d%c%d", &x, &y, &z); Pointers and Arrays
the x would get the value 5, y the value ‘m’ and z the value 6 respectively. But if
instead the input was:
<SPACE>7<SPACE>m<SPACE>8
the value assigned to x would be 7, y would be assigned the char value ‘ ’(SPACE), and
z would retain its former value of 6. The reason for this behaviour is as follows: in
reading an int with %d, scanf() ignores all leading white space charcter; when it
receives the input 7 it assigns it to x. With the %c format conversion charcter, each
keystroke is significant. IF <SPACE> is typed next, that is the value that y gets. Then
m is received; but the corresponding format specifier (%d) expects an int value. Now
scanf() stops reading as soon as it encounters a character that is not valid for the type
being read. The attempt to assign the value m to z fails. This is illustrated in Program
7.13, appended below, and a sample output:
/* Program 7.13; File name: unit-7prog13.c */
# i n c l u d e <stdio.h>
i n t main()
{
i n t x, z;
char y;
printf("Type in the values 5m6 for x, y and z without spaces:");
scanf("%d%c%d", &x, &y, &z);
printf("x = %d, y = %c, z = %d\n", x, y, z);
printf("Type in the values 7<SPACE>M<SPACE>8 for x, y and z:");
scanf("%d%c%d", &x, &y, &z);
r e t u r n (0);
}
For clarity, we have underlined the user inputs. Programs 7.9 and 7.10 would have
behaved somewhat differently if the <SPACE> character had been included inside the
control string, separating %c from the second %d:
scanf("%d%c %d", &x, &y, &z);
Then an arbitrary number of blanks are permitted in the input to separate the value
entered for y from that for z. This is illustrated by the two calls to scanf() in Program
7.14. Note the difference in their control strings. Then note the difference in the
assignments they made (or could not make) to x, y and z.
/* Program 7.14; File name: unit7-prog14.c */
# i n c l u d e <stdio.h>
i n t main ()
{
i n t x, z; 49
Programming in C char y;
printf("Enter values for x, y and z, using \
spaces for separators\n");
printf("Remeber to enter a non-white space \
character for y: ");
scanf("%d %c %d", &x, &y, &z);
printf("x = %d, y = %c, z = %d", x, y, z);
printf("\nEnter values for x, y and z, but this time\n");
printf("no spaces between values for x and y: ");
scanf("%d %c %d", &x, &y, &z);
printf("x = %d, y = %c, z = %d", x, y, z);
r e t u r n (0);
}
The last line of the output above shows that scanf() terminates reading the input
stream as soon as it encounters therein a character invalid for the type listed (it was
given a ‘b’ when it expected a numeric value for z, and did not read the 56 meant for
it). For decimal int s valid characters are an optional sign, followed by the digits 0 - 9;
for octals the digits 0 - 7; and for hex the digits 0 - 9, a - f or A - F. A filed width (e.g.
%4d, %6c, %3o, %2x, etc.) may be specified: reading stops as soon as the specified
number of charcters has been read in, or, as we noted in the last example, a charcter is
entered that does not match the type being read. When a field width is supplied with
%c, it is assumed that the corresponding argument is a pointer to a character array, at
least as big as the field width specified. See Program 7.20.
The second case in which white spaces in the input to a scanf() can be made
significant is through a bracketed string read, explained below.
We learnt from Program 7.10 that the %s specification reads a sequence of characters of
which the first must be a non-white space character; and reading terminates as soon as a
white space character is met with. The corresponding argument must be a character
array, at least one byte bigger than the sequence of non-white space characters entered,
to hold the terminating null character: scanf() automatically appends it when it’s done
reading. If a field width is specified, then no more characters than that width can be
read in. Given this propensity of scanf() to stop reading a string as soon as it
encounters a white space character inside it, how can string such as:
‘‘I am a string.’’
be stored via scanf()? For this purpose one uses a bracketed string read, % [...]
where the square brackets [] are used to enclose all characters which are permissible in
the input. If any character other than those listed within the brackets occurs in the input
string, further reading is terminated. Reciprocally, one may specify within the brackets
50 those characters which, if found in the input, will cause further reading of the string to
be terminated. Such input terminators must be preceded by the caret character (∧) . For Pointers and Arrays
example, if the tilde symbol (∼) is used to end a string, the following scanf() will do:
char string [80];
scanf(‘‘% [^~]’’, string);
Then, if the input for string consists of embedded spaces, no matter: they will all be
accepted by scanf(); and reading will stop when a tilde (∼) is entered. This is
illustrated in Program 7.15 and its output:
/* Program 7.15; File name:unit7-prog15 */
# i n c l u d e <stdio.h>
i n t main ()
{
char string [80];
printf("Enter a string, terminate with a tilde (~)...");
scanf("%[^~]", string);
printf("%s", string);
r e t u r n (0);
}
Though the terminating tilde is not itself included as an element of the string read, it
stays in the “read buffer”–the area of memory designated to store the input–and will be
picked up by th next call to scanf(), even though you may not want it! This is
illustrated by Program 7.16 and its output. There, the second call to scanf() is
executed automatically, and the “dangling” tilde is assigned to the char x. The call to
putchar() prints the value of x.
/* Program 7.16; File name: unit7-pog16.c */
# i n c l u d e <stdio.h>
i n t main()
{
char string[80];
char x;
printf("Enter a string, terminate with a tilde (~)...");
scanf("%[^~]", string);
scanf("%c", &x); /* The leftover from the last scanf()
is read here. This scanf () doesn’t
wait for you to enter another char. */
printf("%s", string);
putchar(x);
r e t u r n (0);
}
Compile and execute Program 7.16. You will find that the machine executes the second
scanf() without so much as a by-your-leave! Such dangling characters must be
“assorted away” by a subsequent call to scanf() with %c, or to getchar() else they
may interfere in unexpected ways with subsequent calls to scanf() or getchar(). 51
Programming in C If there are non-format characters within the control string of a scanf(), they must be
matched in the input. For example, consider the scanf() we’d used in Program 5.16:
scanf("%d-%d-%d", &Day, &Month, &Year);
The hyphens in the control string are non-format character. Their presence implied that
three int s were to read in, with their values to be separated by a single intervening
hyphen. Using any other separators would have led to error. To execute Program 7.17
below correctly, you would have to admit that you like C!
/* Program 7.17; File name: unit7-prog17.c */
# i n c l u d e <stdio.h>
i n t main ()
{
i n t x, y;
printf("Type \"I like C!, then values for x and y...");
i f (scanf("I like C! %d %d", &x, &y) == 2)
printf("Thank you! The sum of x and y \
is: %d\n", x + y);
else
printf("I will not add those numbers for you!\n");
r e t u r n (0);
}
Program 7.17 uses another interesting property of scanf(), one we’ve seen before
(refer to Program 5.16): that it returns the number of items that it has successfully read.
If this does not equal the number expected by the program, an error message can be
output.
An asterisk(*) in the control string of a scanf(), placed between the % sign and the
format conversion character which follows it, causes the corrresponding input to be
ignored. Consider the scanf():
scanf(‘‘%f %*f %f’’, &x, &z);
The valid characters for an input of float s are an optionally signed sequence of decimal
digits, followed by an optional decimal point and another sequence of decimal digits,
followed if need be by the letter e and E and an exponent, which may be signed. The
modifier l with the d or f specifiers is used to read in and assign longs and doubles
respectively. The modifier h with the d specifier reads short ints. These rules are
summarised below:
Again, the argument of puts() may be the name of a string array. Consider the
following program:
/* Program 7.18; File name: unit7-prog18.c */
# i n c l u d e <stdio.h>
i n t main ()
{
s t a t i c char string_1 [] = "I hope that you enjoy C.";
s t a t i c char string_2 [] = "It\’s great fun!";
puts (string_1);
puts (string_2);
r e t u r n (0);
}
Neither of the arrays string_1[] or string_2[] ends with a newline; yet the output
of the program is printed in two lines, with the cursor stationed on the third, as you can
see by executing the program. The puts function replaces the terminating null
character of its string argument by a newline.
gets() gets the characters that you type in at the keyboard, until <CR> is pressed.
This bunch of characters is stored as a string array, for which memory must be allocated
before gets() is invoked. In Program 7.19 below that array of 55 chars is
get_string[]. The <CR> indicating end of keyboard input is not stored in the array;
gets() replaces it by a null character, marking the end of the input string.
/* Program 7.19; File name: unit7-prog19.c */
# i n c l u d e <stdio.h>
i n t main ()
{
char get_string [55];
puts ("Tell me how you are");
puts ("in 55 keystrokes or less.");
puts ("That\’s inclusive of the <CR>,");
puts ("which we expect you to type presently!");
printf("%s", "Begin here: ");
gets (get_string);
puts ("Thanks for that detailed description.");
r e t u r n (0);
}
gets() does more than merely getting a string from the keyboard. If it was able to read
the input string successfully, it returns the address of the array to which the string is
assigned; if not, or if no characters are read, it returns the null pointer, which by
convention has the value 0, and points nowhere.
E5) Describe what happens if you input more than 55 characters to Program 7.19.
E6) Give the output of the following program, in which a field width is specified with
%c:
/* Program 7.20; File name:unit7-prog20.c */
# i n c l u d e <stdio.h>
i n t main ()
{
char array [7]; i n t i; 53
Programming in C printf("Type in the letters ABCDEFG: ");
scanf("%7c", array);
f o r (i = s i z e o f array - 1;i >= 0; i --)
putchar (array [i]);
r e t u r n (0);
}
We close this section here. We will discuss multidimensional arrays in the next section.
The declaration for matrix [][] reserves 120 bytes of storage; the first subscript,
running over the rows, would vary from 0 through 4; the second from 0 through 5. The
element of a two-dimensional array are stored in “row major” form. This means that
they are so arranged in memory that, for minimal access time, it is the rightmost
subscript that should be made to vary the faster. To process every element of the array
matrix [][], you would need two for (;;) loops. The outer loop would run over the
rows, the second over the elements within a row:
f o r (i = 0; i < 5; i ++)
f o r (j = 0; j < 6; j ++)
matrix [i][j] = 0.0;
Note that this arrangement of loops makes the rightmost subscript vary the fastest. You
may find the phrase “minimal access time” a bit strange, if you pause to consider that
the CPU can retrieve any RAM location in the same amount of times; but if the array
was loaded from disk (where also it is stored in row major form), it is possible that not
all if it may have been “read in” in a given disk access. For large arrays such may
indeed be the case in “virtual memory” machines, where disk storage may be thought
of as an extension of RAM. If an array element is required that is not currently in RAM,
a (very much slower) disk access would be needed to fetch it. If this happens
frequently, with the CPU being forced to pause while the disk is searched, (as, for
example, in processing a large two-dimensional array in column major order) the
machine will spend more time in performing disk I/O than in computing. It is then said
to be “thrashing”, very much like a novice swimmer splashing the water vigorously, yet
unable to make any forward progress. Designers of operating systems are interested in
these considerations.
The declaration:
s t a t i c i n t fifty_seven [5][5] =
{
19, 8, 11, 25, 7, 12, 1, 4, 18, 0,
16, 5, 8, 22, 4, 21, 10, 13, 27, 9,
14, 3, 6, 20, 2
};
This notation teaches us that a two dimensional array is actually a one dimensional
array, each element of which is itself an array. Realise, therefore, that such an array
can be thought of as an array of pointers. This idea is explored more fully below.
Program 7.21 reads in two matrices conformable to multiplication, and computes their
product. In this program mat_1, a 4 × 5 matrix multiplies mat_2, a 5 × 6 matrix, and
stores the result in mat_3, a 4 × 6 matrix. The [i][j]th element of mat_3 is obtained
by multiplying the ith row of mat_1 into the jth column of mat_2, each element of the
row to the corresponding element of the column, and then summing the several partial
products.
/* Program 7.21; File name: unit7-prog21.c */
# i n c l u d e <stdio.h>
i n t main()
{
i n t i, j, k, mat_1[4][5], mat_2[5][6], mat_3[4][6];
printf("This program multiplies two matrixes\n");
printf("of dimensions 4 x 5 and 5 x 6.\n\n");
printf("Enter 20 elements for matrix # 1: ");
f o r (i = 0; i < 4; i++)
f o r (j = 0; j < 5; j++)
scanf("%d", &mat_1[i][j]);
printf("\nEnter 30 elements for matrix # 2: ");
f o r (i = 0; i < 5; i++)
f o r (j = 0; j < 6; j++)
scanf("%d", &mat_2[i][j]);
f o r (i = 0; i < 4; i++) {
f o r (j = 0; j < 6; j++) {
mat_3[i][j] = 0;
f o r (k = 0; k < 5; k++)
mat_3[i][j] = mat_3[i][j] +
mat_1[i][k] * mat_2[k][j];
}
}
printf("\nThe given matrixes are:\n\n");
f o r (i = 0; i < 4; i++) {
f o r (j = 0; j < 5; j++)
printf("%3d", mat_1[i][j]);
printf("\n");
}
printf("\nand:\n\n");
f o r (i = 0; i < 5; i++) {
f o r (j = 0; j < 6; j++)
printf("%3d", mat_2[i][j]);
printf("\n");
}
printf("\nTheir product is:\n\n");
f o r (i = 0; i < 4; i++) {
f o r (j = 0; j < 6; j++)
printf("%3d", mat_3[i][j]);
printf("\n");
} 55
Programming in C r e t u r n (0);
}
A string is a one-dimensional array: it has elements along a line. Any element can be
specified by a single subscript, which gives its position in the string. If you had a bunch
of strings, of equal length, each in a separate line, you would need two subscripts to
specify a particular character: the first would refer to the row, e.g. the zeroth, first,
second, ... string in which the char lies, the second to its position in that string, the
column number.
This is a two-dimensional
array of chars. It has 25
columns and 3 small rows.
The picture we have so far presented is for string arrays unnecessarily restrictive,
because strings by their nature will be of arbitrary length. However, since strings are
pointers to char, it is possible to think of a bunch of several strings as a one
dimensional array of pointers. Consider the declaration:
s t a t i c char * pntr_aray [] =
{
"This is the first string.",
"This is the second string, a little longer.",
"This is the third string.",
"And this is the fourth and final string."
};
First, what does this declaration mean? Is pntr_aray an array of pointers to char, or
is it a pointer to an array of chars? This may appear to be a confusing question but is
easily answered if you reason as follows: since the subscript operator, [], has a higher
precedence than the dereferencing operator. * (pntr_aray []) is a
char, pntr_aray [] is a pointer to char, and pntr_aray is an array of pointers to
char. Each element of it is a pointer to a string, because a string is a pointer to itself.
The declaration of pntr_aray[] reflects this:
s t a t i c char * pntr_aray [] =
{
pntr_1, pntr_2, pntr_3, pntr_4
};
where pntr_1, pntr_2, etc, are pointers to the first, second, etc. strings respectively.
pntr_array [] is an array of four pointers. The program Listing 12 on the facing
page prints these strings: The outer loop of Program 7.22 runs over the four pointers;
the inner loop applies the subscript operator with j as subscript to extract the jth
element of the ith string. (Realise that as a loop condition the expression:
pntr_aray [i][j]
is equivalent to:
pntr_array [i][j] ! = ’\0’
Program 7.20 creates a ragged array, in which only as much space is allocated to store
the strings as is required for each separately (And for their pointers). If we had used a
two dimensional array to store these strings, the row dimension would have been at
least as great as to accommodate the largest string. That would have wasted valuable
memory when short strings had to be stored.
}
Listing 12: Array of strings as two dimensional arrays
char ** pntr_aray;
Now suppose that x, y , z, w, are a number of a string pointers, i.e. assume that
they are defined thus:
char * x = ‘‘I am string.’’;
char * y = ‘‘I am one, too.’’;
char * z = ‘‘I am a third string.’’;
char * w = ‘‘And I am the fourth and final here.’’;
Using the versatile subscript operator, another way of saying the same thing as:
pntr_aray [0] = x;
Or, equally,
pntr_aray [0] = "I am a string.";
pntr_aray [1] = "I am one, too.";
pntr_aray [2] = "I am a third string.";
pntr_aray [3] = "And I am the fourth and final here.";
E8) Rewrite Program 7.21 to multiply two matrices using the pointer notation.
E9) Write a C program to test if a string entered from the keyboard is a palindrome,
that is, that it reads the same backwards and forwards, e.g.
“Able was I ere I saw Elba.”
E10) Give the output of the following program:
/* Program 7.23; File name: unit7-prog23.c */
# i n c l u d e <stdio.h>
i n t main ()
{
char ** pntr_aray;
pntr_aray [0] ="I am a string.";
pntr_aray [1] ="I am one, too.";
pntr_aray [2] ="I am a third string.";
pntr_aray [3] ="And I am the fourth and final here.";
putchar (pntr_aray [0][0]);
putchar (pntr_aray [0][1]);
putchar (pntr_aray [2][11]);
putchar (pntr_aray [1][5]);
putchar (pntr_aray [0][1]);
putchar (pntr_aray [1][6]);
putchar (pntr_aray [1][5]);
putchar (pntr_aray [0][8]);
putchar (pntr_aray [0][1]);
putchar (pntr_aray [3][13]);
putchar (pntr_aray [0][10]);
putchar (pntr_aray [0][11]);
putchar (pntr_aray [2][11]);
putchar (pntr_aray [0][1]);
putchar (pntr_aray [2][7]);
putchar (pntr_aray [2][8]);
putchar (pntr_aray [2][9]);
putchar (pntr_aray [0][7]);
putchar (pntr_aray [0][1]);
putchar (pntr_aray [0][8]);
putchar (pntr_aray [3][14]);
putchar (pntr_aray [3][15]);
putchar (pntr_aray [2][18]);
putchar (pntr_aray [2][8]);
putchar (pntr_aray [0][13]);
r e t u r n (0);
}
We have now completed the unit. You may like go through the summary given in the
next section to recaptitulate what we have studied so far.
7.6 SUMMARY
In this unit we have studied the following:
1. Pointers are int like variables that hold the addresses of other variables.
2. The rules of pointer arithmetic: The incrementation of pointers is different from
the incrementation of other variables. For example, depending on whether it
58 points to int , char, on incrementation it points to 8 bytes or one byte ahead.
3. Three operators are used in working with pointers, the “address of ” of operator Pointers and Arrays
&, the “contents of” operator * and the subscript operator [].
4. The name of a array is a pointer to its zeroth element. So, all the rules of pointer
arithmetic apply to array name.
5. The format conversion rules followed by scanf().
%d Reads int s in decimal notation. The
argument must be a pointer to int .
%i Reads an integer with a prefix or suffix.
Prefixes allowed are a sign( + or −) or o to
denote an octal constant, or one of ox, oX
to denote a hex constant. Suffixes allowed
are u or U to denote an unsigned int and l
or L to denote a long int.
%ld Reads long ints; the argument should be a
pointer to long.
%hd Reads short ints; argument must be a
pointer to unsigned.
%u Reads unsigned ints; argument must a
pointer to unsigned.
%o Reads numbers octal notation.
%lo Reads long octals.
%ho Reads short octals.
%x, %X Reads hex itss.
%lx, %lX Reads long hex int s.
%hx, %hX Reads short hex int s.
%e, %E, %f, %f and %G is for reading numbers expressed in
floating point notation.
%le, %lE, %lf, %lf and %lG is for reading double. The arguments
should be pointers to double.
%Le, %LE, %Lf, %Lf and %LG The arguemts must be pointers to
long double.
%c Reads single characters. White space
characters are significant. Therefore, to
read the next non-space character, use %ls.
Argument corresponding to %c is a pointer
to a char.
%s Used for reading a character string.
Characters are read until a white space
character is encountered. The
corresponding argument is a an array of
chars, which must be big enough to hold
the characters entered, plus the null
character which scanf() appends.
%p Reads a pointer. The argument expected is
a pointer to void.
%n records the number ofcharacters read so far
in this call to scanf(); while no
characters are read from the input stream
against \%n, its corresponding data
argument is a pointer to int . 59
Programming in C %[...] Reads a string until a character that is not
found in the square braces is encountered.
If the first character in the braces is ^, then
the subsequent characters listed there serve
as terminators. The input stream is read
until one of the characters listed after the
caret is encountered.
%% Matches a single percent sign % in the input
stream, without any assignment being
made.
7.7 SOLUTIONS/ANSWERS
E5) The program will crash. This is because there is no memory available and the
program will try to write to a memory location not allocated to the program.
E8) The program is given in Listing 13. Note the second * in the statements
*(*(mat_3+i)+j)= 0;
for accessing the value mat_3[i][j].
/* Program 7.21; File name: unit7-prog21.c */
# i n c l u d e <stdio.h>
i n t main()
{
i n t i, j, k, mat_1[4][5], mat_2[5][6], mat_3[4][6];
printf("This program multiplies two matrixes\n");
printf("of dimensions 4 x 5 and 5 x 6.\n\n");
printf("Enter 20 elements for matrix # 1: ");
f o r (i = 0; i < 4; i++)
f o r (j = 0; j < 5; j++)
scanf("%d", (*(mat_1+i)+j));
printf("\nEnter 30 elements for matrix # 2: ");
f o r (i = 0; i < 5; i++)
f o r (j = 0; j < 6; j++)
scanf("%d", (*(mat_2+i)+j));
f o r (i = 0; i < 4; i++) {
f o r (j = 0; j < 6; j++) {
*(*(mat_3+i)+j)= 0;
f o r (k = 0; k < 5; k++)
*(*(mat_3+i)+j) = *(*(mat_3+i)+j) +
*(*(mat_1+i)+k) * *(*(mat_2+ k)+j);
}
}
printf("\nThe given matrixes are:\n\n");
f o r (i = 0; i < 4; i++) {
f o r (j = 0; j < 5; j++)
printf("%3d", mat_1[i][j]);
printf("\n");
}
60 printf("\nand:\n\n");
f o r (i = 0; i < 5; i++) { Pointers and Arrays
f o r (j = 0; j < 6; j++)
printf("%3d", mat_2[i][j]);
printf("\n");
}
printf("\nTheir product is:\n\n");
f o r (i = 0; i < 4; i++) {
f o r (j = 0; j < 6; j++)
printf("%3d", mat_3[i][j]);
printf("\n");
}
r e t u r n (0);
}
Listing 13: Solution to exercise 8.
61
UNIT 8 FUNCTIONS
Structure Page No.
8.1 Introduction 63
Objectives
8.2 Function Prototypes and Declarations 64
8.3 Functions and Scope 69
8.4 Pointers as Function Arguments 78
8.5 Unidimensional Arrays as Function Arguments: String Functions 80
8.6 Multi-Dimensional Arrays as Function Arguments 84
8.7 Summary 86
8.8 Solutions/Answers 86
8.1 INTRODUCTION
Large programs for large “real world” applications require quite different techniques
for their creation than the ones we have so far studied. As programs grow in size and
complexity, it becomes more and more difficult to keep track of the logic; so programs
are divided into separate modules called functions, with each function reflecting a
well-understood activity, and of manageable proportions.
The idea is to divide and conquer. For example, imagine a program to manage your
personal savings bank account, a program that would monitor every activity that is
recorded in your pass book on a computer. Now there are several things to be
considered while designing such a program: for one, you may deposit cash. The
program should return your updated balance; or you may want to withdraw some, in
which case the program should report if the amount to be withdrawn exceeds your
current balance; if not, it should return the new balance.
Second, when a cheque is deposited, the program should ask for its details and store
them for future reference: issued by whom, in which connection, of what amount,
cheque number, name of bank, whether the cheque was local or outstation (because a
collection charge must be deducted from such cheques) the date of issue, etc.; and
similarly when you write a cheque for someone, with the program updating the balance
at the end of each transaction. When you enter the date of a cheque, or of a
withdrawal/deposit, the program must verify if that was a valid date.
And finally, the program should compute the interest that fell to your share, at the end
of the current accounting period, at the rate specified by the bank, and add it to your
balance. As you can see, there are several “black box” activities, none hard to program
in themselves, but unrelated to each other. One single program encompassing every
activity would be quite cumbersome; its logic would quickly become obscure. What
can be more natural than to divide the program into functions, a separate function
accounting for each task?
But there is another reason why functions are a very useful invention, and why all
programming languages have them. Quite often the same activity must be repeated
several times in an executing program. Instead of repeating the lines of code
representing the activity at various places in the program, it would make sense to give
that piece of code a name, and cause it to be executed by invoking it by its name
whenever required.
Sometimes a function written for one problem can be used in different problems; it can
serve as a “building block” to help build other programs. A function to validate a date 63
Programming in C would be equally useful in a program for railway reservations and in one to handle
savings accounts. C carries this “pick up and reuse” design philosophy to a greater
extent than other languages. Even input and output are implemented via functions
available through “standard libraries” provided with your compiler.
The user-defined functions which build a C program may exist in different source files,
as long as a function is not split over more than one file; each of these files may be
separately compiled and linked, and loaded together at execution time.
ANSI C and Classic C differ considerably in the way in which they declare and define
functions, though the classic C style is accepted by current ANSI C compilers. We shall
point the differences as we go along, but it is better to learn the ANSI C way of working
with functions, even if you have a Classic C compiler for one very good reason: C++
follows the ANSI C standard.
Objectives
After studying this unit, you should be able to
• declare function prototypes;
C functions have names that follow the same rules as variable names, with an extra
feature: parentheses after the name. The parentheses may contain a list of arguments,
separated by commas. A function may have no arguments: on the other hand, it may
have several. On invocation, the calling program “injects” the values of these
arguments into the function. The arguments passed to the function are processed by it
in accordance with its instruction. If required, the function may send back the result of
its computation to the calling program, when it returns control to it. That result has of
course a type, given by the return type of the function. A function that has the void
64 return type can return no value to the calling program, and an error message will result
if you attempt to ask for its value. If the return type of a function is omitted in its Functions
declaration, it is assumed to be int .
For our first example of functions, let’s turn to the program in Listing 1 on page 67. It
determines the area of a triangle whose sides are given. It consists of three functions,
other than main(), viz. begin(), form_triangle() and area_triangle().
These functions are declared at the beginning of the program, through functions
prototypes:
void begin ( void ):
i n t form_triangle ( double a, double b, double c);
double area_triangle ( double x, double y, double z);
The prototype declarations tell the compiler three important things about the functions:
1) to expect to encounter them further down the program;
2) the return types of the function, e.g. void, int or double;
3) the number and kind of arguments the function is to handle: none whatever
(represented by the keyword void in the parentheses, as in begin()), or of specified
type as in the declarations of form_triangle() and area_triangle().
The parameter names e.g. double b, double c, etc., listed in a function’s prototype
need not be the same as in the function’s declaration. Indeed, names in a prototype are
optional: one could write:
double area_triangle ( double , double , double );
but you will agree that this is a poor way to say what you really mean: that a triangle
has three sides whose lengths are doubles.
The first of our functions, begin(), has no arguments. Indeed, a function need have
none. begin() tells a user what the program does. Here it is:
void begin( void )
{
printf("This program determines whether three numbers \n ");
printf("that you enter can form the three sides of a \n ");
printf("triangle.If they can, the program prints \n ");
printf("its area...Enter values for the three sides:\n");
}
Upon its invocation by main(), which is done simply by naming it in main(), thus:
begin ();
control passes to begin(), where its printfs() are executed. Control re-enters main()
automatically after the terminating brace of begin() is encountered, to resume from its
next statement.
Apart from declaring its return type, the declarator contains a comma separated list of
the function’s formal parameters, if it has any. If not, the function’s parentheses include
the keyword void.
Note that the function’s declarator is not terminated by a semicolon; it must agree with
the function’s prototype, the declaration which is made before main(), in all
particulars: return type, name, and parameters and their types. Parameter names in a
function’s prototype and its declarator may be different. 65
Programming in C The function’s declarator is followed by its body, enclosed in braces. Further variables
may be declared inside the body of the function, if need be: their scope is limited to the
periphery of the function. We will see an example of this shortly, in the function
area_triangle().
Via its optional return statement a function can return at most a single value to the
calling program, which is converted to the return type of the function if it happens to
be different.
In the program in Listing 1 on the facing page, after control returns to main() from
begin(), it invokes this function in the following way:
i f (form_triangle (side_1, side_2, side_3))
The call is executed from within the if (); the arguments side_1, side_2 and
side_3 are “injected” into the three parameters of form_triangle(): side_1 is
passed to alpha, side_2 to beta and side_3 to gamma. The instructions within the
function itself are written in terms of its formal parameters; but the values used in the
function’s computation are the values that came from main(). You might be wondering
about the difference in meaning between arguments and parameters: the function call
from main() uses arguments; the function definition itself is in terms of parameters.
side_1, side_2 and side_3 are arguments; alpha, beta and gamma are
parameters. It should be obvious that the types of formal parameters in a function’s
definition should match the type of the arguments used in the call, each to each.
The keyword return provides a way for the function to return a value to main(): the
syntax of its usage is
r e t u r n (expression);
where expression is converted to the return type of the function, if need be. Parentheses
around the expression returned are optional: they make for clarity. It is not required that
an expression follow return: the statement:
return;
is syntactically correct. In this case control returns to the caller but without an
accompanying value. On the other hand, a function need have no return statement:
control exits from the function; back whence it came, when its right brace is met. This
happens in begin(), which has no return. Realise therefore that a function can return
at most one value to the calling program via its return statement.
area_triangle() returns this value to main(), via its local variable, area. The
program is given in Listing 1.
/* Program 8.1; File name:unit8-prog1.c */
# i n c l u d e <stdio.h>
# i n c l u d e <math.h>
void begin( void ); /* function prototypes */
i n t form_triangle( double a, double b, double c);
double area_triangle( double x, double y, double z);
i n t main( void )
{
double side_1, side_2, side_3;
begin();
scanf("%lf,%lf,%lf", &side_1, &side_2, &side_3);
i f (form_triangle(side_1, side_2, side_3))
printf("Area of triangle is: %f\n",
area_triangle(side_1, side_2, side_3));
else
printf("The lengths that you entered cannot form \
a triangle.\n");
r e t u r n (0);
}
void begin( void )
{
printf("This program determines whether three numbers\n");
printf("that you enter can form the three sides of a\n");
printf("triangle. If they can, the program prints\n");
printf("its area...Enter values for the three sides:");
}
i n t form_triangle( double alpha, double beta, double gamma)
{
i f ((alpha + beta > gamma) &&
(beta + gamma > alpha) &&
(gamma + alpha > beta))
r e t u r n 1;
else 67
Programming in C r e t u r n 0;
}
double area_triangle( double a, double b, double c)
{
double s, area;
s = (a + b + c) / 2;
area = sqrt(s * (s - a) * (s - b) * (s - c));
r e t u r n (area);
}
Listing 1: Program for finding the area of a triangle.
It is in the subject of functions that there are major changes between Classic C and
ANSI C. Classic C did without prototyping: the function form_triangle() would
simply have been declared before main() thus:
i n t form_triangle ();
No parameter list was admissible; moreover, since the default return type of functions is
int , even the declaration was not required And was as a consequence often skipped,
making for opaque code. On the other hand, since area_triangle() returns a double
result, the declaration:
double area_triangle ();
was in Classic C, and remains in ANSI C, a requirement; but with the following
permitted relaxation of this rule.
Now it is not necessary to place functions after main() in your program, as we have
done above. Functions may certainly be written before main(); indeed, if the function
definition appears before the first call to it, in Classic C even its (prototype) declaration
is not required!
One probably does not gain very much by this saving where there are large programs
involving several functions, because care must then be taken to arrange the functions in
such a way that each function appears in the code before it is called by any other
function. It is easier to declare all functions prototypes before main(), and list the
functions themselves after main(), a practice to which we shall invariably adhere.
The second major difference between Classic C and ANSI C is in the function
definition itself. In the former, the parameters listed in the function’s header are not
typed; instead, their types are declared in a separate statement preceding the function’s
body, thus:
double area_triangle (a, b, c)
double a, b, c; /* A Classic C parameter type declaration */
{
double s, area;
s = (a + b + c) / 2;
area = sqrt (s * (s - a) * (s - b) * (s - c));
r e t u r n (area);
}
If you have a Classic C compiler, remember to declare all functions before main(),
without a parenthesised parameter list. The function’s header should contain only its
formal parameters, separated by commas; their types must be declared in a separate
statement sandwiched between the function’s header and its body.
E3) Write a C function to return the number of primes less than a positive integer
passed to it.
What happens to the value of variables used in functions after the control leaves the
function? Which are the variables that a function can access apart from the ones
declared in the functions? These are some of the questions that we will discuss in the
next section.
When one function is invoked by another, the arguments used by the “caller” are
transmitted, as we have seen, into the formal parameters of the “called” function.
However, the called function is sent only copies of the caller’s variables; it can use
them in its computation, but it cannot change them, except locally, within its own
boundary. Such changes are not reflected in the values of the caller’s variables. The
called function has no power to change them. The reason for this is that the function’s
variables are created in the invocation at addresses different from those of the caller’s
variables. The called function has no access to the caller’s address space, which
remains insulated from it. This is illustrated in Program 8.2 in which the function
float inflation() is sent the prices of milk, potatoes and bread of a few years ago. It
inflates them, prints them, and returns control back to main(). However, the
corresponding variables in main remain unaltered.
/* Program 8.2; File name unit8-prog2.c */
# i n c l u d e <stdio.h>
void inflation( f l o a t m, f l o a t p, f l o a t b);
i n t main()
{
f l o a t milk_price = 2.50, potatoes_prices = 0.50,
bread_price = 2.85;
printf("Unit prices of milk, potatoes \
and bread in 1984\n");
printf("were %.2f, %.2f and %.2f respectively,\n\n",
milk_price, potatoes_prices, bread_price);
printf("Let\’s see what inflation did to them...\n\n");
printf("Transferring these \
prices to inflation ()...\n\n");
inflation(milk_price, potatoes_prices, bread_price);
printf("\nNow control is back in main ()...\n\n");
printf("Print those prices once more...\n\n");
printf("milk: %.2f, potatoes: %.2f and bread: %.2f\n\n",
milk_price, potatoes_prices, bread_price);
printf("What??? They haven’t changed at all \
in main ()!!!\n");
r e t u r n (0);
}
void inflation( f l o a t milk, f l o a t potatoes, f l o a t bread)
{
f l o a t percent_rise, annual_price_ratio;
i n t i, num_years;
printf("We welcome you to inflation ()...\n\n");
printf("enter a %% rise for inflation (range 0...100):");
scanf("%f", &percent_rise);
printf("\nEnter number of years inflation occured: ");
scanf("%d", &num_years);
annual_price_ratio = (100 + percent_rise) / 100; 69
Programming in C f o r (i = 1; i <= num_years; i++) {
milk *= annual_price_ratio;
potatoes *= annual_price_ratio;
bread *= annual_price_ratio;
}
printf("\nAs a result of %.0f%% inflation, \
prices %d years later are...\n",percent_rise, num_years);
printf("milk: %.2f, potatoes: %.2f and bread: %.2f\n\n",
milk,
potatoes, bread);
printf("Thank you for bearing with inflation()...\n\n");
printf("Now return to main ()...\n\n");
}
The automatic variables declared within a function’s body are local to it. They are
created when control passes to the function, are in existence until control remains
within the function, and are annihilated when it departs therefrom. In Program 8.3 the
function f1() is called twice in succession by main(). In its first invocation f1()’s
automatic variable x is assigned a value which it returns to main(). But conditions are
such that in its second invocation x cannot be assigned a value; one might suppose then
that the last value of x, the value it had when the function was first invoked, may still be
available: not so. That variable was destroyed and its value was lost when control
returned to main(). The second call to f1() returns an unpredictable value for x. In
fact, by a little modification of Program 8.3 you can easily show that each call to f1()
creates x at a different address.
/* Program 8.3; File name:unit8-prog3.c */
# i n c l u d e <stdio.h>
i n t f1( i n t a, i n t b);
i n t main()
{
i n t alpha, beta, gamma;
alpha = 10;
70 beta = 20;
gamma = f1(alpha, beta); Functions
printf("alpha = %d, beta =%d, gamma = %d\n",
alpha, beta, gamma);
alpha = 20;
beta = 30;
gamma = f1(alpha, beta);
printf("alpha = %d, beta = %d, gamma = %d\n",
alpha, beta, gamma);
r e t u r n (0);
}
i n t f1( i n t p, i n t q)
{
i n t x;
i f (p * q < 300)
x = p * q;
r e t u r n (x);
r e t u r n (0);
}
it does retain its value between invocations. It doesn’t melt away into the wilderness
when the function is not in use. And is initialised to the value (), unless a different
initial value is specified. That initialisation is made in the first call to the function.
Subsequent calls to it ignore the initialisation but use the last value that the static
variable was left with, as you will verify by executing Program 8.4. But before
proceeding to do so it may be useful to review Section 6.5 and Programs 6.17 - 6.19
once more.
/* Program 8.4; File name:unit8-prog4.c */
# i n c l u d e <stdio.h>
i n t f1( i n t a, i n t b);
i n t main()
{
i n t alpha, beta, gamma;
alpha = 10;
f o r (beta = alpha; beta > alpha / 3; beta--){
printf("Proceeding to f1 ()...\n");
gamma = f1(alpha, beta);
printf("alpha = %d, beta = %d, gamma =%d\n", alpha,
beta, gamma);
}
r e t u r n (0);
}
i n t f1( i n t p, i n t q)
{
s t a t i c i n t x, count = 0; /* initialisation unnecessary,
count is by default 0 */
printf("...Entered f1 ()...\n");
printf("Current number of calls to function: %d\n", ++count);
printf("On entry x was found to have the value: %d\n", x);
i f (p * q > 70)
x = p * q;
printf("...Returning to main ()...\n"); 71
Programming in C r e t u r n (x);
r e t u r n (0);
}
Using an argument list is not the only way by which the variables of one function can
be passed to another. We recall from Unit 6 (Program 6.18 and 6.19) that variables
declared externally to main() are accessible inside it; they are also accessible inside
any functions that follow main(). Program 8.5 has a date validation function with
which it communicates via the global variables Day, Month and Year.
For another example of the use of global variables, let’s look at Program 8.6, which
solves a problem asked in Unit 6: determine how many zeros there are at the end of the
number 1000!. Now, each zero in that expansion signifies the presence of a factor of 2
and a factor of 5 in the product for 1000! If we had a function that could determine how
many times 2 and 5 were factors in each of the multiplicands, and update two variables
name two_occurs and five_occurs each time 2 or 5 was found as a factor, then we
would be in business. The smaller of the values two_occurs and five_occurs
would be the number of zeros in 1000!
The function is2or5factor() updates the two global variables int two_occurs and
five_occurs declared before main(). They are not passed to the function by main(),
they’re already visible in it. The ternary operator in main() yields the number of zeros
in 1000!
Convenient as they are, the indiscriminate use of global variables is not recommended.
For one thing, functions have the power to change them. That makes those variables
vulnerable; suppose you’re using a function “off the shelf”, so to speak, that as a side
effect, changes the values of its variables: lo, that change, desirable or not, will be
transmitted to your program. A function should be given capabilities on an “as needed”
basis; if a date value sent down by main() must be protected, global variables will not
be suitable. But with a list of formal parameters for communication, a function
becomes a module that may be “plugged in” to any “driver” program with a matching
set of arguments. There will be no fear that values of the driver program’s variables
may be changed by the function.
One important feature of global variables that should be kept in mind is that if a
function has a local variable of the same name as a global variable, the local variable
has precedence over the global. Analogously, a variable declared within a block has
precedence over an external variable of the same name. Recall the behaviour of 73
Programming in C Programs 6.16 - 6.19. The local is “in scope” when control enters the function. The
external definition is ignored.
The extern keyword tells the C compiler and linker that the variable thus qualified is
actually the one of the same name defined externally: this is the one that will be used in
the function. New storage is not created for it, because it refers to a variable that
already exists. This brings us to the important distinction between the terms definition
and declaration: a definition creates a variable and allocates storage. A declaration
describes a type but does not reserve space. Precisely for this reason an initial value
cannot be specified with an extern declaration: initialisation means that a value is
assigned at the time that storage is allocated; because storage is not allocated by the
extern declaration, it cannot include an initialisation. Therefore, inside a function the
following statement is unacceptable:
e x t e r n i n t x = 5; /* WRONG */
Now you might wonder: of what use can extern declarations be, when external
variables are by default available within the functions that follow them? True: but what
if a variable is required inside a function, yet is itself defined after the function? An
externally defined variable is in the scope only of the functions that follow it, not of
those that precede it. Then, you will admit, the extern declaration can serve a useful
purpose. It tells the C system: “Look, this extern variable is one that you haven’t seen
before; but don’t panic, have patience, it’s defined externally to a function that’s defined
lower down, or possible in another file, and will soon coming within your ken!” Again,
C functions can be stored in separate files. If a variable is externally declared in one
file, and is needed inside the functions stored in the other files, the extern declaration in
the functions will make it accessible to them.
There is one last scope rule relating to externally defined static variables that must be
stated here for completeness (we hope that you will christen your variables carefully,
and may never need to use it): suppose there is a situation in a multi-file program in
which it is required to use a variable within a file, and within it only; suppose also that a
variable of the same name is defined globally in another file. To restrict the scope of a
variable to the functions contained in a single file, and to prevent name conflicts across
other files, the variable is defined static externally in the file to which it must be
confined. For example, suppose that your program and the functions that it uses are
stored in three files, file_1, file_2 and file_3:
Then int x defined in file_1 is available in main(), inf func_1() because it is external
to it in the same file, and in func_3(), because of the extern qualifier before its
definition in file_2. int y, defined in file_2 at the top of func_2() is available also in
func_1(); it’s redefined inside func_3(), where the automatic variable has precedence
over its externally defined namesake; the external static int variables x and y in file_3
74 are accessible only inside that file; they have nothing to do with other variables of the
same names in file_1 and file_2. Precisely how multi-file programs are compiled and Functions
linked is a compiler-operating system feature that differs from compiler to compiler.
We can only refer you to your system’s manuals.
To understand these rules let’s look at Program 8.7, which consists of three functions
other than main (), func_1(), func_2 and func_3() each of type
int . global_var is a global variable and therefore accessible by every function
except func_3, wherein it’s redefined:
i n t global_var = 2;
when main() calls func_1() it sends in the values 5, 7 and 5 to L. M and N respectively.
The function changes global_var, M and a: the change in M is not reflected back in
main(). M is only a local variable, a country cousin, so to speak. And the function
returns a value to main()’s int variable, c.
E4) Predict the outputs of Program 8.4 and 8.6. Verify your results be creating,
compiling and executing these programs.
E6) What does the following program, spread over three files, print? The contents of
file_1 are:
# i n c l u d e <stdio.h>
i n t x = 5;
void func_1( void ); 77
Programming in C void func_2( void );
void func_3( void );
void func_4( void );
void func_5( void );
i n t main( void )
{
printf("Inside main ()\n");
func_3();
printf("After calling func_3 () x is: %d\n", x);
x = 10;
func_1();
func_5();
}
e x t e r n i n t y;
void func_1( void )
{
printf("Inside func_1 ()\n");
printf("x in func_1 () is: %d\n", x);
printf("y in func_1 () is: %d\n", y);
func_2();
printf("After a call to func_2 () y is: %d\n", y);
func_4();
}
The contents of file_2 are
i n t y = 10;
void func_2( void )
{
y = 5;
}
e x t e r n i n t x;
void func_3( void )
{
i n t y = 15;
x = 20;
}
The contents of file_3 are
s t a t i c i n t x, y;
void func_4( void )
{
x = -5;
y = -10;
}
void func_5( void )
{
printf("In func_5 (), x = %d, y = %d\n", x, y);
}
(Note: See practical guide for instructions to compile programs spread over
multiple files.)
We saw that a function cannot modify the variables of the calling function and it can
return atmost one value. How to overcome these shortcomings? This is the topic of
discussion in the next section.
One apparent drawback of functions is that they can return at most one value back to
78 the calling program. We say apparent with some justification, because the reality is that
a function may be made to send back as many different values as may be required Functions
(though not via the return statement, whose load carrying capacity is limited to but one
expression). A C function has in fact all the power of a FORTRAN function and
subroutine combined. One mechanism by which a function may circumvent the
limitation imposed by the return statement lies in C’s use of global variables. This
corresponds to Fortran’s COMMON statement. As we have seen in program 8.8, a
function can assign the results of its computations to global variables available to
main() to use: two_occurs and five_occurs. Another mechanism for sending back
more than one value is provided by pointers.
We learnt from Program 8.2 that a function cannot change the values of local variables
written in the argument list in its invocation; the function is sent only “copies” of the
caller’s variables. The calling program’s variables remain unaffected by whatever the
function does to its copies. This mode of invoking a function is called “call by value”.
However, when the arguments listed in a function call are pointers, it is the
memory addresses of the caller’s variables that are transmitted to the function.
The function can change the contents stored at those addresses, and these changes
will be visible in main(). A function with pointer variables for parameters can return
several values to main(), in the objects that are pointed to by its parameters. This type
of function invocation is called “call by reference.” Consider Program 8.10 and its
output:
/* Program 8.10; File name: unit8-prog10.c */
# i n c l u d e <stdio.h>
void cant_change( i n t a, i n t b, i n t c);
void can_change( i n t *l, i n t *m, i n t *n);
i n t main()
{
i n t x = 4, y = 5, z = 6;
printf("In main (): x = %d, y = %d, z = %d.\n", x, y, z);
cant_change(x, y, z);
printf("Back in main (): x = %d, y = %d, z = %d.\n", x, y, z);
printf("cant_change () couldn\’t change x, y and z.\n");
can_change(&x, &y, &z);
printf("Back in main (): x = %d, y = %d, z = %d.\n", x, y, z);
printf("can_change () could change x, y and z.\n");
r e t u r n (0);
}
void cant_change( i n t A, i n t B, i n t C)
{
printf("Entered cant_change ()...\n");
A = A * A;
B = A + B * B;
C = B + C * C;
printf("Parameters reset in cant_change ().\n");
printf("A = %d, B = %d, C = %d.\n", A, B, C);
}
void can_change( i n t *A, i n t *B, i n t *C)
{
printf("Entered can_change ()...\n");
*A = *A * *A;
*B = *A + *B * *B;
*C = *B + *C * *C;
printf("Parameters reset in can_change ().\n");
printf("*A = %d, *B = %d, *C = %d.\n", *A, *B, *C);
}
Program 8.11 in Listing 2 on the facing page determines the length, slope, and
y-intercept of a line whose end-points are given. The function line()’s argument list
includes the co-ordinates of the end-points, as well as pointers to the variables that must
be computed. Note that the function has a return type of int ; it returns a value to
main(), the value of error_trap, which indicates the success or failure of the
computation. The function performs its error checking before attempting to execute an
operation that could be illegal, e.g. divide by zero: this would occur if the co-ordinates
of the end-points were the same, or the line were parallel to they y-axis.
E7) Write a C program that uses pointers and a function to compute both the area and
the perimeter of a triangle whose sides are given.
E8) Write a C program that uses pointers and a single function to compute the
co-ordinates of the ex-centre of a triangle, given the co-ordinates of its vertices’s.
Your program should return the radius of the ex-circle.
E9) Repeat exercise 7 for the in-circle of the triangle. (In exercises 7, 8 and 9 be sure
to check that you are not dealing with non-existent triangles!)
E10) Write a C program that uses pointers and a single function to compute the two
roots of a quadratic equation, given its coefficients. Your function must be able to
cope with the four cases: non-existent roots (a = 0); real and different roots; equal
roots; and complex roots.
Since the name of an array is a pointer, when an array is passed to a function, the
function receives a pointer, the address of its zeroth element. This saves storage and
80 enhances program efficiency: the need for copying the entire array into the function’s
address space in obviated. But the mechanism also imparts to the function the power to Functions
change the contents of the array. Let’s look at Program 8.12, which uses a function
called uppercase() to convert the lowercase elements of a string array to uppercase.
/* Program 8.11; File name:unit8-prog11.c */
i n t line( f l o a t x1, f l o a t y1, f l o a t x2, f l o a t y2,
f l o a t *length, f l o a t *slope, f l o a t *y_intercept);
# i n c l u d e <stdio.h>
# i n c l u d e <math.h>
i n t main( void )
{
f l o a t x1, y1, x2, y2, length, slope, y_intercept;
i n t error_trap;
printf("Enter co-ordinates in the form x, y for");
printf("point where line begins: ");
scanf("%f, %f", &x1, &y1);
printf("Co-ordinates at end of line: ");
scanf("%f, %f", &x2, &y2);
error_trap = line(x1, y1, x2, y2, &length, &slope,
&y_intercept);
i f (error_trap == -2)
printf("No line possible between (%f, %f) and (%f, %f)\n",
x1, y1, x2, y2);
e l s e i f (error_trap == -1)
printf("Line has infinite slope. Length is: %f\n", length);
else
printf("Length = %f, Slope = %f, Y_intercept = %f:",
length, slope, y_intercept);
}
i n t line( f l o a t x1, f l o a t y1, f l o a t x2, f l o a t y2,
f l o a t *length, f l o a t *slope, f l o a t *y_intercept)
{
i f ((x1 == x2) && (y1 == y2))
r e t u r n -2;
e l s e i f (x1 == x2) {
*length = y2 > y1 ? (y2 - y1) : (y1 - y2);
r e t u r n -1;
}
else{
*slope = (y2 - y1) / (x2 - x1);
*length = sqrt((x2 - x1) * (x2 - x1)
+ (y2 - y1) * (y2 - y1));
*y_intercept = y1 - *slope * x1;
r e t u r n 0;
}
}
Listing 2: Functions to find the slope a line joining 2 points and the distance between the
points.
i n t main( void )
{
char string_holder[100];
puts("Type a few good words, and press return.");
gets(string_holder);
uppercase(string_holder);
puts(string_holder);
r e t u r n (0);
}
void uppercase( char *ptr) 81
Programming in C {
i n t i;
f o r (i = 0; *ptr; ptr++)
i f ((ptr[i] >= ’a’) && (ptr[i] <= ’z’))
ptr[i] -= 32;
r e t u r n (0);
}
Listing 3: A function to change all the characters in a string to upper case.
these declarations create pointers: arrays are not copied into functions.
Given the fact that C strings are terminated by the null character, it is a simple matter to
write functions to manipulate strings in various ways. The function int howlong(),
which has a string pointer for its formal parameter, returns the number of non-null
characters in the string:
i n t howlong ( char string[])
{
i n t i = 0;
w h i l e (*string ++)
++ i;
r e t u r n i;
}
Here, string is a pointer that holds the address of a string array sent down from the
calling function. string is incremented until the null byte in the array is sensed.
Correspondingly a counter i is also incremented. On exit from the loop i holds the
number of visible chars in the string array. For a second example consider the function
joinem() below, which has two string pointers for parameters: it appends the second
string to the first:
void joinem ( char * string_1, char * string_2)
{
f o r (; *string_1; string_1 ++)
;
f o r (; *string_1 ++ = *string_2 ++;)
;
}
The first for (;;) loop seeks the end of the first string, from where the second loop
begins to copy in the chars of the second string. Again, the string’s null characters tell
the loops where to stop. Needless to say, the calling function must store the first string
in an array big enough to store the second with it. Will the second for (;;) loop of
joinem() copy in the null byte of string_2?
The function void copyover has two string pointers for its parameters. It copies the
second string over the first:
void copyover ( char * strin_1, char * string_2)
{
f o r (; *string_1 ++ = *string_2 ++;)
}
These functions are available in the standard library where they are known by other
names: howlong() is called strlen(), joinem () is called strcat() (from string
concatenation), and copyover() is called strcpy(). ANSI C compliant compilers
come with a header <string.h> which contains declarations for these and several
82 other functions for string manipulation. Older compilers do not have <string.h>; the
Functions
Table 1: Some <string.h> functions
char *strcpy (s1, s2) copies s2 to s1, returns s1
char *strncpy (s1, s2, n) copies at most n characters of s2 to s1, returns s1
char *strcat (s1, s2) appends s2 to s1, returns s1
char *strncat (s1, s2, n) appends at most n characters of s2 to s1, returns s1
int strcmp (s1, s2) Compares s1 to s2, returns a negative, zero or positive
value depending on s1 being less than, equal to or
greater than s2
int strncmp (s1, s2, n) compares at most n characters of s1 to s2, returns a
negative, zero or positive value depending on s1 being
less than, equal to or greater than s2
char *strchr (s1, c) returns pointer to first occurrence of char c in s1, or
NULL of c is not in s1
char *strrchr(s1, c) returns pointer to last occurrence of char c in s1, or
NULL of c is not in s1
char *strbrk (s1, s2) returns pointer to first occurrence in string s1 of any
character in string s2, NULL if none is present
char *strstr (s1, s2) returns to first occurrence of string s2 in s1,
NULL if not present
size_t strlen (s) returns length of s as an unsigned int (size_t is the type
of result produced by the sizeof operator, and unsigned int.)
functions are directly available. Some of the functions defined in <string.h> are
listed in Table. 1 (your compiler may provide several others): Though string arrays are
by far the most commonly used arrays in C programs, it is frequently required to
process lists of numbers such as student’s marks, a savings account’s transactions, etc.
Program 8.13 illustrates how an array of int s is passed to a function; the function
returns the maximum and minimum elements of the array and the mean of all its
elements. The interesting thing about Program 8.13 is that it can work for arrays of any
size; on doesn’t have to keep track of the number of elements. The sizeof operator
applied to an array yields the number of bytes in it. Dividing this number of
sizeof (int) gives the number of elements in the array:
num_el = s i z e o f array / s i z e o f ( i n t );
/* Program 8.13; File name:unit8-prog13.c */
# i n c l u d e <stdio.h>
f l o a t minmax( i n t arrayofints [], i n t num_el, i n t *max_el, i n t *min_el);
i n t main()
{
i n t *min, *max;
f l o a t avg;
s t a t i c i n t array[24] = {
-1, -2, -3, -4, -5, -6,
7, 8, 9, 10, 11, 12,
-13, -14, -15, -16, -17, -18,
19, 20, 21, 22, 23, 24,
};
avg = minmax (array, s i z e o f array / s i z e o f ( i n t ), min, max);
printf("Max is %d, Min is %d, Avg of elements is %.2f\n",
* max, * min, avg);
r e t u r n (0);
}
f l o a t minmax ( i n t intarray [], i n t num_elements, i n t *m, i n t *n)
{
i n t i;
f l o a t total = 0;
*m = *n = *intarray; 83
Programming in C f o r (i = 0; i < num_elements; i ++){
i f (* (intarray + i) > *n)
*n = * (intarray + i);
i f (* (intarray + i) < *m)
*m = * (intarray + i);
total += * (intarray + i);
}
r e t u r n (total/i);
}
E11) Write a function that returns the day which fell on a given date passed to it. Make
sure that your function returns a message “Invalid date”, if an illegal date is
passed to it.
E12) Write a function that accepts two one-dimensional arrays A [] and B [] and returns
their scalar product:
A[0] * B[0] + A[1] * B[1] + A[2] * B[2] +...+ A[n] * B[n]
E13) Write a function that accepts two one-dimensional arrays A[] and B[] and returns
their sum in a third array C[].
E14) Write a function that accepts a string and determines the number of times a given
character appears in it. For example, it the string is “Malayalam”, and the
character is ’a’, your program should print the value 4.
E15) Modify Program 8.13 so that it returns to main() the subscripts of the maximum
and minimum elements in it.
E16) A set of marks in the range 0 - 100 is given. Write a C function to determine how
many of the scores fell in the ranges 0 - 10, 11 - 20, 21 - 30, ..., 91 - 100.
E17) You are given a list of numbers, some of which may occur more than once in the
list. Write a C function to remove duplicate numbers from the list.
E18) Write a C functions to sort an array of numbers using the interchange sort. Your
function should be able to handle any “reasonably sized” array of int s.
The first form of the declaration does not specify the two-dimensional nature of the
array; the second does not go far enough in describing the shape of the array: is
numbers a 2 x 12 matrix, or 3 x 8, or 4 x 6 or 6 x 4, etc. Since there are in fact 6
elements in each row, the number of rows is immaterial; the number of columns fixes
84 the dimensions of the array. Either of the following header declarations will do:
addrows ( i n t numbers [4] [6]) Functions
{}
/*or*/
addrows ( i n t numbers [] [6])
{}
/*or*/
addrows ( i n t (*numbers) [6])
{}
The first declaration is explicit; the second tells that numbers [] [6] has 6 columns;
the third informs the compiler that the parameter is a pointer to a row of six integers.
The parentheses around *numbers are necessary since the subscript operator has a
higher priority than the indirection operator. Without these parentheses the declaration
would have implied an array of six pointers to integers. Generally the first subscript
limit of a multidimensional array need not be specified in its declaration in functions;
all other dimensions must be specified. The third form of the declaration should give
you a hint that numbers [1][0], and numbers [3] points to numbers [3][0].
For C a two-dimensional array is an array of one-dimensional arrays!
Program 8.14 illustrates how the two-dimensional array matrix [4][6] is passed to
a function addrows(), which returns the sums of elements in each of its four rows.
/* Program 8.14 */
# i n c l u d e <stdio.h>
void addrows( i n t mat[][6], i n t s[]);
i n t main()
{
s t a t i c i n t sums[4];
s t a t i c i n t matrix[4][6] = {
{1, 2, 3, 4, 5, 6},
{7, 8, 9, 10, 11, 12},
{13, 14, 15, 16, 17, 18},
{19, 20, 21, 22, 23, 24}
};
addrows(matrix, sums);
printf("Row sums are: %d, %d, %d and %d\n",
sums[0], sums[1], sums[2], sums[3]);
r e t u r n (0);
}
void addrows( i n t mat[][6], i n t *s1)
{
i n t i, j;
f o r (i = 0; i < 4; i++)
f o r (j = 0; j < 6; j++)
s1[i] = s1[i] + mat[i][j];
}
E21) Write a C function to transpose a matrix, that is, your function should make its
rows into columns, and vice-versa. 85
Programming in C E22) Write a C function to multiply two conformable matrices. Use your function to
find the product P = M × M × M for the matrix M [3][3] given by
{ {4, 9, 2},
{3, 5, 7},
{8, 1, 6}
}
E23) Write a C function to determine if a given matrix input to it is a magic square, that
is, if all its rows, columns and diagonals sum to the same number. Use your
function to prove that both M and P in exercise 22 above are magic squares.
We conclude this unit here. You may use the summary given in the next section to
recapitulate what we have discussed regarding functions.
8.7 SUMMARY
1. We have discussed how the functions are helpful in breaking up large programs
into small, manageable tasks and encourage modular programming.
3. We discussed the scope of variables declared in functions and the rules governing
the use of variables declared externally to a function.
8.8 SOLUTIONS/ANSWERS
Note that we have used the variable factor to save the prime factor returned by
isprime(). The assignment statement factor = isprime(number) has the
value returned by isprime(). We use this to check whether the value returned
by isprime() is one or greater than one. If it is greater than one, the number is
composite and the value saved in factor is its smallest prime factor.
You can also use strchr() function given in Table. 1 on page 83 to write such a
function. Try it!
91
UNIT 9 FUNCTIONS -II
Structure Page No.
9.1 Introduction 93
Objectives
9.2 Recursive Functions 94
9.3 Macros 99
9.4 Conditional Compilation 101
9.5 Macros with Parameters 102
9.6 Command-line Arguments 104
9.7 Variable-length Argument Lists 106
9.8 Complicated Declarations 107
9.9 Dynamic Memory Allocation 110
9.10 Summary 114
9.11 Solutions/Answers 114
9.1 INTRODUCTION
The C language imparts to the function concept a reach and power. For one thing, a C
function can call itself. Functions that call themselves are said to be recursive. This is a
powerful idea; it enables elegant solutions where the iterative approach of loop
structures can provide only cumbersome alternatives. In such a call the values of the
current parameters as well as the return address are preserved, and control reenters the
function from its beginning with a new set of parameters. In this new computation again
the function can call itself, and again in the computation arising from this last call, and
then once more, and so endlessly. Each call to a recursive function can generate a new
call to it. There is the very real danger therefore of an infinite number of calls never
punctuated by a return; the stack, which must hold not merely each return address but
also the values of the parameters created in each call, can soon become full, causing a
carelessly written recursive program to be gracelessly terminated. For a recursion to be
successful, at some point such conditions must be created that no further calls to the
function will be required. Then control can return to the address it last came from; then
back again to where that call came from, and further back, telescoping inwards in this
way until the stack which maintains addresses and parameter values is again empty. In
Sec. 9.2 of this Unit we will look at some simple examples of recursive functions.
Then again, functions can be created to cope with a variable number of arguments:
print() and scanf() are two examples. But you may create such functions yourself.
We’ll see how this is done in a function called addem(), which returns the sum of its
arguments. In one call you may send in 15 values to it; in the next, 20.
Also in this unit we will learn how to supply arguments to a program when it begins
executing. such arguments are called “command-line arguments”, because the
arguments are supplied in the same line as the command itself. How can this feature be
used? Well, computer users apply it everyday in such simple commands as:
copy file_1 file_2 <CR>
This command makes a copy of file_1 and names the copy file_2. The copy
program is designed to accept file names at run time. The file names are parameters, but
copy does not know their names in advance. In all the function we’ve used so far, the
parameters came from within the program, where they had been “wired in”. Here,
parameters are supplied when the program begins to execute!
Finally in this Unit we will look at two memory allocating functions, calloc() and
malloc(). these functions are required when memory must be allocated to a program
while it is executing, i.e. dynamically. As we know, the one drawback of arrays is that
their sizes must be predeclared. They are therefore of limited practical use in situations
where one doesn’t know beforehand how much storage the data which will be input to
the program, or which it will generate, can require. With help of memory allocation
functions we can allocate memory while it is executing.
Objectives
After studying this unit, you should be able to
• define and use recursive functions;
• use macros as functions;
• write programs that can take command-line arguments;
• write functions with variable-length argument lists;
• create and understand complicated Declarations; and
• do dynamic memory allocation.
Recursive functions are functions that call themselves, so a recursion is the invocation
of a computation inside a currently executing identical computation. Therefore, a
recursive function is one which uses itself in its own definition. If I ask, “What is the
94 meaning of meaning?”, and you begin to answer that question with the words, “The
meaning of meaning is...”, you will be in an infinite recursion. You will be using the Functions-II
word in giving its definition. Each time that you use it I can insist that you go back to
define it! Each time that you attempt to define the word, you must use it. We will
therefore both be trapped in an infinite recursion. Fortunately the recursive procedures
of computer science are free (still) of a metaphysical component, and can be used to
create elegant algorithms for problems that would be difficult to solve iteratively.
For an illustrative example, consider a function int sum (int n) that returns the sum
of the first n integers. If n were 100, it would evaluate the expression:
1 + 2 + 3 + · · · + 98 + 99 + 100.
This call states a self-evident truth. “The sum of the first n integers is n plus the value
of a function which sums the first (n - 1) integers.” If we knew how to compute
sum ( n - 1), it would be an easy matter to find sum (n). We would just add n to
find the answer. Again,
sum (n - 1) = (n - 1) + sum (n - 2);
To find sum (n - 1) we would want to call sum (n - 2); once we had the result
from that call, we would add n − 1, and that would be the value of sum (n - 1)! The
call to sum (n - 2) would require a call to sum (n - 3), and so on. Finally we
would want sum (1), which is simple; it happens to be 1. Here is the function:
i n t sum ( i n t n)
{
i f (n == 1)
r e t u r n 1;
else
r e t u r n (n + sum (n - 1));
}
The method works because whenever the function invokes itself it, uses an argument
smaller than the one in its last call. The value of the function in its final call is defined
without self reference. Recursion provides another example of the divide and conquer
technique: to solve a large problem, divide it into successively smaller problems; and
solve those to solve the large problem.
Probably the most famous example of recursion is the towers of Hanoi puzzle, in which
64 disks of different radii are piled on a peg, called SOURCE, in order of increasing
radius from top to bottom. See Fig. 1 on the following page. Two other pegs are given,
namely TEMP and TARGET and the puzzle is to transfer all the disks to TARGET,
with two conditions:
i) only one disk can be moved at a time
ii) a disk cannot be placed upon a smaller one.
TEMP is used as a temporary location necessitated by proviso (ii) above while effecting
the transfer. A recursive algorithm for the tower of Hanoi Puzzle with n disks is easy to
state:
Hanoi (n, SOURCE, TEMP, TARGET)
{
i f (n == 1)
move disk from SOURCE to TARGET;
i f (n > 1)
{
/* move n - 1 disks from SOURCE to TEMP, using TARGET */
Hanoi (( n - 1), SOURCE, TARGET, TEMP); 95
Programming in C
Quite different from recursion is the case of chained calls to a function. Suppose we
have a function that returns the HCF(also known as GCD) of two integers a and b.
Then, if we wish to use it to find the HCF of three numbers a, b and c we may invoke
it twice in the following way:
hcf (hcf (a, b), c);
The inner calls to hcf() are not recursive: hcf() does not call itself. It’s the
programmer who does so.
Program 9.2 uses Euclid’s Algorithm to compute the HCF of two numbers:
/* Program 9.2; File name: unit9-prog2.c */
# i n c l u d e <stdio.h>
i n t hcf( i n t a, i n t b);
i n t main( void )
{
i n t x, y, z, w, answer; 97
Programming in C printf("Enter four positive integers: ");
scanf("%d %d %d %d", &x, &y, &z, &w);
answer = hcf(hcf(x, y), hcf(z, w));
printf("%d\n", answer);
answer = hcf(hcf(hcf(x, y), z), w);
printf("%d\n", answer);
r e t u r n (0);
}
i n t hcf( i n t p, i n t q)
{
i n t divisor, dividend, remainder;
dividend = (divisor = p < q ? p : q) == p ? q : p;
w h i l e (remainder = (dividend % divisor)) {
dividend = divisor;
divisor = remainder;
}
r e t u r n divisor;
}
E1) int hcf ( int p, int q) is an iterative function. However its definition suggests
the following recursive implementation:
i f (p < q)
hcf (p, q) = hcf (q, p);
e l s e i f ( p >= q && p % q == 0)
r e t u r n q;
else
r e t u r n hcf (q, p % q);
Rewrite Program 9.2 using this recursive definition of hcf().
E2) Create and execute Program 9.3 below:
1 /* Program 9.3; File name: unit9-prog3.c */
2 # i n c l u d e <stdio.h>
3 char lifo( char c);
4 i n t main( void )
5 {
6 char anykey;
7 lifo(anykey = getchar());
8 putchar(anykey);
9 r e t u r n (0);
10 }
11 char lifo( char C)
12 {
13 i f (C == ’\n’)
14 r e t u r n ’\0’;
15 C = getchar();
16 lifo(C);
17 putchar(C);
18 r e t u r n (0);
19 }
Can you explain its output?
E3) The Fibonacci numbers are most easily evaluated iteratively. However, their
definition:
fib (n) = fib (n - 1) + fib ( n - 2)
invites a recursive computation, in which unfortunately there are great overheads
because of the several superfluous calls. Include the function below in a program
98 to compute fib (10).
i n t fib (n) Functions-II
{
i f (n == 1 || n == 2)
r e t u r n 1;
else
r e t u r n (fib (n - 1) + fib (n - 2);
}
Modify your program to print how many calls are made to the function. The
number might surprise you!
E4) The C language has no operator for exponentiation. Write a recursive function
power (x, n) that returns x to the nth power.
E5) Write a recursive function fact (n) to compute the factorial of a small positive
integer.
We close this section here. In the next section, we will discuss how to create C macros,
what are their advantages etc.
9.3 MACROS
We have frequently made use of macro definitions in our programs. In this section we
shall study their properties in somewhat greater detail.
99
Programming in C Typically, a macro definition has the form:
#define name replacement-string
A replacement string in a #define may include other, previously #defined identifiers. In
such cases, after the replacement has been made at the appropriate position in the
program text, the substitution is scanned again to determine if it contains other #defined
identifiers, which are then replaced by their current tokens. A long replacement string
of a #define may be continued into the next line by placing a backslash at the end of the
part of the string in the current line. The scope of a macro definition is limited to the
lines of code which follow below in the same file. Only a #defined token is substituted,
but not when it occurs inside a quoted string, nor if it happens to be included in the
name of another identifier. For example, consider the definitions:
#define h 1
#define e 2
#define n 3
At first glance Program 9.5 doesn’t look very much like a C language program. But the
#definitions in defs.h, processed before the other program statements, ensure that
the compiler gets to see a compilable program. Here are the contents of the file
defs.h:
# define DO_THE_FOLLOWING BEGIN
# define BEGIN {
# define WRITE_THIS_STRING printf(‘‘%s\n’’,
# define WRITE_THE_VALUE_OF printf(‘‘\%f\n’’,
# define LONG_PRODUCT 1.23 * 2.34 * 3.45 * 4.56 * 5.67 * 6.78
# define AND );
# define NO_MORE END
# define END }
# define CONSIDER_THIS_PROGRAM main()
Observe that the file defs.h has been enclosed by double quotes in the #include,
rather than by angle brackets as in the case of stdio.h. The double quotes tell the
preprocessor to search for the named file first in the directory which contains the source
100 file. If the file is not found seek the specified file only in the system directory.
Inclusion of files is a very powerful feature of C. It’s used typically in situations like the Functions-II
following: suppose a physicist uses the values of constants such as the velocity of light,
the charge of the electron, Planck’s constant, etc. in her computations. Then, instead of
assigning values to them in each of her source programs, she can collect them in one
place, say in a file called consts.h, and #include this file wherever the values are
needed.
#included files may contain more than just #defines: variable declarations or
definitions, function prototype declarations and functions may occur in them, too. More
than one file may be #included in a program; however, if in a #included file_A there is
a reference to quantities defined in another #included file, say file_B, the the inclusion
of file_B must precede that of file_A. More, a #included file may itself contain other
#include directives.
Suppose we are working on program that will run on a variety of machines with
different word sizes. How can we ensure that the program compiles correctly on all the
machines? This can be achieved using macros that facilitate conditonal compilation, as
we will see in the next section.
Some C macros provide the possibility of conditional compilation; because macros are
evaluated during preprocessing, with the use of conditionals it is possible to control the
compilation process itself. Suppose the physicist programmer wants to avoid multiple
inclusion of the file consts.h. She might write:
# i f n d e f ELECTRON_CHARGE /* i.e. if consts.h not #included */
# i n c l u d e ‘‘consts.h’’
#endif
#include "consts.h"
Similarly the conditional #ifdef checks to see if something has already been #defined:
# i f d e f PC_32BIT
# d e f i n e WORD_SIZE 32
# else
# i f d e f PC_64BIT
# d e f i n e WORD_SIZE 64
#endif
These statements assume that there was a previous #definition, one or other of the
following:
# d e f i n e PC_32BIT
or
# d e f i n e PC_64BIT
If you had a program that was dependent on the word size of the host machine, you
would want to include one or other of these definitions before compiling it for the target
machine. Note that the apparently incomplete statement: 101
Programming in C # d e f i n e VAX
suffices; it imparts to VAX a nonzero value, though for clarity you might wish to write:
# d e f i n e VAX 1
The # if and # elif (else if) preprocessor statements provide more extensive control over
compilation. The # if checks to see if its argument, which must be a constant
expression, evaluates to a non-zero value: then subsequent lines up to the next # elif or
#endif are processed, else they are ignored.
In Unit 2, we briefly discussed macro definitions. We also saw how a function can be
defined using a macro. In the next section, we will discuss macros with parameters that
enable us to define functions as macros.
Each occurrence of a formal parameter will be replaced by the actual argument in the
program. Thus, with the statements:
x = 3, y = 4, z = 5;
volume = VOL_CUBOID(x, y, z);
the value assigned to volume will be 60. Such “inline-functions” are convenient to use,
of course. But they have disadvantages. too: for example, if you need to find the
volumes of cuboids at several different places in your program, each “call” to the macro
at those places will translate into a line of code, which will be inserted into your
program’s text in substitution of the macro. Your executable program could become
longer than if you had used a function, and called it instead each time that you needed
to compute a volume. On the other hand, calling a function entails the overhead of
passing values, saving return addresses and the like. Using macros may consume space;
using functions may consume time. Moreover in using functions you must worry about
argument types: will you write different functions for different data types?
Quite apart from considerations of space, time and the labour of writing, two notes of
caution are in order in the use of macros with parameters. First suppose that the sides of
our cuboid are increased by 2 units each. The statement:
volume = VOL_CUBOID(x + 2, y + 2, z + 2);
will not yield the volume of a cuboid of sides 5, 6 and 7: instead, the macro will expand
to:
volume = x + 2*y + 2*z + 2;
giving a result which is quite wrong. You might think that the correct answer will
follow if parentheses are used in the macro’s definition:
# d e f i n e VOL_CUBOID(A, B, C) (A)*(B)*(C)
Second, consider what might happen if you had a macro to determine the volume of a
cube, and you wanted to use it to compute the volumes of two cubes, one of side 17 and
the other of side 18 units. In your program you might write:
# d e f i n e VOL_CUBE (X) ((X)*(X)*(X))
i n t side, volume;
side = 17;
volume = VOL_CUBE(side);
No doubt this would give you the correct volume for a cube of side 17. But if, for the
second cube you wrote:
volume = VOL_CUBE(++ side);
side gets incremented thrice, and you will be returned the volume of a cuboid of sides
18, 19 and 20! That wouldn’t have happened if you’d used a function instead. Moral:
unpleasant side-effects can result if you use the incrementation or decrementation
operators inside macros with parameters .
E7) Write a macro to swap to values of two int s X and Y. Under what conditions may
your macro fail?
Before graphical user interfaces were created, all the programs have to run from the
command line. Even now, many programs are run from the command-line. In the next 103
Programming in C section, we will see how to create programs that can take arguments from the command
line.
For a straightforward example, suppose that you’ve gone grocery shopping and have
made purchases at several shops. On returning home you wish to add up the amount
you’ve spent. Fortunately you have a program called add.c, with which all you need
do is type the command add and the amounts of your bills at the operating system
prompt:
add 12.34 23.45 34.56 45.67 56.78 67.89 78.90 <CR>
Promptly the machine responds: 319.59. In this instance the number of parameters
(excluding the program name itself, add) was seven. On another occasion that number
may be 17. No matter: C can handle them! The implication is that main() itself is now
a function to which values are passed by you. The driving program is you yourself, and
main() the function that you invoke, dynamically passing arguments when doing so.
Convenient as they are, command line arguments force the regrettable interpretation
that the user is herself a program, that passes values to a function!
To write programs with command line arguments, one must provide main() itself with
a parameter list, since main() is now a function driven by the user. Two parameters
suffice. These are customarily called argv, which is a value of type int ; and argc,
which is an array of pointers to char. main() is declared as follows:
void main ( i n t argc, char * argv [])
argc keeps a count of the number of parameters passed, including the program name
itself; so argc is at least 1. In the present example, the number of parameters
(including the program’s name string, add), is eight; that is the value of argc. The first
parameter is “add”, while the eight is the quantity “78.90”. Each parameter is stored ad
a separate string in the array of pointers to char, argv[]. Thus:
argv [0] == "add",
argv [1] == "12.34",
...............
argv[7] == "78.90"
Further, it is guaranteed that argv [argc] is the null pointer. Thus argv [8] has the
value 0, and points nowhere.
One complication that we must deal with straightaway before writing the code for add.c
is that the value 12.34, etc., ( the amounts of your bills) are stored as alphanumeric
strings in argv[]. How can one do arithmetic with them? In order to be able to do so,
naturally we must first extract their numeric values. The library function atof() (for
ascii to float) comes to our aid. This function, declared in <stdlib.h>, converts its
string argument to a double precision floating point number. Such a function is not
terribly difficult to write: it must first look for an optional sign in the numeric string,
104 and then extract each char digit, one by one. To begin with suppose it finds a digit ’d’.
Its ordinal value will be (’d’- ’0’). If the next digit encountered is ’e’, the number Functions-II
built so far will have the value 10 * (’d’- ’0’) + (’e’- ’0’). Proceeding in
this way, the number is constructed from the string, digit by digit; for every digit
encountered, the number built so far is multiplied by 10, and the last digit found is
added in, until a decimal point is encountered. Digits after the decimal point are
weighted by negative powers of 10. Of course the programming becomes tricky if your
function must be able to cope with inputs like -3.14e-53. Luckily for us, atof() can
take even such strings in its stride. add.c is Program 9.6 below:
/* Program 9.6; File name: unit9-prog6.c */
# i n c l u d e <stdio.h>
# i n c l u d e <stdlib.h>
i n t main( i n t argc, char *argv[])
{
i n t i;
double result = 0;
f o r (i = 1; i < argc; i++)
result += atof(argv[i]);
printf("%.2f\n", result);
r e t u r n (0);
}
Besides atof(), the header <stdlib.h> contains declarations for several functions
for number conversion, memory allocation, random number generation and related
utilities. atoi() converts its string argument to an int value, and atol() returns a
long result from a digit string.
For another example of command line arguments consider Program 9.7 below which
echoes its string input in reverse order.
/* Program 9.7; File name: unit9-prog7.c */
# i n c l u d e <stdio.h>
# i n c l u d e <stdlib.h>
i n t main( i n t argc, char *argv[])
{
i n t i;
f o r (i = argc - 1; i > 0; i --)
printf("%s", argv[i]);
printf("\n");
r e t u r n (0);
}
You have to input each letter separated by space. If you want to enter a space precede it
by \ , i.e. a backslash followed by a space. For example, if you want to enter the string
“A man”, you will have to enter it as A \ man.
E10) Write a program that prints the maximum, minimum and average of the values
typed to it in the command line.
E11) Modify Program 9.7 so that it reverses every word in the string input to it.
Therefore for a palindromic input like:
able was i ere i saw elba
the output should be identical to the input.
(Hint: Use strlen() function discussed in Unit 8.)
Earlier, we had mentioned that scanf() is a C library function. You would have
noticed that in all the functions we have come across, with the exception of functions 105
Programming in C like scanf() and printf(), we knew before hand the number of arguments of the
functions. How can we create functions like scanf() and printf() which can have
any number of arguments. We will discuss this in the next section.
The declaration ... means that the number and types of arguments may vary; the “three
dots” ... declaration can appear only at the end of an argument list. The header
<stdarg.h> must be #included; it declares a new type va_list; va_list is used
to declare a pointer which can be made to point to each of the unnamed arguments
subsumed in the declaration ... In addem() we call this pointer vals_ptr.
The final call to va_end() from addem() does any house-keeping that may be
required before control is sent back to main().
/* Program 9.8; File name: unit-prog8.c */
# i n c l u d e <stdio.h>
# i n c l u d e <stdarg.h>
i n t addem( i n t *how_many, ...);
i n t main( void )
{
i n t sum, *num_args;
sum = addem(num_args, 1, 2, 3, 4, 5, 0);
printf("Sum of arguments was: %d\n", sum);
sum = addem(num_args, 10, 20, 30, 40, 50, 60, 70, 0);
printf("Sum of arguments was: %d\n", sum);
r e t u r n (0);
}
106 i n t addem( i n t *how_many, ...)
{ Functions-II
i n t current_value, total = 0;
va_list vals_ptr;
va_start(vals_ptr, how_many);
f o r (; current_value = va_arg(vals_ptr, i n t );)
total += current_value;
va_end(vals_ptr);
r e t u r n total;
}
E12) Modify Program 9.8 above so that how_many conveys the number of arguments
passed to addem(). (The for (;;) loop of addem() should be processed
how_many times: a zero as the argument list terminator is then not required.)
E13) Write a C language program that calls a function max () with a variable length
argument list to return the maximum value of the arguments passed to it.
So far, we have seen functions that have variables of some type of other, char, int etc,
as arguments. Suppose we want to write a function that takes another function as an
argument, how can we do it? For example, if we want to write a function that integrates
functions or differentiates functions ? How can we we declare such a function? We will
discuss these issues in the next section.
How would you declare a function returning a pointer to an array of pointers to float ?
Beginners often find C declarations a source of confusion, but the process of
constructing the correct declaration to suit a particular purpose, and conversely, the
correct interpretation of a given declaration is algorithmic, and therefore mechanical. In
any declaration, for “*” read “pointer to”; “()” means “function returning”; and “[]”
implies “array of”. There are two operative rules:
1) The parentheses and subscript operators have a higher priority than the indirection
operator.
2) The closer an operator is to the identifier, the higher is its priority.
Consider the two declarations:
char * func_1 ();
char (* func_2) ();
In the first declaration, the priority of the parentheses operator over the indirection
provides the proper interpretation: func_1 is a function that returns a pointer to char.
In the second declaration the parentheses around * func_2 have the highest priority,
by rule 2: so func_2 is a pointer to a function returning char. What does this mean?
Suppose there is a function of type char called seventh_char() that returns the
seventh character of a string. Then the assignment:
func_2 = seventh_char;
will call seventh_char() with the argument “Hello World”, which will deposit the
letter W at the current position of the cursor, as indeed would the statement:
putchar (seventh_char ("Hello World\n"));
the compiler will flag an error because the assignment attempts to associate the value
returned by the function seventh_char – a char – with a pointer to a function
returning char(the definition of func_2): a type mismatch error.
Note
Dereferencing a function pointer is equivalent to making a function call. Thus
is equivalent to
is also acceptable, but the first form in which the pointer is explicitly derefer-
enced is preferable because older compilers may not support this syntax.
Include the following instruction in your program and compile it with any ANSI
compiler.
putchar (func_2(first_string));/*in ANSI C*/
When a pointer to a function f() is used as an argument to another function g(), say,
then f(), and any other function that returns a pointer like f(), can be executed
through g(). So g() can be used as the “envelope” to execute several different
functions, if need be, each returning pointers of the same type. This is often a great
convenience in advanced level programming. For another example of a somewhat
complicated declaration, let’s illustrate the difference between the two declarations:
i n t * x [5];
and
i n t (* y) [5];
The first declares x to be an array of pointers to int , by rule 1, since the subscript
operator has a higher priority than the indirection operator; the second declares y to be
a pointer to an array of int s. Similarly, the declaration:
i n t * ptr [5][6]; 109
Programming in C makes ptr an array of 5 elements because the leftmost subscript operator has the
highest priority, by rules 1 and 2. Those elements must be pointers, because the *
operator has by rule 2 a higher priority than the rightmost subscript operator. So ptr is
an array of 5 pointers to a six element array of int s.
This means:
In next section, we will see how to allocate memory dynamically as and when needed
by the program.
How many will they be? One way to tackle the problem is to declare an array of
long ints of some anticipated size, say 50,000, optimistically hoping that so large an
array will suffice every time the program is run. The declaration:
long primes [50000];
does this for us. It sets aside 50000 words of memory. But there may be times when
this may be too much memory (when there are too few primes to store), and times when
this may be too little, when n is large. In the first case memory is wasted; in the second,
the primes generated will overflow their designated area, and may be written over
110 potentially important code or data!
It so happens, however, that it is possible to estimate the number of primes that will be Functions-II
generated in each run of the program. There’s a lovely theorem (named the Hadamard -
de la valle theorem after its two discovers) which tells us that for a given N, the
approximate (asymptotic) number of primes less than N is the integral:
Z N
1
/* approx number of primes ≤ N */
2 ln x
Since N is known only at run time, n, the number of primes to be stored, can be found
by computing the integral. The number of bytes of storage required will be
n * sizeof prime.
The pointer returned by calloc() or malloc() must be cast to the appropriate types,
as in:
primes_ptr = ( i n t *) calloc (num_primes, s i z e o f ( i n t ));
(int *) casts the value returned by calloc() to a pointer to int . A call to the function
free (ptr) frees the space allocated by a previous call to calloc() or malloc(),
where ptr is the pointer returned by the allocator. It is an error to release memory by a
call to free(), that had not previously been allocated by calloc() or malloc().
Blocks may be freed in any order, independently of the order in which they were
requested.
111
Programming in C f(x)
f(x)
x
a b
h
The program uses two other int pointers, top_ptr and bottom_ptr. Primes_ptr as
returned by calloc() returned in top_ptr. As primes are generated, each new one
that is found is stored:
*(++primes_ptr) = next_try;
Finally, when all primes up to N have been found, the current value of prime_ptr is
saved in bottom_ptr; the for () loop
f o r (primes_ptr = top_ptr; primes_ptr <= bottom_ptr; primes_ptr++)
printf("%d\n", *primes_ptr);
lets you look at all these primes as they scroll past you on the screen. Note that the
comparison of two pointers is a valid pointer operation.
Program 9.10 in Listing 1 on the facing page is a simple example illustrating how
calloc() is called. A place where malloc() would be useful is in a program to
balance a bank account’s pass book, whose requirements we briefly described in the
last Unit. As each new record is added, we have to set aside more memory for the new
data items. The function cheque_issue() below is a “bare bones” illustration of
malloc() in such situations; it has no error checking, and its external variables would be
external to main().
/* Program cheque issue */
char cheque_for_who_ever[80];
f l o a t cheque_amt;
# i n c l u d e <string.h>
# i n c l u d e <stdio.h>
# i n c l u d e <stdlib.h>
void cheque_issue( void )
{
char *ptr;
printf("Cheque is in name of: ");
gets(cheque_for_who_ever);
printf("Enter amount being issued: ");
scanf("%f", &cheque_amt);
ptr = ( char *) malloc(strlen(cheque_for_who_ever)); 113
Programming in C strcpy(ptr, cheque_for_who_ever);
printf("%s %.2f", ptr, cheque_amt);
}
9.10 SUMMARY
C macros can be written with parameters. Such a “macro function” can be placed
anywhere inside a program, where the macro processor replaces it bye its #defined
expansion in terms of the “arguments” in the call. In contrast to functions, while such
macros save the overheads of passing arguments, the fact that they are expanded in situ
makes the executable program to become longer than if a function was used instead.
The # if , #ifdef, #ifndef, #else and #endif preprocessor statements enable
programs to be compiled subject to prescribed conditions.
The calloc() and malloc() functions can be used to dynamically allocate memory;
memory obtained in this way can be freed by free(). These functions are accessible
by including the header <stdlib.h>.
9.11 SOLUTIONS/ANSWERS
E2) Suppose you type ‘abc’ and press <CR>. This is what the program will do:
1) Save the character in anykey and calls lifo() with the argument ’a’. The
control reaches line no. 12 of the program. Since this test for the carriage
return character fails, the line C = getchar() is executed and C gets the
value ’b’. The function lifo() is called for the second time with the value
’b’.
2) In the second call, the control reaches line no. 12. Again the the test for
carriage fails. The function lifo() is called for the third time with the value
’c’.
The third time, since we typed the carriage return after ’c’, the test in line 12
is passed and the control returns to the second call of the function at line 16,
from where the function was called for the second time. The rest of the
function is executed. Remember that, in the second call, the value of C is ’c’
and this is printed by line number 17. After this, the control returns to the
first call of lifo() and in this call, the value of C is ’b’ and the instruction
in line 17, prints this value. Finally, control returs to the main program at line
8. The value of anykey, which is ’a’ is printed.
So, the program prints the characters we have typed in reverse order!
118
UNIT 10 FILES AND STRUCTS, UNIONS AND
BIT-FIELDS
Structure Page No.
10.1 INTRODUCTION
If you have used any computer application, whether it is a word processor or a
spreadsheet or a presentation package, you would have saved your work in files in the
hard disk. This is a very convenient feature because you have a permanent record of
your work that you can change or update and you can also share your work with others.
Also, sometimes very large programs use a large amount of data again and it is rather a
waste of effort to generate it again and again. So, you need to know how to work with
files, how to save data from files, read data and modify files. We will disscuss this in the
Sec. 10.2.
Let us now turn our attention to the organisation of information within files. Suppose
that you want to store data about the passbook entries for a savings bank account. For
each transaction you would want to store a variety of items of different types; to make
our example more concrete, suppose that you’ve received a cheque, and you’d like to
record the following details about it: by whom given, of what amount, on which bank,
whether local or outstation, and date and number of cheque. How will this information
be organised? It would be convenient if there was a single data receptacle that could
hold all this information in one place. Clearly that receptacle couldn’t be an array. An
array can only hold items of just one type, int s or chars or whatever; the information
that you need to store happens to involve a variety of types: char arrays, int s and a
float . What you’re looking for is a data structure that in C parlance is called the struct .
A struct, similar to Pascal’s record, can hold several kinds of items, and enables you
to maintain related information of different types in a single object. Structs are
introduced in Sec. 10.3 of this Unit. In Sec. 10.4, Sec. 10.5 and Sec. 10.6, we discuss
how to store and access data in structs.
A union is a C device that allows you to view a single storage area in more than one
way; a variable declared as a union can store different kinds of data at different times.
We discuss union in Sec. 10.7 of this unit.
Files are the most visible and the most valuable feature of any computer system to its
users because they provide a mechanism for the long term storage of data; they remain
unaffected even if a computer’s power is switched off. Computers would be no use at
all if they did not provide a mechanism for the permanent storage of information.
Files are stored in the concentric circular tracks of a floppy or a “hard” disk. A hard
disk, unlike a floppy, consists of several platters rigidly mounted on a common spindle;
each surface of platter may have several hundred tracks. These tracks contain an even
distribution of particles of a magnetic oxide, each of which can be permanently
magnetised in one of two ways, to store a 0 or a 1. So the bi-stable circuits of a
computer’s RAM find an analogue in the dual states of magnetisation of particles of
iron oxide on the disk’s surface. As a disk spins in its “drive” (this is a motor driven
platform on which the disk is fixed), a “read-write” head can be made to home in to any
of its tracks. In a hard (or even a floppy) disk drive, there’s a head for every surface.
These heads are joined to a common actuator arm; the arm (and so the heads) can move
radially along the disk’s surface, towards or away from its centre. Any head can be
positioned on any track on the surface it serves. When a file is read the contents of the
track spinning past the head (the individual little magnets) induce corresponding
electric signals inside a coil in the read-write head; these signals flow in the RAM,
where they are stored as bits. Thus reading a file means picking up the particulate
magnetisations on a disk’s tracks and storing them as electric signals of 0 or 1 in the
computer’s RAM. Conversely, when a file is written the contents of RAM–electric
signals of low or high voltage–are made to travel as currents to the coil in the read-write
head; these currents magnetise the particles flowing by under the head, while the disk
spins, creating in them an image precisely similar to the bits from which the signals
emanated. Thus are the contents of RAM captured on disk, and files are “written”. Disk
files are permanent to the extent that the magnetism of the particles in which they are
stored is permanent.
The tracks on a disk’s surface are divided into a number of sectors of equal size. Just as
each word of RAM has an address, so also has each sector of a disk. These addresses
are known to the computer’s operating system. The operating system also knows which
file is written on which sector(s)–these need not be contiguous–and it also keeps a list
of the free sectors on the disk. When a file is to be read,the operating system moves the
read-write head to the tracks which contains the sector on which the file begins; the
time that this head movement takes is called the “seek time”. Having reached its
designated track, the head waits there until the desired sector has appeared under it: this
waiting period is called the “latency time”. Finally the data from disk can be transferred
to the RAM; this third delay is called the “transfer time”. Large files may occupy
several sectors, which need not be contiguous. The read-write head may have to be
directed to sectors of different tracks on several surfaces to read large fragmented files.
You can see that because mechanical motions are involved, a disk read or write must by
CPU standards be a very slow operation; it is in fact hundreds of thousands of times
120 slower than a CPU cycle. So it’s just as well that in a read operation an entire sector is
read at a time. The contents of a sector are placed in a block of RAM called the input Files and Structs, Unions
buffer. Intelligent programmers organise their data in disk files so that when further and Bit-Fields
data items are needed by their program, they may already be in the input buffer from
the last read operation.
In a read operation, precisely how the read-write head is directed to a disk’s track from
which it must pick the chosen finole from the thousands that may be resident on the
disk, and how it performs the read while the magnetised particles rush past it
(sometimes as fast as two hundred km. per hour!) is a matter of enormous and quite
awe-inspiring complexity. Fortunately for us, as users of the computer we have nothing
whatsoever to do with this complexity. How C finds where exactly our files are on a
given disk, and how it finds free tracks therein to store the new files that we may create,
is a matter that it must negotiate with the computer’s operating system. That’s not our
concern. What is nice for us is that C provides a us with a very convenient interface to
deal with files. Its convenience lies in the fact that the functions for I/O that we have so
far used–printf(), scanf(), puts(), gets(), putchar() and getchar() for
monitor and keyboard I/O–have quite similar counterparts for disk I/O. There is a
special reason for this: the functions for keyboard input and monitor output that we are
familiar with, are themselves implemented via files. These functions depend upon three
files that work in the background, generally invisible to us. The files, declared in
<stdio.h>, are the standard input, the standard output and the standard error:
we shall have more to say about them later. The good news is, if you know how to use
scanf() and printf(), you know how to read from the disk and write to disk files!
Designers of operating systems call this uniformity of interface “device independence”.
Before a file can be used for reading or writing, it must be opened. This is done by a
call to the fopen() function. fopen() takes two string arguments, therefore enclosed
in double quotes. The first of these is the file’s name; the second is an option
(r, w, a, etc.) that tells C what we want to do with the file: read it, or write to it, or
append to it, etc. File handling functions are prototyped in <stdio.h>, which also
includes other needed declarations. Naturally this header must be #included in all your
programs that work with files. Table. 1 lists the options available with fopen(). A call
To connect your program to the file that fopen() opens, your program must also
declare a pointer of type FILE: that declaration is easy:
FILE * file_ptr;
Now you can assign to file_ptr the pointer that fopen() returned:
file_ptr = fopen ("passbook.dat", "r");
In this way file_ptr provides you read access to the file passbook.dat. You need
never again refer to the file itself by name: file_ptr will do all the required
book-keeping for you, such as holding your place in the file, etc.
In typical concise fashion C programmers combine the file opening operation with a
check to see if the pointer returned by fopen() was NULL:
i f ((file_ptr = fopen ("passbook.dat", "r")) != NULL)
printf("Had no difficulty in opening passbook.dat\n");
else
printf("Regrets...couldn’t open passbook.dat");
Even more concisely, one writes:
i f (file_ptr = fopen ("passbook.dat", "r")) etc.
A call to fclose() closes a previously opened file, flushing as it does so any
associated buffer; the single argument of fclose() is the relevant file pointer. Let’s
now look at Programs 10.1 - 10.5.
fprintf ()
Program 10.1 illustrates how the three parts of the argument list of fprintf(), the
function to write to a file opened by fopen(), are written: the first is the file pointer,
the second is the control string, and the third is the list of variables or expressions to be
written:
fprintf(file_1, "%s", "Writing to a disk file is\n");
Upon successful return fprintf() returns the number of characters printed.
/* Program 10.1; file name: unit10-prog1.c */
# i n c l u d e <stdio.h>
i n t main( void )
{
FILE *file_1;
i f ((file_1 = fopen("newfile.dat", "w")) != NULL) {
fprintf(file_1, "%s", "Writing to a disk file is\n");
fprintf(file_1, "%s", "quite easy, as no doubt you\n");
fprintf(file_1, "%s", "will readily agree.\n");
fclose(file_1);
} else
printf("Unable to open newfile.dat for writing");
r e t u r n (0);
}
Executing Program 10.1 creates the 77 byte file named newfile.dat. Here are its
contents:
Program 10.2 reads 5 int values from the keyboard, and stores them in the file
122 numbers.dat:
/* Program 10.2; file name:unit10-prog2.c */ Files and Structs, Unions
# i n c l u d e <stdio.h> and Bit-Fields
i n t main( void )
{
FILE *ptr;
i n t numbers[5];
i n t i;
i f ((ptr = fopen("numbers.dat", "w")) != NULL) {
printf("Enter 5 numbers,\
to be stored in numbers.dat...");
f o r (i = 0; i < 5; i++) {
scanf("%d", &numbers[i]);
fprintf(ptr, "%d\t", numbers[i]);
}
fprintf(ptr, "\n");
fclose(ptr);
}
else
printf("Unable to open numbers.dat for writing...\n");
r e t u r n (0);
}
Here is the result of executing Program 10.2:
Enter 5 numbers, to be stored in numbers.dat...1 2 3
4 5
The file numbers.dat was created:
1 2 3 4 5
fscanf ()
Similarly fscanf() reads files. Its arguments are written as shown below:
Each successive pointer argument must correspond properly with each successive
conversion specifier. Program 10.3 reads the file numbers.dat, and sums its entries.
printf() and fprintf() are therefore close cousins, as are scanf() and
fscanf(); but there are two other useful members of this family, namely sprinf()
and sscanf(). sprintf() writes its output in a string, whose address is its first
argument; as before, its second argument is the format control string, and this is
followed by a list of variables or expressions:
Likewise, sscanf() can read a string and store its contents into the pointers
constituting list:
fgets ()
Recall from Unit 7 that scanf() is not the most convenient function to read strings
with–it regards white space as a string delimiter–and fscanf() is not different in this
regard. But the fgets() function, like the gets(), is easy to use if you wish to read a
whole line from a file at a time. It is generally considered unsafe to use gets(). This
is because there is no restriction on the length of the string read. Because of this, if a
very long string is read in, the program can crash if there isn’t enough memory to store
it. This doesn’t happen in fgets() because the size of the string is specifically
mentioned.(Another alternative is to use scanf() with the format specifier %20s, for
124 example, to limit the length of the string read in to 20.) fgets() is called with three
arguments: a pointer to a char array that will store the string read, an integer specifying Files and Structs, Unions
the maximum number of characters to be read, say n, and a file pointer specifying the and Bit-Fields
file to be read. Program 10.5 uses an array of 50 chars, named input_buffer [], in
which fgets() deposits the contents of newfile.dat, a line at a time. fgets() reads
characters until a newline occurs in the input, which is stored, or until it has read n - 1
characters. It appends the null character after the last character read. As a function,
fgets() returns the value of its first argument (the pointer input_buffer if a read
was successful, otherwise the NULL pointer. This property is exploited in the while()
loop of Program 10.5.
/* Program 10.5; file name:unit10-prog5.c */
# i n c l u d e <stdio.h>
i n t main( void )
{
FILE * file_1;
char input_buffer[50];
i f ((file_1 = fopen("newfile.dat", "r")) != NULL){
w h i l e ((fgets (input_buffer,
s i z e o f (input_buffer), file_1)) != NULL)
printf(input_buffer);
fclose (file_1);
}
else
printf(" Sorry, unable to open newfile.dat \n ");
r e t u r n (0);
}
fputs ()
Similarly, fputs(), used in Program 10.6, writes a string in a file. Its two arguments
are the memory address of an input string and the file pointer. The input string is
written up to (but not including) its null character. Program 10.6 accepts the names of
two files in its command line. It copies the first file into the second, line by line.
/* Program 10.6; file name:unit10-prog6.c */
# i n c l u d e <stdio.h>
# i n c l u d e <stdlib.h>
i n t main( i n t argc, char *argv[])
{
FILE * file_1, *file_2;
char input_buffer[50];
i f ((file_1 = fopen(argv[1], "r")) == NULL)
printf("Unable to open %s for reading\n", argv[1]);
else
i f ((file_2 = fopen(argv[2],"w")) == NULL)
printf("Unable to open %s for writing\n",argv[2]);
else{
w h i l e ((fgets(input_buffer,
s i z e o f (input_buffer), file_1)) != NULL)
fputs(input_buffer, file_2);
fclose(file_1);
fclose(file_2);
}
r e t u r n (0);
}
getc () and putc ()
getc() and putc() are two character I/O functions for files, very similar to
getchar() and putchar() respectively. The single arguments of getc() is an input
file pointer, it fetches the next character from the file, and returns EOF at end of file, or
if an error occurs. putc() has two arguments: the character to be written, and a pointer
to the output file. (Why has the variable next_char in Program 10.7 been declared as 125
Programming in C an int ?)
/* Program 10.7; file name: unit10-prog7.c */
# i n c l u d e <stdio.h>
i n t main ( void )
{
FILE *file_1;
i n t next_char;
i f (file_1 = fopen ("newfile.dat", "r")){
w h i l e ((next_char = getc (file_1)) != EOF)
putc(next_char, stdout);
fclose (file_1);
}
else
printf("Sorry, unable to open newfile.dat\n");
r e t u r n (0);
}
Realise from Program 10.7 that putchar(x) is equivalent to putc (x, stdout),
and getchar() is equivalent to getc (stdin).
The call:
fread (copy, s i z e o f ( double ), NUM_EL f1);
reads NUM_EL doubles from a file associated with the pointer f1 into a buffer named
copy.
Program 10.8 opens (creates) a file double.dat for both writing and reading: observe
the ‘‘w+’’ option in the call to fopen(). numbers is the name (i.e. address) of a
buffer of dimension NUM_EL. The doubles stored in numbers are written into
doubles.dat; but this file cannot be displayed on the screen because the objects written
in it are in the format in which they were stored in memory. To look at the contents of
the file, you’ll have to access it via an fread(). However, the file pointer after the last
write is stationed at the bottom of the file; before you can begin to read it you must
bring its pointer back to the top. The call:
rewind (f1);
brings the read pointer back to the beginning of the file.
The call to fread() in Program 10.8 reads the file into the buffer copy. The for (;;)
loop prints the contents of copy [].
/* Program 10.8; file name:unit10-prog8.c */
# i n c l u d e <stdio.h>
# d e f i n e NUM_EL 100
i n t main( void )
126 {
FILE * f1; Files and Structs, Unions
s t a t i c double numbers[NUM_EL] = and Bit-Fields
{
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12
};
s t a t i c double copy[NUM_EL];
i n t i;
i f ((f1 = fopen("doubles.dat", "w+")) != NULL)
fwrite(numbers, s i z e o f ( double ), NUM_EL, f1);
else
printf("Error writing in file doubles.dat...\n");
rewind(f1);
fread(copy, s i z e o f ( double ), NUM_EL, f1);
f o r (i = 0; copy[i] > 0; i++)
printf("%.0f\n", copy[i]);
fclose(f1);
r e t u r n (0);
}
The end-of-file condition can be tested by a call to feof(); this function, whose single
argument is a file pointer, returns a non-zero value if an attempt is made to read beyond
the end of a file:
i f (feof (fp))
printf("Error: cannot read beyond EOF");
Here are some exercises for you.
E1) Write a C program to count the number of characters, words, and in lines in a text
file whose name is supplied in the command line.
You may have wondered that we have not written any program involving complex
numbers. We will discuss struct in the next section, which will enable us to work with
complex numbers.
10.3 STRUCTS
Arrays, as we have seen in the previous units, provide a means to bundle homogeneous
data items into a single named unit. But, more often than not, “real world” applications
require the grouping together of heterogeneous data items. Consider once more the
information that one would want to store about depositing a cheque in one’s savings
account:
i) by WHO issued;
ii) on what bank;
iii) whether cheque was local or outstation;
iv) cheque number
v) date of issue
vi) date of deposit
vii) amount of cheque
Of the items in our list, the first four are strings; the fifth and sixth, the dates of issue
and deposit, consist of triplets of int s; and the seventh is a float . If we considered using
arrays to store the records of several similar transactions, then we would need eleven in
all: four two-dimensional arrays for the strings; three int arrays each for the two dates;
and one of float s to hold the amounts of the cheques. Every transaction would require 127
Programming in C us to access these eleven arrays; nor could we know the appropriate number of elements
to specify for them: altogether an approach to be avoided.
Structs are a C facility that permit several heterogeneous but interrelated items to be
treated as a single unit. The keyword struct introduces such a unit:
s t r u c t cheque_deposited
{
char by_whom_given [30];
char bank [30];
char local_or_not [2];
char cheque_number [10];
i n t dd_issued;
i n t mm_issued;
i n t yy_issued;
i n t dd_deposited;
i n t mm_deposited;
i n t yy_deposited;
f l o a t amount;
};
This struct declaration names eleven members; (arrays have elements; structs have
members). But it reserves no storage. It creates a template that can be used to define
objects of type struct cheque_deposited:
s t r u c t cheque_deposited nobel_prize, copley_medal, fields_medal;
Here nobel_prize, copley_medal and fields_medal are structure identifiers.
Each of these structures can store the details of a cheque, just as much as an int can
store an integer value. The scientist who wins a Nobel Prize, a Copley Medal and a
Fields Medal can store the details of the cheques he receives in these structures. The
name cheque_deposited is a structure tag, and is optional: structs may be defined
without a tag, as for example the following structs to store the dates of our national
holidays:
struct
{
i n t dd;
i n t mm;
i n t yy;
} gandhi_jayanti, independence_day, republic_day;
Here gandhi_jayanti, independence_day, republic_day are three
structures that can store dates. Because the struct declaration is not tagged, a type has
not been created to store any other dates.
E2) Show how you will declare a struct to hold employee payroll data: name,
department, date of birth, date of joining, basic salary, dearness, house-rent and
other allowances, provident fund, life insurance and income tax deduction, and
contributions towards CGHS, ESI, etc.
E3) Show how you will declare a struct to hold the fee-bills of students in school. The
structures must be able to record the following information: student name, date of
birth, class and section, father’s name, address and telephone number, whether
student uses the school bus, distance she travels on the bust (this information is
used to compute the bus bill), tuition fee, and fees chargeable for extracurricular
activities, such as swimming, skating, etc.
E4) Show how you will declare a struct to record information about students enrolled
for a degree program at a University. The University offers, B.A., B.Sc., M.A.,
M.Sc. and Ph.D. degrees in several subjects. Each student is identified by a
unique enrolment number.
We have discussed in this section how to store heterogeneous data using struct. In the
next section, we will see how to access the data stored in stucts.
In order to gain access to a member of a structure, say float amount in the cheque for
the Nobel Prize, we use the dot operator, “.”
structure_name.member_name
as in the printf() below:
printf("Congratulations on your award! Amount deposited = %.2f",
nobel_prize.amount);
Though the dot operator may appear to you as a binary operator in the above
expression, it has in fact a priority as high as the parentheses or the subscript operators,
and like them it groups from left to right.
The members of a structure can be initialised to constant values by enclosing the values
to be assigned in braces after the structure’s definition (non ANSI C compilers may
require the static keyword in the lead):
/* static */ s t r u c t date Gandhiji_birthday = {
2, 10, 1869
},
Nehruji_Birthday = {
14, 11, 1889
};
/* static */ s t r u c t cheque_deposited delhi_lottery = {
"Delhi Administration",
"State Bank of India", 129
Programming in C "Y",
"381654729",
2, 5, 1994, 3, 5, 1994,
50000000.00
}
If there are fewer values listed in an initialisation than the number of members present,
then the remaining members are initialised to zero by default.
To print the approximate difference in the ages of Gandhiji and Nehruji we can write:
printf("Age difference = %d\n",
Nehruji_birthday.yy - Gandhiji_birthday.yy);
Program 10.9 illustrates how the dot operator is used to extract the members of a
structure:
/* Program 10.9 ; file name: unit10-prog9.c*/
# i n c l u d e <stdio.h>
s t r u c t cheque_deposited
{
char by_whom_given [30];
char bank [30];
char local_or_not [2];
char cheque_number [10];
i n t dd_issued;
i n t mm_issued;
i n t yy_issued;
i n t dd_deposited;
i n t mm_deposited;
i n t yy_deposited;
f l o a t amount;
};
i n t main ( void )
{
/* static */ s t r u c t cheque_deposited delhi_lottery =
{"Delhi Administration",
"State Bank of India",
"Y",
"381654729",
2, 5, 1994, 3, 5, 1994,
50000000.00
};
printf ("Cheque date: %d-%d-%d\n",
delhi_lottery.dd_issued,
delhi_lottery.mm_issued,
delhi_lottery.yy_issued);
printf("Cheque number: %s\n", delhi_lottery.cheque_number);
printf("Drawn on: %s\n", delhi_lottery.bank);
printf("Amount: Rs. %.2f", delhi_lottery.amount);
r e t u r n (0);
}
Finally, structures may be nested. Given the prior declaration of struct date,
130 struct cheque_deposited may be declared:
s t r u c t cheque_deposited Files and Structs, Unions
{ and Bit-Fields
char by_whom_given [30];
char bank [30];
char local_or_not [2];
char cheque_number [10];
s t r u c t date issued;
s t r u c t date deposited;
f l o a t amount;
};
Program 10.10 - 10.11 illustrate struct declarations and the use of the dot operator.
Program 10.10, which declares struct complex, implements the addition of complex
numbers. Its members store the real and imaginary parts of three complex numbers,
val_1, val_2 and their complex sum, name result.
/* Program 10.10 ; file name: unit10-prog10.c*/
# i n c l u d e <stdio.h>
s t r u c t complex
{
f l o a t real;
f l o a t imaginary;
};
i n t main ()
{
s t r u c t complex val_1, val_2, result;
printf("This program adds two complex numbers.\n");
printf("Enter real and imaginary parts of first\
number: ");
scanf("%f %f", &val_1.real, &val_1.imaginary);
printf("Enter real and imaginary parts of second\
number: ");
scanf("%f %f", &val_2.real, &val_2.imaginary);
result.real = val_1.real + val_2.real;
result.imaginary = val_1.imaginary + val_2.imaginary;
printf("The sum is %f + i * %f\n", result.real,
result.imaginary);
r e t u r n (0);
}
Program 10.11 uses nested structures: struct date is used in the declaration of
struct cheque_deposited. Because the dot operator associates from left to right, in
order to access the month parameter of struct date of structure nobel_prize, we
write:
nobel_prize.issued.mm;
/* Program 10.11; file name: unit10-prog11.c */
# i n c l u d e <stdio.h>
s t r u c t date
{
i n t dd;
i n t mm;
i n t yy;
};
s t r u c t cheque_deposited
{
char by_whom_given [30];
char bank [30];
char local_or_not [2];
char cheque_number [10];
s t r u c t date issued;
s t r u c t date deposited;
f l o a t amount;
} 131
Programming in C nobel_prize;
i n t main ( void )
{
printf("Size in bytes of struct cheque_deposited is %d\n",
s i z e o f ( s t r u c t cheque_deposited));
printf("Size in bytes of struct date is %d\n",
s i z e o f ( s t r u c t date));/*Continued from previous page*/
printf("Sizeof month parameter in struct date of ");
printf("structure nobel_prize is: %d\n",
s i z e o f nobel_prize.issued.mm);
r e t u r n (0);
}
Here are some exercises for you.
E5) Write a C language program to create an array of structures to record and print
employee payroll data. (Refer to exercise 2.)
E6) Write a C language program to create an array of structures to record and print
student fee payment data. (Refer to exercise 3.) Bus fees will depend upon actual
distance between home and school, which must be input to the program in each
case. Fees may be paid in cash or by cheque. If fees are paid by cheque, your
program should record relevant details.
E7) Write a C program to record and print data about students enrolled in the various
courses at a University. (Refer to exercise 4.)
In the next section, we will discuss the function fseek() that can be used to randomly
access any part of the file.
Structs are customarily associated with files and databases. Our program to record the
transactions listed in a passbook must store the information in files if it’s to be any use
at all. Naturally, because details about cheque issues and deposits must be different
from those for cash withdrawals and deposits, it may be simplest to have separate files
for each of these activities. Program 10.12 below is a “quick and dirty” way of writing
the information about deposited cheques to a file named cheqdpst.dat, and fetching
the data about any cheque already deposited. It’s quick and dirty because it uses the
same char array, namely value_holder [], to read the number of records to be
entered, the amount on each cheque deposited, and the number of the record to be
fetched; nor does the program store the number of transactions currently held in
cheqdpst.dat. Moreover, it has no error checking, no facility for data correction
after entry, no input for date variables, etc. But these features are easily built in. The
new, and extremely useful feature that this program introduces, is the C capability to
seek out an arbitrarily chosen record from the hundreds or thousands that may exist in
cheqdpst.dat. The fseek () function allows a file to be treated as though it were an
array of bytes: we can position ourselves anywhere inside that we want, and read any
number of bytes forwards or backwards from it!
The ‘‘a+’’ option in the call to fopen() opens the file cheqdpst.dat for
appending and reading. If the file does not exist, this call to fopen() creates it.
/* Program 10.12 */
# i n c l u d e <stdio.h>
# i n c l u d e <stdlib.h>
132 # d e f i n e NUM_CHEQUES 100
s t r u c t cheque_deposited Files and Structs, Unions
{ and Bit-Fields
char by_whom_given[30];
char bank[30];
char local_or_not[2];
char cheque_number[10];
f l o a t amount;
};
i n t main ( void )
{
FILE * fp;
s t r u c t cheque_deposited cheques[NUM_CHEQUES], cheque_holder;
i n t number, rcrd_nmb, i;
char value_holder[30];
f l o a t amt_deposited = 0.0;
i f ((fp = fopen("cheqdpst.dat", "a+")) != NULL){
printf("How many cheques to be deposited? ");
gets(value_holder);
number = atoi(value_holder);
f o r (i = 0; i < number; i++){
printf("\t\t\t...Enter details for \
cheque %d...\n", i + 1);
printf("Cheque issued by: ");
gets((cheques[i]).by_whom_given);
printf("Bank name: ");
gets((cheques[i]).bank);
printf("Local bank (Y/N): ");
gets((cheques[i]).local_or_not);
printf("Cheque number: ");
gets((cheques[i]).cheque_number);
printf("Cheque amount: ");
gets(value_holder);
cheques[i].amount = atof(value_holder);
amt_deposited += cheques[i].amount;
}
fwrite(cheques, s i z e o f ( s t r u c t cheque_deposited), number, fp);
printf("Amount deposited was: %.2f\n", amt_deposited);
printf("Which record number to be retrieved...?");
gets(value_holder);
rcrd_nmb = atoi(value_holder);
rewind(fp);
i f ((fseek(fp, ( long )
((rcrd_nmb - 1) * s i z e o f ( s t r u c t cheque_deposited)),
0)) == 0){
fread(&cheque_holder,
s i z e o f ( s t r u c t cheque_deposited), 1, fp);
printf("Cheque details are as under:\n");
printf("cheque issued by: ");
puts(cheque_holder.by_whom_given);
printf("Bank name: ");
puts(cheque_holder.bank);
printf("Local bank (Y/N): ");
puts(cheque_holder.local_or_not);
printf("Cheque number: ");
puts(cheque_holder.cheque_number);
printf("Cheque amount: ");
printf("%.2f\n", cheque_holder.amount);
}
else
printf("Sorry, unable to locate record %d\n",
rcrd_nmb);
fclose(fp);
133
Programming in C }
else
printf("Error opening passbook.dat for \
appending records...\n");
r e t u r n (0);
}
fseek ()
fseek() need three arguments: the first is a file pointer, the second, a long offset
value, which is the number of bytes the position pointer must move in order to home in
to the chosen byte; the third argument of fseek() specifies the origin from which
offset is measured: this can be 0, 1 or 2. 0 sets the file beginning as the origin of the
search, 1 sets the current position in the files as the search origin, and 2 sets the end of
the file as the search origin. fseek() returns a non-zero value on error.
i f ((fseek (fp, ( long )
((rcrd_nmb - 1) * s i z e o f ( s t r u c t cheque_deposited)), 0)) == 0)
etc.
Error checking is extremely important to any program that writes data to files. Program
10.13 validates a date read in as a struct via the function int valid_date(). Each
member of the structure any_date is passed to valid_date() as a separate
parameter. The function cannot alter the values of the arguments sent to it. The function
zeller() is invoked only after valid_date() confirms that the three date
parameters sent to it are good. As usual, it reports the day corresponding to the given
date. Note that it does so without the switch statement.
/* Program 10.13; file name: unit10-prog13.c */
# i n c l u d e <stdio.h>
# d e f i n e LEAP_YEAR(Year) (Year %4 == 0 && Year % 100 != 0)\
||Year % 400 == 0
s t r u c t date
{
i n t dd;
i n t mm;
i n t yy;
};
i n t valid_date( i n t D, i n t M, i n t Y);
i n t Zeller( i n t D, i n t M, i n t Y);
char *days[] =
{
"Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday",
"Saturday"
};
i n t main()
{
s t r u c t date any_date;
printf("This program reads a date in the \
format dd-mm-yyyy\n");
printf("and determines if its valid. \
If it is, it returns the\n");
printf("Corresponding day. Enter a date: ");
scanf("%d-%d-%d", &any_date.dd, &any_date.mm, &any_date.yy);
i f (valid_date(any_date.dd, any_date.mm, any_date.yy))
134 printf("That was a %s",
days[Zeller(any_date.dd, any_date.mm, Files and Structs, Unions
any_date.yy)]); and Bit-Fields
else
printf("Not a valid date.\n");
r e t u r n (0);
}
i n t valid_date( i n t dd, i n t mm, i n t yy)
{
i f (yy < 0)
r e t u r n 0;
i f (yy < 100)
yy += 1900;
i f (yy < 1582 || yy > 4902)
r e t u r n 0;
i f (!(LEAP_YEAR(yy)) && (mm == 2) && (dd > 28))
r e t u r n 0;
i f (!(LEAP_YEAR(yy)) && (mm == 2) && (dd > 29))
r e t u r n 0;
i f (mm < 1 || mm > 12)
r e t u r n 0;
i f (dd < 1 || dd > 31)
r e t u r n 0;
i f ((dd > 30)
&& (mm == 4 || mm == 6 || mm == 9 || mm == 11))
r e t u r n 0;
r e t u r n 1;
}
i n t Zeller( i n t DD, i n t MM, i n t YY)
{
i n t ZM, ZY;
i f (MM < 3)
ZM = MM + 10;
else
ZM = MM - 2;
i f (ZM > 10)
ZY = YY - 1;
else
ZY = YY;
r e t u r n ((( i n t ) ((13 * ZM - 1) / 5) + DD + ZY % 100 +
( i n t ) ((ZY % 100) / 4) - 2 * ( i n t ) (ZY / 100) +
( i n t ) (ZY / 400) + 77) % 7);
r e t u r n (0);
}
Instead of passing the members of a structure separately to a function (as was done in
the program above), one can pass a structure to a function in toto, provided that the
struct declaration is known to the function. As usual, if the values of the members are
changed in the function, then those changes are restricted to the function. Program
10.14 uses a function to implement complex multiplication. The function has two
structures for its parameters, each of type struct complex, and returns a value of the
same type.
/* Program 10.14; file name: unit10-prog14.c */
# i n c l u d e <stdio.h>
s t r u c t complex
{
f l o a t real;
f l o a t imaginary;
};
s t r u c t complex result ( s t r u c t complex val_1, s t r u c t complex val_2);
i n t main ( void )
{
s t r u c t complex num_1, num_2, product; 135
Programming in C printf("This program multiplies two complex numbers.\n");
printf("Enter real and imaginary parts of first number: ");
scanf("%f %f", &num_1.real, &num_1.imaginary);
printf("Enter real and imaginary parts of second number: ");
scanf("%f %f", &num_2.real, &num_2.imaginary);
product = result (num_1, num_2);
printf("Result: %f + i * %f\n", product.real,
product.imaginary);
r e t u r n (0);
}
s t r u c t complex result ( s t r u c t complex v1, s t r u c t complex v2)
{
s t r u c t complex answer;
answer.real = v1.real * v2.real
- v1.imaginary * v2.imaginary;
answer.imaginary = v1.real * v2.imaginary
+ v2.real * v1.imaginary;
r e t u r n answer;
}
Pointers to structures are declared in the same way as pointers to any other variable:
s t r u c t complex * pc;
pc is now a pointer to a structure of type complex. The address of a structure of this
type may be assigned to it.
pc = &num_1; (For ANSI C compliant compilers pc would now point to the first
member of num_1; for pre-ANSI C compilers this may not be true.) Structure pointers
may be passed to functions. This is often more convenient than passing (i.e. copying)
an entire structure into the function, but there is an inherent risk of unintended changes
to the structure. Within the function the real and imaginary parts of the complex
number referenced by pc are given by (*pc).real and(*pc).imaginary.
The parentheses are necessary. Though the “contents of” operator binds very tightly to
its operand, the membership operator has a higher priority. If the parentheses were
absent the order of evaluation would have been *(pc.real) and
* (pc.imaginary). But pc is not a struct , it’s a pointer. It doesn’t have members;
pc.real and pc.imaginary are therefore illegal constructs. n the other hand, the
notation (*pc).real is cumbersome: so C has as an alternative–the
arrow operator -> (which consists of a minus sign followed by the greater than
sign) for the member reference operator. The member reference:
pc->real has the same effect as: (*pc).real
Program 10.15 shows how the arrow operator is used. The program determines the
distance between two points (x1, y1) and (x2, y2). Each point is represented as a
structure, and the addresses of these structures are passed to the function distance().
The arrow operator is used to extract the value of each of the members–the x and y
co-ordinates–of the structures from their pointers. These values are used in
Pythagoras’s formula for the distance between (x1 , y1 ) and (x2 , y2 ).
/* Program 10.15; file name: unit10-prog15.c*/
# i n c l u d e <stdio.h>
# i n c l u d e <math.h>
s t r u c t point
{
f l o a t x;
f l o a t y;
};
f l o a t distance( s t r u c t point *p1, s t r u c t point *p2);
i n t main( void )
136 {
s t r u c t point p1, p2; Files and Structs, Unions
printf("This program determines the distance\n"); and Bit-Fields
printf("between two points in the X-Y plane.\n");
printf("Enter X and Y co-ordinates of first point: ");
scanf("%f %f", &p1.x, &p1.y);
printf("Enter X and Y co-ordinates of second point: ");
scanf("%f %f", &p2.x, &p2.y);
printf("Distance between (%f, %f) and (%f, %f) is: %f\n",
p1.x, p1.y, p2.x, p2.y, distance(&p1, &p2));
r e t u r n (0);
}
f l o a t distance( s t r u c t point *p1, s t r u c t point *p2)
{
r e t u r n (sqrt((p1->x - p2->x) * (p1->x - p2->x) +
(p1->y - p2->y) * (p1->y - p2->y)));
}
Here are some exercises for you.
In the next section, we will discuss another variable type called union which can hold
variables of different types.
10.7 UNIONS
A union is a C variable typed by the keyword union that can accommodate at any time
just one of its listed members, which may be objects of different types, i.e. of different
widths:
union hold_all
{
char alpha;
i n t beta;
f l o a t gamma;
double delta [3];
i n t * ptr;
}
This instances box_1, box_2 and box_3 of the union hold_all can hold variables
of each of its listed types. Obviously they will be wide enough to hold the biggest of its
members, in this case double delta [3]. Each member of a union is stored at the
same base address. Similarly to structures, the dot and arrow operators are used to
access any of the union’s members, via the mechanisms:
union_name.member_name;
as in:
box_1.alpha = ’x’;
box_2.beta = 3;
box_3.delta [0] = 3.14;
or union_pointer->member, as in:
x->gamma = 2.71 137
Programming in C where x is a pointer to a union variable, say box_1.
A union may be initialised by a value of the type of its first member. If a union has
been assigned a value of a certain type, then it is not correct to attempt to retrieve from
it a value of a different type. Program 10.16 and its output illustrate some properties of
unions. x is a pointer of type union hold_all, and is a parameter of the function
func() whose two other parameters are an int and a double. The union hold_all is
just wide enough to accommodate the three doubles of the array delta []. The
unions box_1, box_2 and box_3 are assigned a char, and int and a double value
respectively. func() is invoked with the following arguments: a pointer to box_1, and
the current values of box_2.beta and box_3.delta [0]. Within func() itself the
expression P->alpha reference the member alpha of box_1, which has the value
’x’. This is changed to ’y’.
/* Program 10.16 */
# i n c l u d e <stdio.h>
union hold_all
{
char alpha;
i n t beta;
f l o a t gamma;
double delta [3];
i n t * ptr;
} box_1, box_2, box_3, * x;
void func ( union hold_all *p, i n t q, double r);
i n t main ( void )
{
printf("The size of the union hold_all is: %d\n",
s i z e o f ( union hold_all));
printf("The size of an instance of it, box_1 is: %d\n",
s i z e o f box_1);
box_1.alpha = ’x’;
box_2.beta = 3;
box_3.delta [0] = 3.14;
printf("Can func () change box_1.alpha, \
which noe is: %c?\n", box_1.alpha);
printf("Let\’s see...sending \
control of func ()...\n");
func(&box_1, box_2.beta, box_3.delta[0]);
printf("Yes it can: box_1.alpha after func()\
is: %c\n", box_1.alpha);
r e t u r n (0);
}
void func ( union hold_all *P, i n t Q, double R)
{
P->alpha += R/Q;
}
In the next section, we will discuss how to manipulate the individual bits of values
138 integer variables.
Files and Structs, Unions
10.8 BIT FIELDS: THE BITWISE OPERATORS and Bit-Fields
In C there are two ways in which the individual bits within a word can be manipulated.
The first depends upon storing a value as an int and then to use the bitwise operators,
explained below, to mask or set specific bits of the word. The second uses the bit field
approach, in which the syntax of definition and the access method are based on
structures.
C has six bitwise operators, of which five are binary and one unary. They may be
applied to any singed or unsigned integer operands, char, short, int or long. The
unary operator ,̃ a tilde sign, one’s complements its operand, i.e. it changes the 1-bits to
0s, and vice versa. It associates from right to left. Suppose the int x stores the value-1
on a two’s complement machine. On such a machine this int value is represented by all
bits set to 1s. Then ~x will have all its bits set to 0s. Of the binary bitwise operators,
three are logical connectives: in order of decreasing priority they are:
The two shift operators << and >> push their left operand to the left or to the right,
respectively, a number of bit positions given by their right operand.
To illustrate how these operators are used, suppose a 16-bit int x has the octal value
012345, i.e. 0001010011100101 binary, and another 16-bit int y has the octal value
023456, i.e. 0010011100101110 binary, then x & y is produced by ANDing the
corresponding bits of x and y, as follows:
x 0001010011100101 octal 12345
y 0010011100101110 octal 23456
x & y 0000010000100100, i.e. octal 2044
Similarly, ORing the bits of x and y yields:
x 0001010011100101
y 0010011100101110
x | y 0011011111101111, i.e. octal 33757
The second way to manipulate bits within a word is by creating a field structure.
Groups of contiguous bits within a word are called a field. A field is assigned both a
name as well as a width, this being the number of bits in the field. The value stored
within a field must be an int -like quantity:
s t r u c t bitfields
{
unsigned x : 2;
unsigned y : 2;
unsigned z : 3;
unsigned z : 1;
} psw;
140 This defines a variable called psw which contains four unsigned bit fields.
The number following the colon is the field width. Field variables may be assigned Files and Structs, Unions
values; be careful however that the value assigned to a field is not greater than the and Bit-Fields
maximum storable inside it. Individual fields are referenced as though they were
structure members. The assignments:
psw.z = 5;
set the bits in the field z as 101. The signed or unsigned specification makes for
portability; this is important because bit fields are extremely implementation dependent.
For example, C does not specify whether fields must be stored left to right within a
word, or vice versa. Some compilers may not allow fields to cross a word boundary.
Unnamed fields may be used as fillers. In declaring the structure pc as follows we have
forced a 3 bit gap between the fields x and y:
struct
{
unsigned x : 2;
: 3;
unsigned y : 4;
} pc;
An unnamed field of width 0 will cause the next field to begin in the following word
instead of at the boundary of the last field. The fields within a word do not have
addresses. It is incorrect to use the & (address of) operator with them.
E10) Write a C language program to count the number of bits in your computer’s word.
E11) Write a C program to implement right or left rotation of bits within a word. n bits
(where n is a positive integer less than the word size) are pushed out to the right or
left, caught as they fall, and pushed in from the left or right respectively.
We now end our discussion by recalling what we have discussed so far in the next
section.
10.9 SUMMARY
In this Unit we have studied how disk files may be read and written by C programs. File
I/O functions are for the most part similar to the keyboard/monitor I/O functions that
we have already seen in the preceding Units; they are therefore particularly easy to use.
fopen()
fopen(string_pointer,options)
where the string_pointer is either the file name itself as a string(like
"password.dat" for example) or a pointer to a string containing the file
name. The options are given in Table. 1 on page 121.
fprintf()
fprintf(file pointer,control string,expressions)
This is similar to printf() but for the extra argument, the file pointer.
fscanf()
fscanf(file pointer,control string,expressions)
This is similar to scanf() but for the extra argument, the file pointer.
141
Programming in C sprintf() and sscanf()
sprintf(output_buffer, control_string, list);
sscanf(input_buffer, control_string, list);
are for reading strings writing output to strings.
rewind()
The function rewind(file pointer) makes the file pointer to point to
the beginning of the file.
feop()
The function feop(file pointer) can be used to check if the file pointer
is pointing to the end of the file.
fseek()
fseek(file pointer,offset,origin)
allows us to access any portion of the file. The first argument is the file pointer.
The third is the position in the file, 0 for the beginning, 1 for the current position
and 2 for the end of the file. The second is the offset from the origin specified
in argument 3.
Structs are a C facility that allow the storage of heterogeneous but related information.
A struct variable has members which can be accessed by the dot and arrow operations.
A union is a type of struct that can hold at any time just one of its members, which
may be of various types. It is similar to Pascal’s variant records.
It is possible to manipulate the bits within an int-like word. There are two ways by
which this may be accomplished. The first method depends upon the six bitwise
142 operators of C: The second uses the approach of bit fields, which are groups of
Files and Structs, Unions
Table 2: 6 bit operations. and Bit-Fields
Operator Action
x & y Finds the AND of x and y. In the result a bit
is 1 if and only if both the corresponding bits
in x and y are 1; otherwise it is zero.
x |y Finds the OR of x and y. In the result a bit is
0 if and only if both the corresponding bits in
x and y are 0; otherwise it is 1.
x ^y Finds the exlusive OR of x and y. In the the
result a bit is zero if and only if both the cor-
responding bits in x and y are the same.
x «n Pushes out the n left most bits and replaces
the right most n bits vacated bits by 1.
x » Pushes out the n right most bits. If x is an
unsigned int, it replaces the vacated bits by 0s.
If x is a signed int, the result is compiler de-
pendent; the vacated bets are replaced by 0s
or by 1s depending on whether the compiler
implements logical or arithmetical shifts.
contiguous bits within a word, and which can be assigned small integer values. Bit
fields are accessed like structure members.
10.10 SOLUTIONS/ANSWERS
E1) See the program in Listing 1. This is just a modified version of the program in
page 20 of Kernighan and Ritchie’s book, second edition. Create a small file to
test the program and work through the program to find what happens when your
file is processed by the programme.
# i n c l u d e <stdio.h>
# i n c l u d e <stdlib.h>
# i n c l u d e <string.h>
# d e f i n e IN 1 /*inside a word.*/
# d e f i n e OUT 0 /*outside a word.*/
i n t main( i n t argc, char *argv[])
{
FILE *file;
char name[20];
i n t c, nl, nw, nc, state;
nl=nw=nc=0;
state = OUT;
strcpy(name,argv[1]);
printf("File name is %s.\n",name);
i f ((file=fopen(name,"r")) == NULL){
printf("Unable to open file.");
exit(1);
}
else{
w h i l e ((c=getc(file)) != EOF){
++nc;
i f (c == ’\n’)
++nl;
i f (c == ’ ’ || c == ’\n’ || c == ’\t’)
state = OUT;
e l s e i f (state == OUT){
state = IN; 143
Programming in C ++nw;
}
}
}
printf("%d %d %d\n", nl, nw, nc);
r e t u r n (0);
}
Listing 1: Solution to exercise 1.
E8) The program is given in Listing 2. As in the program to count the number of lines,
words and characters in a file, create a small example file and work through it.
/*Answer to exercise 8; file name:unit10-ans-ex-8.c*/
# i n c l u d e <stdio.h>
# d e f i n e MAXSIZE 100
i n t main( void )
{
char fname[MAXSIZE];
FILE *fp;
fprintf(stderr, "\nInput a file name:");
scanf("%s",fname);
fp = fopen(fname,"r");
fseek(fp,0,2);/*Go to the end of the file.*/
w h i l e (fseek(fp,-1,1) == 0){
/*move back by a character if possible.*/
putchar(getc(fp));
/*Move forward one character and print it.*/
fseek(fp, -1, 1);/*Move back on character.*/
}
printf("\n");
r e t u r n (0);
}
Listing 2: Solution to exercise 8.
144