C Programming For Beginners (2015)
CHAPTER 5
Programs with Repetition Logic
In this chapter, we will explain the following:
§ How to use the while construct to perform ‘looping’ in a program
§ How to find the sum and average of an arbitrary set of numbers
§ How to get a program to ‘count’
§ How to find the largest and smallest of an arbitrary set of numbers
§ How to read data from a file
§ How to write output to a file
§ How to use the for construct to perform ‘looping’ in a program
§ How to produce tables using for
5.1 Introduction
In Chapter 3, we showed you how to write programs using sequence logic—programs whose statements are executed “in sequence” from the first to the last.
In Chapter 4, we showed you how to write programs for problems which require selection logic. These programs used the if and the if...else statements.
In this chapter, we discuss problems which require repetition logic. The idea is to write statements once and get the computer to execute them repeatedly as long as some condition is true. We will see how to express repetition logic using the while and for statements.
5.2 The while Construct
Consider the problem of writing a program to find the sum of some numbers which the user enters one at a time. The program will prompt the user to enter numbers as follows:
Enter a number: 13
Enter a number: 8
Enter a number: 16
and so on. We want to let the user enter as many numbers as he wishes. Since we can have no idea how many that will be, and the amount could vary from one run of the program to the next, we must let the user ‘tell’ us when he wishes to stop entering numbers.
How does he ‘tell’ us? Well, the only time the user ‘talks’ to the program is when he types a number in response to the prompt. If he wishes to stop entering numbers, he can enter some ‘agreed upon’ value; when the program reads this value, it will know that the user wishes to stop.
In this example, we can use 0 as the value which tells the program that the user wishes to stop. When a value is used this way, it is referred to as a sentinel or end-of-data value. It is sometimes called a rogue value—the value is not to be taken as one of the actual data values.
What can we use as a sentinel value? Any value that cannot be confused with an actual data value would be okay. For example, if the data values are all positive numbers, we can use 0 or -1 as the sentinel value. When we prompt the user, it is a good idea to remind him what value to use as the sentinel value.
Assume we want the program to run as follows:
Enter a number (0 to end): 24
Enter a number (0 to end): 13
Enter a number (0 to end): 55
Enter a number (0 to end): 32
Enter a number (0 to end): 19
Enter a number (0 to end): 0
The sum is 143
How do we get the program to run like that? We want to be able to express the following logic in a form the computer could understand:
As long as the user does not enter 0, keep prompting him for another number and add it to the sum
It seems obvious that we must, at least, prompt him for the first number. If this number is 0, we must print the sum (which, of course, would be 0 at this time). If the number is not 0, we must add it to the sum and prompt for another number. If this number is 0, we must print the sum. If this number is not 0, we must add it to the sum and prompt for another number. If this number is 0..., and so on.
The process will come to an end when the user enters 0.
This logic is expressed quite neatly using a while construct (also called a while statement or while loop):
//Algorithm for finding sum
set sum to 0
get a number, num
while num is not 0 do
add num to sum
get another number, num
endwhile
print sum
Note, particularly, that we get a number before we enter the while loop. This is to ensure that the while condition makes sense the first time. (It would not make sense if num had no value.)
To find the sum, we need to:
§ Choose a variable to hold the sum; we will use sum.
§ Initialize sum to 0 (before the while loop).
§ Add a number to sum (inside the while loop). One number is added each time through the loop.
On exit from the loop, sum contains the sum of all the numbers entered.
The while construct lets us execute one or more statements repeatedly as long as some condition is true. Here, the two statements
add num to sum
get another number, num
are executed repeatedly as long as the condition num is not 0 is true.
In pseudocode, the while construct is usually written as follows:
while <condition> do
statements to be executed repeatedly
endwhile
The statements to be executed repeatedly are called the body of the while construct (or, simply, the body of the loop). The construct is executed as follows:
1. <condition> is tested.
2. If true, the body is executed and we go back to step 1; if false, we continue with the statement, if any, after endwhile.
We now show how the algorithm is executed using the sample data entered above. For easy reference, the data was entered in the following order:
24 13 55 32 19 0
Initially, num is undefined and sum is 0. We show this as follows:
num |
sum |
0 |
24 is entered and stored in num;
num is not 0 so we enter the while loop;
num (24) is added to sum (0), giving:
num |
24 |
sum |
24 |
13 is entered and stored in num;
num is not 0 so we enter the while loop;
num (13) is added to sum (24), giving:
num |
13 |
sum |
37 |
55 is entered and stored in num;
num is not 0 so we enter the while loop;
num (55) is added to sum (37), giving:
num |
55 |
sum |
92 |
32 is entered and stored in num;
num is not 0 so we enter the while loop;
num (32) is added to sum (92), giving:
num |
32 |
sum |
124 |
19 is entered and stored in num;
num is not 0 so we enter the while loop;
num (19) is added to sum (124), giving:
num |
19 |
sum |
143 |
0 is entered and stored in num;
num is 0 so we exit the while loop and go to print sum with
num |
0 |
sum |
143 |
sum is now 143 so the algorithm prints 143.
When a while construct is being executed, we say the program is looping or the while loop is being executed.
It remains to show how to express this algorithm in C. Program P5.1 shows how.
Program P5.1
//print the sum of several numbers entered by a user
#include <stdio.h>
int main() {
int num, sum = 0;
printf("Enter a number (0 to end): ");
scanf("%d", &num);
while (num != 0) {
sum = sum + num;
printf("Enter a number (0 to end): ");
scanf("%d", &num);
}
printf("\nThe sum is %d\n", sum);
}
Of particular interest is the while statement. The pseudocode
while num is not 0 do
add num to sum
get another number, num
endwhile
is expressed in C as
while (num != 0) {
sum = sum + num;
printf("Enter a number (0 to end): ");
scanf("%d", &num);
}
When the program is run, what would happen if the very first number entered was 0? Since num is 0, the while condition is immediately false so we drop out of the while loop and continue with the printf statement. The program will print the correct answer:
The sum is 0
In general, if the while condition is false the first time it is tested, the body is not executed at all.
Formally, the while construct in C is defined as follows:
while (<condition>) <statement>
The word while and the brackets are required. You must supply <condition> and <statement>. <statement> must be a single statement or a block—one or more statements enclosed by { and }. First, <condition> is tested; if true, <statement> is executed and <condition> is tested again. This is repeated until <condition> becomes false; when this happens, execution continues with the statement, if any, after <statement>. If <condition> is false the first time, <statement> is not executed and execution continues with the following statement, if any.
In Program P5.1, <condition> is num != 0 and <statement> is the block
{
sum = sum + num;
printf("Enter a number (0 to end): ");
scanf("%d", &num);
}
Whenever we want to execute several statements if <condition> is true, we must enclose the statements by { and }. Effectively, this makes them into one statement, a compound statement, satisfying C’s syntax rule which requires one statement as the body.
5.2.1 Highest Common Factor
Let us write a program to find the highest common factor, HCF, (also called the greatest common divisor, GCD) of two numbers. The program will run as follows:
Enter two numbers: 42 24
Their HCF is 6
We will use Euclid’s algorithm for finding the HCF of two integers, m and n. The algorithm is as follows:
1. if n is 0, the HCF is m and stop
2. set r to the remainder when m is divided by n
3. set m to n
4. set n to r
5. go to step 1
Using m as 42 and n as 24, step through the algorithm and verify that it gives the correct answer, 6.
Steps 2, 3 and 4 are executed as long as n is not 0. Hence, this algorithm can be expressed using a while loop as follows:
while n is not 0 do
set r to m % n
set m to n
set n to r
endwhile
HCF is m
We can now write Program P5.2 which finds the HCF of two numbers entered.
Program P5.2
//find the HCF of two numbers entered by a user
#include <stdio.h>
int main() {
int m, n, r;
printf("Enter two numbers: ");
scanf("%d %d", &m, &n);
while (n != 0) {
r = m % n;
m = n;
n = r;
}
printf("\nTheir HCF is %d\n", m);
}
Note that the while condition is n != 0 and the while body is the block
{
r = m % n;
m = n;
n = r;
}
The algorithm and, hence, the program, works whether m is bigger than n or not. Using the example above, if m is 24 and n is 42, when the loop is executed the first time, it will set m to 42 and n to 24. In general, if m is smaller than n, the first thing the algorithm does is swap their values.
5.3 Keep a Count
Program P5.1 finds the sum of a set of numbers entered. Suppose we want to count how many numbers were entered, not counting the end-of-data 0. We could use an integer variable n to hold the count. To get the program to keep a count, we need to do the following:
§ Choose a variable to hold the count; we choose n.
§ Initialize n to 0.
§ Add 1 to n in the appropriate place. Here, we need to add 1 to n each time the user enters a non-zero number.
§ Print the count.
Program P5.3 is the modified program for counting the numbers.
Program P5.3
//print the sum and count of several numbers entered by a user
#include <stdio.h>
int main() {
int num, sum = 0, n = 0;
printf("Enter a number (0 to end): ");
scanf("%d", &num);
while (num != 0) {
n = n + 1;
sum = sum + num;
printf("Enter a number (0 to end): ");
scanf("%d", &num);
}
printf("\n%d numbers were entered\n", n);
printf("The sum is %d\n", sum);
}
The following is a sample run of the program:
Enter a number (0 to end): 24
Enter a number (0 to end): 13
Enter a number (0 to end): 55
Enter a number (0 to end): 32
Enter a number (0 to end): 19
Enter a number (0 to end): 0
5 numbers were entered
The sum is 143
Comments on Program P5.3
§ We declare and initialize n and sum to 0 before the while loop.
§ The statement
§ n = n + 1;
§ adds 1 to n. We say n is incremented by 1. Suppose n has the value 3.
§ When the right hand side is evaluated, the value obtained is 3 + 1 = 4. This value is stored in the variable on the left hand side, i.e. n. The net result is that 4 is stored in n.
§ This statement is placed inside the loop so that n is incremented each time the loop body is executed. Since the loop body is executed when num is not 0, the value of n is always the amount of numbers entered so far.
§ When we exit the while loop, the value in n will be the amount of numbers entered, not counting 0. This value is then printed.
§ Observe that if the first number entered were 0, the while condition would be immediately false and control will go directly to the first printf statement after the loop with n and sum both having the value 0. The program will print, correctly:
§ 0 numbers were entered
§ The sum is 0
§ If one number is entered, the program will print "1 numbers were entered"—not very good English. Use an if statement to fix this.
5.3.1 Find Average
Program P5.3 can be easily modified to find the average of the numbers entered. As we saw above, on exit from the while loop, we know the sum (sum) and how many numbers were entered (n). We can add a printf statement to print the average to 2 decimal places, say, like this:
printf("The average is %3.2f\n", (double) sum/n);
For the data in the sample run, the output will be
5 numbers were entered
The sum is 143
The average is 28.60
As explained in Section 2.5.4, note the use of the cast (double) to force a floating-point calculation. Without it, since sum and n are int, an integer division would be performed, giving 28.
Alternatively, we could declare sum as double, and print the sum and average with this:
printf("The sum is %3.0f\n", sum);
printf("The average is %3.2f\n", sum/n);
However, there is still a problem. If the user enters 0 as the first number, execution will reach the last printf statement with sum and n both having the value 0. The program will attempt to divide 0 by 0, giving the error “Attempt to divide by 0”. This is an example of a run-time (or execution) error.
To cater for this situation, we could use the following after the while loop:
if (n == 0) printf("\nNo numbers entered\n");
else {
printf("\n%d numbers were entered\n", n);
printf("The sum is %d\n", sum);
printf("The average is %3.2f\n", (double) sum/n);
}
The moral of the story is that, whenever possible, you should try to anticipate the ways in which your program might fail and cater for them. This is an example of what is called defensive programming.
5.4 Increment and Decrement Operators
There are a number of operators that originated with C and give C its unique flavour. The best known of these is the increment operator, ++. In the last program, we used
n = n + 1;
to add 1 to n. The statement
n++;
does the same thing. The operator ++ adds 1 to its argument, which must be a variable. It can be written as a prefix (++n) or as a suffix (n++).
Even though ++n and n++ both add 1 to n, in certain situations, the side-effect of ++n is different from n++. This is so because ++n increments n before using its value, whereas n++ increments n after using its value. As an example, suppose n has the value 7. The statement
a = ++n;
first increments n and then assigns the value (8) to a. But the statement
a = n++;
first assigns the value 7 to a and then increments n to 8. In both cases, though, the end result is that n is assigned the value 8.
As an exercise, what is printed by the following?
n = 5;
printf("Suffix: %d\n", n++);
printf("Prefix: %d\n", ++n);
The decrement operator -- is similar to ++ except that it subtracts 1 from its variable argument. For example, --n and n-- are both equivalent to
n = n - 1;
As explained above, --n subtracts 1 and then uses the value of n; n-- uses the value of n and then subtracts 1 from it. It would be useful to do the above exercise with ++ replaced by --.
5.5 Assignment Operators
So far, we have used the assignment operator, =, to assign the value of an expression to a variable, as in the following:
c = a + b
The entire construct consisting of the variable, = and the expression is referred to as an assignment expression. When the expression is followed by a semicolon, it becomes an assignment statement. The value of an assignment expression is simply the value assigned to the variable. For example, if a is 15 and b is 20, then the assignment expression
c = a + b
assigns the value 35 to c. The value of the (entire) assignment expression is also 35.
Multiple assignments are possible, as in
a = b = c = 13
The operator = evaluates from right to left, so the above is equivalent to
a = (b = (c = 13))
The rightmost assignment is done first, followed by the one to the left, and so on.
C provides other assignment operators, of which += is the most widely used. In Program P5.3, above, we used the statement
sum = sum + num;
to add the value of num to sum. This can be written more neatly using += as:
sum += num; //add num to sum
To add 3 to n, we could write
n += 3
which is the same as
n = n + 3
Other assignment operators include -=, *=, /= and %=. If op represents any of +, -, *, / or %, then
variable op= expression
is equivalent to
variable = variable op expression
We point out that we could write all our programs without using increment, decrement or the special assignment operators. However, sometimes, they permit us to express certain operations more concisely, more conveniently and, possibly, more clearly.
5.6 Find Largest
Suppose we want to write a program which works as follows: the user will type some numbers and the program will find the largest number typed. The following is a sample run of the program (underlined items are typed by the user):
Enter a number (0 to end): 36
Enter a number (0 to end): 17
Enter a number (0 to end): 43
Enter a number (0 to end): 52
Enter a number (0 to end): 50
Enter a number (0 to end): 0
The largest is 52
The user will be prompted to enter numbers, one at a time. We will assume that the numbers entered are all positive integers. We will let the user enter as many numbers as she likes. However, in this case, she will need to tell the program when she wishes to stop entering numbers. To do so, she will type 0.
Finding the largest number involves the following steps:
§ Choose a variable to hold the largest number; we choose bigNum.
§ Initialize bigNum to a very small value. The value chosen should be such that no matter what number is entered, its value would be greater than this initial value. Since we are assuming that the numbers entered would be positive, we can initialize bigNum to 0.
§ As each number (num, say) is entered, it is compared with bigNum; if num is greater than bigNum, then we have a bigger number and bigNum is set to this new number.
§ When all the numbers have been entered and checked, bigNum will contain the largest one.
These ideas are expressed in the following algorithm:
set bigNum to 0
get a number, num
while num is not 0 do
if num is bigger than bigNum, set bigNum to num
get a number, num
endwhile
print bigNum
Like before, we get the first number before we enter the while loop. This is to ensure that the while condition makes sense (is defined) the first time. It would not make sense if num had no value. If it is not 0, we enter the loop. Inside the loop, we process the number (compare it with bigNum, etc.) after which we get another number. This number is then used in the next test of the while condition. When the while condition is false (num is 0), the program continues with the print statement after the loop.
This algorithm is implemented as shown in Program P5.4.
Program P5.4
//find the largest of a set of numbers entered
#include <stdio.h>
int main() {
int num, bigNum = 0;
printf("Enter a number (0 to end): ");
scanf("%d", &num);
while (num != 0) {
if (num > bigNum) bigNum = num; //is this number bigger?
printf("Enter a number (0 to end): ");
scanf("%d", &num);
}
printf("\nThe largest is %d\n", bigNum);
}
Let us ‘step through’ this program using the sample data entered at the beginning of this section. For easy reference, the data was entered in the following order:
36 17 43 52 50 0
Initially, num is undefined and bigNum is 0. We show this as:
num |
bignum |
0 |
36 is entered and stored in num;
num is not 0 so we enter the while loop;
num (36) is compared with bigNum (0);
36 is bigger so bigNum is set to 36, giving:
num |
36 |
bignum |
36 |
17 is entered and stored in num;
num is not 0 so we enter the while loop;
num (17) is compared with bigNum (36);
17 is not bigger so bigNum remains at 36, giving:
num |
17 |
bignum |
36 |
43 is entered and stored in num;
num is not 0 so we enter the while loop;
num (43) is compared with bigNum (36);
43 is bigger so bigNum is set to 43, giving:
num |
43 |
bignum |
43 |
52 is entered and stored in num;
num is not 0 so we enter the while loop;
num (52) is compared with bigNum (43);
52 is bigger so bigNum is set to 52, giving:
num |
52 |
bignum |
52 |
50 is entered and stored in num;
num is not 0 so we enter the while loop;
num (50) is compared with bigNum (52);
50 is not bigger so bigNum remains at 52, giving:
num |
50 |
bignum |
52 |
0 is entered and stored in num;
num is 0 so we exit the while loop and go to printf with
num |
0 |
bignum |
52 |
bigNum is now 52 and the printf statement prints
The largest is 52
5.7 Find Smallest
In addition to finding the largest of a set of items, we are sometimes interested in finding the smallest. We will find the smallest of a set of integers. To do so involves the following steps:
§ Choose a variable to hold the smallest number; we choose smallNum.
§ Initialize smallNum to a very big value. The value chosen should be such that no matter what number is entered, its value would be smaller than this initial value. If we have an idea of the numbers we will get, we can choose an appropriate value.
§ For instance, if we know that the numbers will contain at most 4 digits, we can use an initial value such as 10000. If we do not know this, we can set smallNum to the largest integer value defined by the compiler (32767 for 16-bit integers). Similarly, when we are finding the largest, we can initialize bigNum (say) to a very small number like -32767.
§ Another possibility is to read the first number and set smallNum (or bigNum) to it, provided it is not 0. For variety, we will illustrate this method.
§ As each number (num, say) is entered, it is compared with smallNum; if num is smaller than smallNum, then we have a smaller number and smallNum is set to this new number.
§ When all the numbers have been entered and checked, smallNum will contain the smallest one.
These ideas are expressed in the following algorithm:
get a number, num
if num is 0 then stop //do nothing and halt the program
set smallNum to num
while num is not 0 do
if num is smaller than smallNum, set smallNum to num
get a number, num
endwhile
print smallNum
The first number is read. If it is 0, there is nothing to do and the program halts. If it is not 0, we set smallNum to it. We could, at this stage, get another number before we execute the while statement. However, in the interest of brevity, we don't. The penalty for this is that, even though we know that num is not 0 and it is not smaller than smallNum (it is the same), we still do these tests before getting the next number.
This algorithm is implemented as shown in Program P5.5.
Program P5.5
//find the smallest of a set of numbers entered
#include <stdio.h>
int main() {
int num;
printf("Enter a number (0 to end): ");
scanf("%d", &num);
if (num == 0) return; //halt the program
int smallNum = num;
while (num != 0) {
if (num < smallNum) smallNum = num;
printf("Enter a number (0 to end): ");
scanf("%d", &num);
}
printf("\nThe smallest is %d\n", smallNum);
}
In C, the keyword return can be used in main to halt the program by "returning" to the operating system. We will discuss return in more detail in Chapter 7.
When run, if numbers are entered in the following order:
36 17 43 52 50 0
the program will print
The smallest is 17
and if the numbers entered are
36 -17 43 -52 50 0
the program will print
The smallest is -52
5.8 Read Data from a File
So far, we have written our programs assuming that data to be supplied is typed at the keyboard. We have fetched the data using scanf for reading numbers and gets for reading strings. Typically, the program prompts the user for the data and waits for the user to type the data. When the data is typed, the program reads it, stores it in a variable (or variables) and continues with its execution. This mode of supplying data is called interactive since the user is interacting with the program.
We say we have been reading data from the “standard input”. C uses the predefined identifier stdin to refer to the standard input. When your program starts up, C assumes that stdin refers to the keyboard. Similarly, the predefined identifier stdout refers to the standard output, the screen. So far, our programs have written output to the screen.
We can also supply data to a program by storing the data in a file. When the program needs data, it fetches it directly from the file, without user intervention. Of course, we have to ensure that the appropriate data has been stored in the file in the correct order and format. This mode of supplying data is normally referred to as batch mode. (The term batch is historical and comes from the old days when data had to be ‘batched’ before being submitted for processing.)
For example, suppose we need to supply an item number (int) and a price (double) for several items. If the program is written assuming that the data file contains several pairs of numbers (an int constant followed by a doubleconstant) then we must ensure that the data in the file conforms to this.
Suppose we create a file called input.txt and type data in it. This file is a file of characters or a text file. Depending on the programming environment provided by your C compiler, it may be possible to assign stdin to input.txt—we say redirect the standard input to input.txt. Once this is done, your program will read data from the file rather than the keyboard. Similarly, it may be possible to redirect the standard output to a file, output.txt, say. If done, your printf’s will write output to the file, rather than the screen.
We will take a slightly different approach, which is a bit more general since it will work with any C program and does not depend on the particular compiler or operating system you happen to be using.
Suppose we want to be able to read data from the file input.txt. The first thing we need to do is declare an identifier called a “file pointer”. This can be done with the statement
FILE * in; // read as "file pointer in"
The word FILE must be spelt as shown, with all uppercase letters. The spaces before and after * may be omitted. So you could write FILE* in, FILE *in or even FILE*in. We have used the identifier in; any other will do, such as inf, infile, inputFile, payData.
The second thing we must do is associate the file pointer in with the file input.txt and tell C we will be reading data from the file. This is done using the function fopen, as follows:
in = fopen("input.txt", "r");
This tells C to “open the file input.txt for reading”: "r" indicates reading. (We will use "w" if we want the file to be opened for “writing”, that is, to receive output.) If we wish, we could accomplish both things with one statement, thus:
FILE * in = fopen("input.txt", "r");
Once this is done, the “data pointer” will be positioned at the beginning of the file. We can now write statements which will read data from the file. We will see how shortly.
It is up to us to ensure that the file exists and contains the appropriate data. If not, we will get an error message such as “File not found”. If we need to, we can specify the path to the file.
Suppose the file is located at C:\testdata\input.txt.
We can tell C we will be reading data from the file with this:
FILE * in = fopen("C:\\testdata\\input.txt", "r");
Recall that the escape sequence \\ is used to represent \ within a string. If the file is on a flash drive with assigned letter E, we can use:
FILE * in = fopen("E:\\input.txt", "r");
5.8.1 fscanf
We use the statement (more precisely, the function) fscanf to read data from the file. It is used in exactly the same way as scanf except that the first argument is the file pointer in. For example, if num is int, the statement
fscanf(in, "%d", &num);
will read an integer from the file input.txt (the one associated with in) and store it in num. Note that the first argument is the file pointer and not the name of the file.
When we have finished reading data from the file, we should close it. This is done with fclose, as follows:
fclose(in);
There is one argument, the file pointer (not the name of the file). This statement breaks the association of the file pointer in with the file input.txt. If we need to, we could now link the identifier in with another file (paydata.txt, say) using:
in = fopen("paydata.txt", "r");
Note that we do not repeat the FILE * part of the declaration, since in has already been declared as FILE *. Subsequent fscanf(in, ...) statements will read data from the file paydata.txt.
5.8.2 Find Average of Numbers in a File
To illustrate the use of fscanf, let us re-write Program P5.3 to read several numbers from a file and find their average. Previously, we discussed how to find the average. We just need to make the changes to read the numbers from a file. Suppose the file is called input.txt and contains several positive integers with 0 indicating the end, for example,
24 13 55 32 19 0
Program P5.6 shows how to define the file as the place from which the data will be read and how to find the average.
Program P5.6
//read numbers from a file and find their average; 0 ends the data
#include <stdio.h>
int main() {
FILE * in = fopen("input.txt", "r");
int num, sum = 0, n = 0;
fscanf(in, "%d", &num);
while (num != 0) {
n = n + 1;
sum = sum + num;
fscanf(in, "%d", &num);
}
if (n == 0) printf("\nNo numbers supplied\n");
else {
if (n == 1) printf("\n1 number supplied\n");
else printf("\n%d numbers supplied\n", n);
printf("The sum is %d\n", sum);
printf("The average is %3.2f\n", (double) sum/n);
}
fclose(in);
}
Comments on Program P5.6
§ FILE * and fopen are used so that the fscanf statement would fetch data from the file input.txt.
§ Since the data is being read directly from the file, the question of prompting for data does not arise. The printf statements which prompted for data are no longer necessary.
§ The program makes sure that n is not 0 before attempting to find the average.
§ When run, the program reads the data from the file and prints the results without any user intervention.
§ If the data file contains
§ 24 13 55 32 19 0
§ the output will be
§ 5 numbers were supplied
§ The sum is 143
§ The average is 28.60
§ The numbers in the file could be supplied in “free format”—any amount could be put on a line. For example, the sample data could have been typed on one line as above or as follows:
§ 24 13
§ 55 32
§ 19 0
§ or like this:
§ 24 13
§ 55
§ 32 19
§ 0
§ or like this:
§ 24
§ 13
§ 55
§ 32
§ 19
§ 0
§ As an exercise, add statements to the program so that it also prints the largest and smallest numbers in the file.
File cannot be found: when you try to run this program, it may not run properly because it cannot find the file input.txt. This may be because the compiler is looking for the file in the wrong place. Some compilers expect to find the file in the same folder/directory as the program file. Others expect to find it in the same folder/directory as the compiler. Try placing input.txt in each of these folders, in turn, and run the program. If this does not work then you will need to specify the complete path to the file in the fopen statement. For example, if the file is in the folder data which is in the folder CS10E which is on the C: drive, you will need to use the following statement:
FILE * in = fopen("C:\\CS10E\\data\\input.txt", "r");
5.9 Send Output to a File
So far, our programs have read data from the standard input (the keyboard) and sent output to the standard output (the screen). We have just seen how to read data from a file. We now show you how you can send output to a file.
This is important because when we send output to the screen, it is lost when we exit the program or when we switch off the computer. If we need to save our output, we must write it to a file. Then the output is available as long as we wish to keep the file.
The process is similar to reading from a file. We must declare a “file pointer” (we use out) and associate it with the actual file (output.txt, say) using fopen. This can be done with
FILE * out = fopen("output.txt", "w");
This tells C to “open the file output.txt for writing”; "w" indicates writing. When this statement is executed, the file output.txt is created if it does not already exist. If it exists, its contents are destroyed. In other words, whatever you write to the file will replace its original contents. Be careful that you do not open for writing a file whose contents you wish to keep.
5.9.1 fprintf
We use the statement (more precisely, the function) fprintf to send output to the file. It is used in exactly the same way as printf except that the first argument is the file pointer out. For example, if sum is int with value 143, the statement
fprintf(out, "The sum is %d\n", sum);
will write
The sum is 143
to the file output.txt.
Note that the first argument is the file pointer and not the name of the file.
When we have finished writing output to the file, we must close it. This is especially important for output files since, the way some compilers operate, this is the only way to ensure that all output is sent to the file. (For instance, they send output to a temporary buffer in memory; only when the buffer is full is it sent to the file. If you do not close the file, some output may be left in the buffer and never sent to the file.) We close the file with fclose, as follows:
fclose(out);
There is one argument, the file pointer (not the name of the file). This statement breaks the association of the file pointer out with the file output.txt. If we need to, we could now link the identifier out with another file (payroll.txt, say) using:
out = fopen("payroll.txt", "w");
Note that we do not repeat the FILE * part of the declaration, since out has already been declared as FILE *. Subsequent fprintf(out, ...) statements will send output to the file payroll.txt.
For an example, we re-write Program P5.6 as Program P5.7 by adding the fopen and fprintf statements. The only difference is that P5.6 sends its output to the screen while P5.7 sends its output to the file output.txt.
Program P5.7
//read numbers from a file and find their average; 0 ends the data
#include <stdio.h>
int main() {
FILE * in = fopen("input.txt", "r");
FILE * out = fopen("output.txt", "w");
int num, sum = 0, n = 0;
fscanf(in, "%d", &num);
while (num != 0) {
n = n + 1;
sum = sum + num;
fscanf(in, "%d", &num);
}
if (n == 0) fprintf(out, "No numbers entered\n");
else {
fprintf(out, "%d numbers were entered\n", n);
fprintf(out, "The sum is %d\n", sum);
fprintf(out, "The average is %3.2f\n", (double) sum/n);
}
fclose(in);
fclose(out);
}
As explained in Section 5.7, you can, if you wish, specify the complete path to your file in the fopen statement. For instance, if you want to send the output to the folder Results on a flash drive (with assigned letter F), you can use
FILE * out = fopen("F:\\Results\\output.txt", "w");
When you run Program P5.7, it will appear as if nothing has happened. However, if you check your file system, using the file path you specified, you will find the file output.txt. Open it to view your results.
5.10 Payroll
So far, our programs have read data from the standard input (the keyboard) and sent output to the standard output (the screen). We have just seen how to read data from a file. We now show you how you can send output to a file.
The data for each employee consists of a first name, a last name, the number of hours worked and the rate of pay. The data will be stored in a file paydata.txt and output will be sent to the file payroll.txt.
In order to show you another way to read a string, we will assume that the data is stored in the file as follows:
Maggie May 50 12.00
Akira Kanda 40 15.00
Richard Singh 48 20.00
Jamie Barath 30 18.00
END
We use the “first name” END as the end-of-data marker.
Regular pay, overtime pay and net pay will be calculated as described in Section 4.3.1. The employee name, hours worked, rate of pay, regular pay, overtime pay and net pay are printed under a suitable heading. In addition, we will write the program to do the following:
§ Count how many employees are processed.
§ Calculate the total wage bill (total net pay for all employees).
§ Determine which employee earned the highest pay and how much. We will ignore the possibility of a tie.
For the sample data, the output should look like this:
Name |
Hours |
Rate |
Regular |
Overtime |
Net |
|
Maggie May |
50.0 |
12.00 |
480.0 |
180.00 |
660.00 |
|
Akira Kanda |
40.0 |
15.00 |
600.0 |
0.00 |
600.00 |
|
Richard Singh |
48.0 |
20.00 |
800.0 |
240.00 |
1040.00 |
|
Jamie Barath |
30.0 |
18.00 |
540.0 |
0.00 |
540.00 |
|
Number of employees: 4 |
||||||
Total wage bill: $2840.00 |
||||||
Richard Singh earned the most pay of $1040.00 |
An outline of the algorithm for reading the data is as follows:
read firstName
while firstName is not "END" do
read lastName, hours, rate
do the calculations
print results for this employee
read firstName
endwhile
We will use the specification %s in fscanf for reading the names. Suppose we have declared firstName as
char firstName[20];
We can read a string into firstName with the statement
fscanf(in, "%s", firstName);
The specification %s must be matched with a character array, like firstName. As mentioned in Section 3.3, when an array name is an argument to scanf (or fscanf), we must not write & before it.
%s is used for reading a string of characters not containing any whitespace characters. Beginning with the next non-whitespace character, characters are stored in firstName until the next whitespace character is encountered. It is up to us to make sure that the array is big enough to hold the string.
Because a whitespace character ends the reading of a string, %s cannot be used to read a string containing blanks. For this reason, we will use separate variables for first name (firstName) and last name (lastName).
For example, suppose the next piece of data contains (◊ denotes a space):
◊◊◊Robin◊◊◊◊Hood◊◊
The statement
fscanf(in, "%s", firstName);
will skip over spaces until it reaches the first non-whitespace character R. Starting with R, it stores characters in firstName until it reaches the next space, the one after n. Reading stops and Robin is stored in firstName. The data pointer is positioned at the space after n. If we now execute
fscanf(in, "%s", lastName);
fscanf will skip over spaces until it reaches H. Starting with H, it stores characters in lastName until it reaches the space after d. Reading stops and Hood is stored in lastName. If d were the last character on the line, the end-of-line character (which is whitespace) would have stopped the reading.
Because of the way %s works, we will need to read the first and last names separately. However, in order to get the output to line up neatly as shown on the previous page, it would be more convenient to have the entire name stored in one variable (name, say). Suppose Robin is stored in firstName and Hood is stored in lastName. We will copy firstName to name with
strcpy(name, firstName);
We will then add a space with
strcat(name, " ");
strcat is a predefined string function which allows us to join (concatenate) two strings. It stands for “string concatenation”. If s1 and s2 are strings, strcat(s1, s2) will add s2 to the end of s1. It assumes that s1 is big enough to hold the joined strings.
We will then add lastName with
strcat(name, lastName);
Using our example, at the end of all this, name will contain Robin Hood.
In our program, we will use the specification %-15s to print name. This will print name left-justified in a field width of 15. In other words, all names will be printed using 15 print columns. This is necessary for the output to line up neatly. To cater for longer names, you can increase the field width.
To use the string functions, we must write the directive
#include <string.h>
at the head of our program if we want to use the string functions supplied by C.
Our program will need to check if the value in firstName is the string "END". Ideally, we would like to say something like
while (firstName != "END") { //cannot write this in C
but we cannot do so since C does not allow us to compare strings using the relational operators. What we can do is use the predefined string function strcmp (string compare).
If s1 and s2 are strings, the expression strcmp(s1, s2) returns the following values:
§ 0 if s1 is identical to s2
§ < 0 if s1 is less than s2 (in alphabetical order)
§ > 0 if s1 is greater than s2 (in alphabetical order)
For example,
strcmp("hello", "hi") is < 0
strcmp("hi","hello") is > 0
strcmp("allo","allo") is 0
Using strcmp, we can write the while condition as
while (strcmp(firstName, "END") != 0)
If strcmp(firstName, "END") is not 0, it means that firstName does not contain the word END so we have not reached the end of the data; the while loop is entered to process that employee.
When faced with a program which requires so many things to be done, it is best to start by working on part of the problem, getting it right and then tackling the other parts. For this problem, we can start by getting the program to read and process the data without counting, finding the total or finding the highest-paid employee.
Program P5.8 is based on program P4.7 (Section 4.5.2).
Program P5.8
#include <stdio.h>
#include <string.h>
#define MaxRegularHours 40
#define OvertimeFactor 1.5
int main() {
FILE * in = fopen("paydata.txt", "r");
FILE * out = fopen("payroll.txt", "w");
char firstName[20], lastName[20], name[40];
double hours, rate, regPay, ovtPay, netPay;
fprintf(out,"Name Hours Rate Regular Overtime Net\n\n");
fscanf(in, "%s", firstName);
while (strcmp(firstName, "END") != 0) {
fscanf(in, "%s %lf %lf", lastName, &hours, &rate);
if (hours <= MaxRegularHours) {
regPay = hours * rate;
ovtPay = 0;
}
else {
regPay = MaxRegularHours * rate;
ovtPay = (hours - MaxRegularHours) * rate * OvertimeFactor;
}
netPay = regPay + ovtPay;
//make one name out of firstName and lastName
strcpy(name,firstName); strcat(name," "); strcat(name,lastName);
fprintf(out, "%-15s %5.1f %6.2f", name, hours, rate);
fprintf(out, "%9.2f %9.2f %7.2f\n", regPay, ovtPay, netPay);
fscanf(in, "%s", firstName);
}
fclose(in);
fclose(out);
}
Comments on Program P5.8
§ We use the “file pointers” in and out for reading data from paydata.txt and sending output to payroll.txt.
§ Since data is being read from a file, prompts are not required.
§ We use fscanf for reading data and fprintf for writing output.
§ We use fclose to close the files.
§ We print a heading with the following statement:
§ fprintf(out,"Name Hours Rate Regular Overtime Net\n\n");
§ To get the output to line up nicely, you will need to fiddle with the spaces between the words and the field widths in the statements which print the results. For example, there are 12 spaces between e and H, 3 spaces between s and R, 2 between e and R, 2 between r and O and 5 between e and N.
§ You should experiment with the field widths in the fprintf statements (which write one line of output) to see what effect it has on your output.
§ We use a while loop to process several employees. When the “first name” END is read, the program knows it has reached the end of the data. It closes the files and stops.
Now that we’ve got the basic processing right, we can add the statements to perform the other tasks. Program P5.9 is the complete program which counts the employees, calculates the total wage bill and determines the employee who earned the highest salary.
Counting the employees and finding the total wage bill are fairly straightforward. We use the variables numEmp and wageBill which are initialized to 0 before the loop. They are incremented inside the loop and their final values are printed after the loop. If you have difficulty following the code, you need to re-read Sections 5.1 and 5.2. We use numEmp++ to add 1 to numEmp and wageBill += netPay to add netPay to wageBill.
The variable mostPay holds the most pay earned by any employee. It is initialized to 0. Each time we calculate netPay for the current employee, we compare it with mostPay. If it is bigger, we set mostPay to the new amount and save the name of the employee (name) in bestPaid.
Program P5.9
#include <stdio.h>
#include <string.h>
#define MaxRegularHours 40
#define OvertimeFactor 1.5
int main() {
FILE * in = fopen("paydata.txt", "r");
FILE * out = fopen("payroll.txt", "w");
char firstName[20], lastName[20], name[40], bestPaid[40];
double hours, rate, regPay, ovtPay, netPay;
double wageBill = 0, mostPay = 0;
int numEmp = 0;
fprintf(out,"Name Hours Rate Regular Overtime Net\n\n");
fscanf(in, "%s", firstName);
while (strcmp(firstName, "END") != 0) {
numEmp++;
fscanf(in, "%s %lf %lf", lastName, &hours, &rate);
if (hours <= MaxRegularHours) {
regPay = hours * rate;
ovtPay = 0;
}
else {
regPay = MaxRegularHours * rate;
ovtPay = (hours - MaxRegularHours) * rate * OvertimeFactor;
}
netPay = regPay + ovtPay;
//make one name out of firstName and lastName
strcpy(name,firstName); strcat(name," "); strcat(name,lastName);
fprintf(out, "%-15s %5.1f %6.2f", name, hours, rate);
fprintf(out, "%9.2f %9.2f %7.2f\n", regPay, ovtPay, netPay);
if (netPay > mostPay) {
mostPay = netPay;
strcpy(bestPaid, name);
}
wageBill += netPay;
fscanf(in, "%s", firstName);
} //end while
fprintf(out, "\nNumber of employees: %d\n", numEmp);
fprintf(out, "Total wage bill: $%3.2f\n", wageBill);
fprintf(out,"%s earned the most pay of $%3.2f\n",bestPaid, mostPay);
fclose(in); fclose(out);
}
5.11 The for Construct
In Chapters 3, 4 and 5 we showed you three kinds of logic which can be used for writing programs—sequence, selection and repetition. Believe it or not, with these three, you have all the logic control structures you need to express the logic of any program. It has been proven that these three structures are all you need to formulate the logic to solve any problem that can be solved on a computer.
It follows that all you need are if and while statements to write the logic of any program. However, many programming languages provide additional statements because they allow you to express some kinds of logic more conveniently than using if and while. The for statement is a good example.
Whereas while lets you repeat statements as long as some condition is true, for lets you repeat statements a specified number of times (25 times, say). Consider the following pseudocode example of the for construct (more commonly called the for loop):
for h = 1 to 5 do
print "I must not sleep in class"
endfor
This says to execute the print statement 5 times, with h assuming the values 1, 2, 3, 4 and 5, one value for each of the 5 times. The effect is to print the following:
I must not sleep in class
I must not sleep in class
I must not sleep in class
I must not sleep in class
I must not sleep in class
The construct consists of:
§ the word for
§ the loop variable (h, in the example)
§ =
§ the initial value (1, in the example)
§ the word to
§ the final value (5, in the example)
§ the word do
§ one or more statements to be executed each time through the loop; these statements make up the body of the loop
§ the word endfor, indicating the end of the construct
We emphasize that endfor is not a C word and does not appear in any C program. It is just a convenient word used by programmers when writing pseudocode to indicate the end of a for loop.
In order to highlight the structure of the loop and make it more readable, we line up for and endfor, and indent the statements in the body.
The part of the construct between for and do is called the control part of the loop. This is what determines how many times the body is executed. In the example, the control part is h = 1 to 5. This works as follows:
§ h is set to 1 and the body (print) is executed
§ h is set to 2 and the body (print) is executed
§ h is set to 3 and the body (print) is executed
§ h is set to 4 and the body (print) is executed
§ h is set to 5 and the body (print) is executed
The net effect is that, in this case, the body is executed 5 times.
In general, if the control part is h = first to last, it is executed as follows:
§ if first > last, the body is not executed at all; execution continues with the statement, if any, after endfor; otherwise
§ h is set to first and the body is executed
§ 1 is added to h; if the value of h is less than or equal to last, the body is executed again
§ 1 is added to h; if the value of h is less than or equal to last, the body is executed again
§ and so on
When the value of h reaches last, the body is executed for the last time and control goes to the statement, if any, after endfor.
The net effect is that the body is executed for each value of h between first and last, inclusive.
5.11.1 The for Statement in C
The pseudocode construct
for h = 1 to 5 do
print "I must not sleep in class"
endfor
is implemented in C as follows:
for (h = 1; h <= 5; h++)
printf("I must not sleep in class\n");
assuming that h is declared as int. However, it is more common to declare h in the for statement itself, like this:
for (int h = 1; h <= 5; h++)
printf("I must not sleep in class\n");
In this case, though, note that the scope of h extends only to the body of the for (see next).
Caution: The ability to declare the loop variable in the for statement was not allowed in early versions of C. If you are using an older compiler, you will get an error. In that case, just declare the loop variable before the forstatement.
In C, the body of the for must be a single statement or a block. In the example, it is the single printf statement. If it were a block, we would write it in the following form:
for (int h = 1; h <= 5; h++) {
<statement1>
<statement2>
etc.
}
When we declare the loop variable (h in the example) in the for statement, h is "known" (can be used) within the block only. If we attempt to use h after the block, we will get an "undeclared variable" error message.
Program P5.10 illustrates how the for statement is used to print the following 5 times:
I must not sleep in class
As you could probably figure out, if you want to print 100 lines, say, all you have to do is change 5 to 100 in the for statement.
Program P5.10
#include
int main() {
int h;
for (h = 1; h <= 5; h++)
printf("I must not sleep in class\n");
}
The general form of the for statement in C is
for (<expr1>; <expr2>; <expr3>)
<statement>
The word for, the brackets and the semicolons are required. You must supply <expr1>, <expr2>, <expr3> and <statement>.
In detail, the for statement consists of
§ The word for
§ A left bracket, (
§ <expr1>, called the initialization step; this is the first step performed when the for is executed.
§ A semicolon, ;
§ <expr2>, the condition which controls whether or not <statement> is executed.
§ A semicolon, ;
§ <expr3>, called the re-initialization step
§ A right bracket, )
§ <statement>, called the body of the loop. This can be a simple statement or a block.
When a for statement is encountered, it is executed as follows:
1. <expr1> is evaluated.
2. <expr2> is evaluated. If it is false, execution continues with the statement, if any, after <statement>. If it is true, <statement> is executed, followed by <expr3>, and this step (2) is repeated.
This can be expressed more concisely as follows:
<expr1>;
while (<expr2>) {
<statement>;
<expr3>;
}
Consider the following:
for (h = 1; h <= 5; h++)
printf("I must not sleep in class\n");
§ h = 1 is <expr1>
§ h <= 5 is <expr2>
§ h++ is <expr3>
§ <statement> is printf(...);
This code is executed as follows:
1. h is set to 1
2. The test h <= 5 is performed. It is true, so the body of the loop is executed (one line is printed). The re-initialization step h++ is then performed, so h is now 2.
3. The test h <= 5 is again performed. It is true, so the body of the loop is executed (a second line is printed); h++ is performed, so h is now 3.
4. The test h <= 5 is again performed. It is true, so the body of the loop is executed (a third line is printed); h++ is performed, so h is now 4.
5. The test h <= 5 is again performed. It is true, so the body of the loop is executed (a fourth line is printed); h++ is performed, so h is now 5.
6. The test h <= 5 is again performed. It is true, so the body of the loop is executed (a fifth line is printed); h++ is performed, so h is now 6.
7. The test h <= 5 is again performed. It is now false, so execution of the for loop ends and the program continues with the statement, if any, after printf(...).
On exit from the for loop, the value of h (6, in this case) is available and may be used by the programmer, if required. Note, however, that if h was declared in the for statement, it would be unavailable outside the loop.
If we need a loop to count backwards (from 5 down to 1, say), we can write
for (int h = 5; h >= 1; h--)
The loop body is executed with h taking on the values 5, 4, 3, 2 and 1.
We can also count upwards (or downwards) in steps other than 1. For example, the statement
for (int h = 10; h <= 20; h += 3)
will execute the body with h taking on the values 10, 13, 16 and 19. And the statement
for (int h = 100; h >= 50; h -= 10)
will execute the body with h taking on the values 100, 90, 80, 70, 60 and 50.
In general, we can use whatever expressions we need to get the effect that we want.
In Program P5.10, h takes on the values 1, 2, 3, 4 and 5 inside the loop. We have not used h in the body but it is available, if needed. We show a simple use in Program P5.11 in which we number the lines by printing the value of h.
Program P5.11
#include <stdio.h>
int main() {
for (int h = 1; h <= 5; h++)
printf("%d. I must not sleep in class\n", h);
}
When run, this program will print the following:
1. I must not sleep in class
2. I must not sleep in class
3. I must not sleep in class
4. I must not sleep in class
5. I must not sleep in class
The initial and final values in the for statement do not have to be constants; they can be variables or expressions. For example, consider this:
for (h = 1; h <= n; h++) ...
How many times would the body of this loop be executed? We cannot say unless we know the value of n when this statement is encountered. If n has the value 7, then the body would be executed 7 times.
This means that before the computer gets to the for statement, n must have been assigned some value and it is this value which determines how many times the loop is executed. If a value has not been assigned to n, the for statement would not make sense and the program will crash (or, at best, give some nonsensical output).
To illustrate, we can modify Program P5.11 to ask the user how many lines she wants to print. The number entered is then used to control how many times the loop is executed and, hence, how many lines are printed.
The changes are shown in Program P5.12.
Program P5.12
#include <stdio.h>
int main() {
int n;
printf("How many lines to print? ");
scanf("%d", &n);
printf("\n"); //print a blank line
for (int h = 1; h <= n; h++)
printf("%d. I must not sleep in class\n", h);
}
A sample run is shown below. We will show shortly how to neaten the output.
How many lines to print? 12
1. I must not sleep in class
2. I must not sleep in class
3. I must not sleep in class
4. I must not sleep in class
5. I must not sleep in class
6. I must not sleep in class
7. I must not sleep in class
8. I must not sleep in class
9. I must not sleep in class
10. I must not sleep in class
11. I must not sleep in class
12. I must not sleep in class
Note that we do not (and cannot) know beforehand what number the user will type. However, that is not a problem. We simply store the number in a variable (n is used) and use n as the “final value” in the for statement. Thus, the number the user types will determine how many times the body is executed.
Now the user can change the number of lines printed simply by entering the desired value in response to the prompt. No change is needed in the program. Program P5.12 is much more flexible than P5.11.
5.11.2 A Bit of Aesthetics
In the above run, while the output is correct, the numbers do not line up very nicely with the result that the I’s do not line up properly. We can get things to line up by using a field width when printing h. For this example, 2 will do. However, if the number could run into the hundreds, we must use at least 3 and for thousands at least 4, and so on.
In Program P5.12, if we change the printf statement to this:
printf("%2d. I must not sleep in class\n", h);
the following, more neatly-looking output, would be printed:
How many lines to print? 12
1. I must not sleep in class
2. I must not sleep in class
3. I must not sleep in class
4. I must not sleep in class
5. I must not sleep in class
6. I must not sleep in class
7. I must not sleep in class
8. I must not sleep in class
9. I must not sleep in class
10. I must not sleep in class
11. I must not sleep in class
12. I must not sleep in class
5.12 Multiplication Tables
The for statement is quite handy for producing multiplication tables. To illustrate, let us write a program to produce a “2 times” table from 1 to 12. The following should be printed by the program:
1 x 2 = |
2 |
|
2 x 2 = |
4 |
|
3 x 2 = |
6 |
|
4 x 2 = |
8 |
|
5 x 2 = |
10 |
|
6 x 2 = |
12 |
|
7 x 2 = |
14 |
|
8 x 2 = |
16 |
|
9 x 2 = |
18 |
|
10 x 2 = |
20 |
|
11 x 2 = |
22 |
|
12 x 2 = |
24 |
A look at the output reveals that each line consists of three parts:
1. A number on the left which increases by 1 for each new line.
2. A fixed part " x 2 = " (note the spaces) which is the same for each line.
3. A number on the right; this is derived by multiplying the number on the left by 2.
We can produce the numbers on the left by using this statement:
for (int m = 1; m <= 12; m++)
We then print m each time through the loop. And we can produce the number on the right by multiplying m by 2.
Program P5.13 shows how to write it. When run, it will produce the table above.
Program P5.13
#include <stdio.h>
int main() {
for (int m = 1; m <= 12; m++)
printf("%2d x 2 = %2d\n", m, m * 2);
}
Note the use of the field width 2 (in %2d) for printing m and m * 2. This is to ensure that the numbers line up as shown in the output. Without the field width, the table would not look neat—try it and see.
What if we want to print a “7 times” table? What changes would be needed? We would just need to change the printf statement to
printf("%2d x 7 = %2d\n", m, m * 7);
Similarly, if we want a “9 times” table, we would have to change the 7s to 9s. And we would have to keep changing the program for each table that we want.
A better approach is to let the user tell the computer which table he wants. The program will then use this information to produce the table requested. Now when the program is run, it will prompt:
Enter type of table:
If the user wants a “7 times” table, he will enter 7. The program will then go ahead and produce a “7 times” table. Program P5.14 shows how.
Program P5.14
#include <stdio.h>
int main() {
int factor;
printf("Type of table? ");
scanf("%d", &factor);
for (int m = 1; m <= 12; m++)
printf("%2d x %d = %2d\n", m, factor, m * factor);
}
Since we do not know beforehand what type of table would be requested, we cannot use 7, say, in the format string, since the user may want a “9 times” table. We must print the variable factor which holds the type of table.
The following is a sample run:
Type of table? 7 |
||
1 x 7 = |
7 |
|
2 x 7 = |
14 |
|
3 x 7 = |
21 |
|
4 x 7 = |
28 |
|
5 x 7 = |
35 |
|
6 x 7 = |
42 |
|
7 x 7 = |
49 |
|
8 x 7 = |
56 |
|
9 x 7 = |
63 |
|
10 x 7 = |
70 |
|
11 x 7 = |
77 |
|
12 x 7 = |
84 |
We now have a program which can produce any multiplication table from 1 to 12. But there is nothing sacred about the range 1 to 12 (special, maybe, since that’s what we all learnt in school). How can we generalize the program to produce any table in any range? We must let the user tell the program what type of table and what range he wants. And in the program, we will need to replace the numbers 1 and 12 by variables, (start and finish, say).
All these changes are reflected in Program P5.15.
Program P5.15
#include <stdio.h>
int main() {
int factor, start, finish;
printf("Type of table? ");
scanf("%d", &factor);
printf("From? ");
scanf("%d", &start);
printf("To? ");
scanf("%d", &finish);
printf("\n");
for (int m = start; m <= finish; m++)
printf("%2d x %d = %2d\n", m, factor, m * factor);
}
The following sample run shows how to produce a “6 times” table from 10 to 16.
Type of table? 6 |
||
From? 10 |
||
To? 16 |
||
10 x 6 = |
60 |
|
11 x 6 = |
66 |
|
12 x 6 = |
72 |
|
13 x 6 = |
78 |
|
14 x 6 = |
84 |
|
15 x 6 = |
90 |
|
16 x 6 = |
96 |
To cater for bigger numbers, we would need to increase the field width of 2 in the printf statement if we want the numbers to line up neatly.
Comment on Program P5.15
The program assumes that start is less than or equal to finish. What if this is not so? For example, suppose the user enters 20 for start and 15 for finish. The for statement becomes
for (int m = 20; m <= 15; m++)
m is set to 20; since this value is immediately bigger than the final value 15, the body is not executed at all and the program ends with nothing printed.
To cater for this possibility, we can let the program validate the values of start and finish to ensure that the ‘From’ value is less than or equal to the ‘To’ value. One way of doing this is to write the following:
if (start > finish)
printf("Invalid data: From value is bigger than To value\n");
else {
printf("\n");
for (int m = start; m <= finish; m++)
printf("%2d x %d = %2d\n", m, factor, m * factor);
}
Validating data entered is yet another example of defensive programming. Also, it is better to print a message informing the user of the error rather than have the program do nothing. This makes the program more user-friendly.
Another option here is not to treat a bigger start value as an error but simply print the table in reverse order, going from largest to smallest. Yet another possibility is to swap the values of start and finish and print the table in the normal way. These variations are left as exercises.
5.13 Temperature Conversion Table
Some countries use the Celsius scale for measuring temperature while others use the Fahrenheit scale. Suppose we want to print a table of temperature conversions from Celsius to Fahrenheit. The table runs from 0 degrees C to 100 degrees C in steps of 10, thus:
Celsius |
Fahrenheit |
|
0 |
32 |
|
10 |
50 |
|
20 |
68 |
|
30 |
86 |
|
40 |
104 |
|
50 |
122 |
|
60 |
140 |
|
70 |
158 |
|
80 |
176 |
|
90 |
194 |
|
100 |
212 |
For a Celsius temperature, C, the Fahrenheit equivalent is 32 + 9C/5.
If we use c to hold the Celsius temperature, we can write a for statement to let c take on the values 0, 10, 20, ..., up to 100, with
for (c = 0; c <= 100; c += 10)
Each time the loop is executed, c is incremented by 10. Using this, we write Program P5.16 to produce the table.
Program P5.16
#include <stdio.h>
int main() {
double c, f;
printf("Celsius Fahrenheit\n\n");
for (c = 0; c <= 100; c += 10) {
f = 32 + 9 * c / 5;
printf("%5.0f %9.0f\n", c, f);
}
}
An interesting part of the program are the printf statements. In order to get the temperatures centred under the heading, we need to do some counting. Consider the heading
Celsius Fahrenheit
with the C in column 1 and 2 spaces between s and F.
Assume we want the Celsius temperatures lined up under i and the Fahrenheit temperatures lined up under n (see output above).
By counting, we find that i is in column 5 and n is in column 15.
From this, we can figure out that the value of c must be printed in a field width of 5 (the first 5 columns) and the value of f must be printed in the next 10 columns. We use a field width of 9 for f since there is already one space between f and % in printf(...).
We print c and f without a decimal point using 0 as the number of decimal places in the format specification. If any temperature is not a whole number, the 0 specification will print it rounded to the nearest whole number, as in the table below.
As an exercise, re-write Program P5.16 so that it requests threes values for start, finish and incr and produces a conversion table with Celsius temperatures going from start to finish in steps of incr. Follow the ideas of the previous section for producing any multiplication table. For example, if start is 20, finish is 40 and incr is 2, the program should produce the following table (with Fahrenheit temperatures rounded to the nearest whole number):
Celsius |
Fahrenheit |
|
20 |
68 |
|
22 |
72 |
|
24 |
75 |
|
26 |
79 |
|
28 |
82 |
|
30 |
86 |
|
32 |
90 |
|
34 |
93 |
|
36 |
97 |
|
38 |
100 |
|
40 |
104 |
As another exercise, write a program which produces a table from Fahrenheit to Celsius. For a Fahrenheit temperature, F, the Celsius equivalent is 5(F - 32)/9.
5.14 Expressive Power of for
In C, the for statement can be used for a lot more than just counting the number of times a loop is executed. This is possible because <expr1>, <expr2> and <expr3> can be any expressions; they are not even required to be related in any way. So, for instance, <expr1> can be h = 1, <expr2> can test if a is equal to b and <expr3> can be k++ or any other expression the programmer desires. The following is perfectly valid:
for (h = 1; a == b; k++)
It is also possible to omit any of <expr1>, <expr2> or <expr3>. However, the semicolons must be included. Thus, to omit <expr3>, one can write
for (<expr1>; <expr2>; ) <statement>
In this case,
1. <expr1> is evaluated; then
2. <expr2> is evaluated. If it is false, execution continues after <statement>. If it is true, <statement> is executed and this step (2) is repeated.
This is equivalent to
<expr1>;
while (<expr2>) <statement>
If, in addition, we omit <expr1>, we will have
for ( ; expr2 ; ) <statement> // note the semicolons
Now, <expr2> is evaluated. If it is false, execution continues after <statement>. If it is true, <statement> is executed, followed by another evaluation of <expr2>, and so on. The net effect is that <statement> is executed as long as <expr2> is true—the same effect achieved by
while (<expr2>) <statement>
Most times, <expr1> will initialize some variable, <expr2> will test it and <expr3> will change it. But more is possible. For instance, the following is valid:
for (lo = 1, hi = n; lo <= hi; lo++, hi--) <statement>
Here, <expr1> consists of two assignment statements separated by a comma; <expr3> consists of two expressions separated by a comma. This is very useful when two variables are related and we want to highlight the relationship. In this case, the relationship is captured in one place, the for statement. We can easily see how the variables are initialized and how they are changed.
This feature comes in very handy when dealing with arrays. We will see examples in Chapter 8. For now, we leave you with the example of printing all pairs of integers which add up to a given integer, n.
The code is:
int lo, hi;
//assume n has been assigned a value
for (lo = 1, hi = n - 1; lo <= hi; lo++, hi--)
printf("%2d %2d\n", lo, hi);
If n is 10, this code will print the following:
1 9
2 8
3 7
4 6
5 5
The variables lo and hi are initialized to the first pair. After a pair is printed, lo is incremented by 1 and hi is decremented by 1 to get the next pair. When lo passes hi, all pairs have been printed.
5.15 The do...while Statement
We have seen that the while statement allows a loop to be executed zero or more times. However, there are situations in which it is convenient to have a loop executed at least once. For example, suppose we want to prompt for a number representing a day of the week, with 1 for Sunday, 2 for Monday, and so on. We also want to ensure that the number entered is valid, that it lies in the range 1 to 7. In this case, at least one number must be entered. If it is valid, we move on; if not, we must prompt again for another number and must do so as long as the number entered is invalid. We could express this logic using a while statement as follows:
printf("Enter a day of the week (1-7): ");
scanf("%d", &day); //assume int day
while (day < 1 || day > 7) { //as long as day is invalid
printf("Enter a day of the week (1-7): ");
scanf("%d", &day);
}
While this will work, we can express it a bit neater using a do...while statement.
The general form of the do...while statement in C is
do
<statement>
while (<expression>);
The words do and while, the brackets and the semicolon are required. You must supply <statement> and <expression>.
When a do...while is encountered,
1. <statement> is executed
2. <expression> is then evaluated; if it is true (non-zero), repeat from step 1. If it is false (zero), execution continues with the statement, if any, after the semicolon.
As long as <expression> is true, <statement> is executed. It is important to note that because of the way the construct is written, <statement> is always executed at least once.
Using do...while, we can express the logic above as follows:
do {
printf("Enter a day of the week (1-7): ");
scanf("%d", &day);
} while (day < 1 || day > 7); //as long as day is invalid
Note how much neater this looks. Here, <statement> is the block delimited by { and }, and <expression> is day < 1 || day > 7.
We illustrate the use of do...while with two more examples.
5.15.1 Highest Common Factor
Previously, we wrote Program P5.2 to find the HCF of two integers using Euclid's algorithm. We now rewrite the program using do...while to ensure that the two numbers entered are indeed positive integers. We also re-code the algorithm using do...while instead of while. This is shown as Program P5.17.
Program P5.17
#include <stdio.h>
int main() {
int m, n, r;
do {
printf("Enter two positive integers: ");
scanf("%d %d", &m, &n);
} while (m <= 0 || n <= 0);
// At this point, both m and n are positive
printf("\nThe HCF of %d and %d is ", m, n);
do {
r = m % n;
m = n;
n = r;
} while (n > 0);
printf("%d\n", m);
}
Program P5.17 requests two positive values. The first do...while keeps asking until two positive values are entered. When this occurs, the program continues with the calculation of the HCF. On exit from the second do...while, the value of n is 0 and the value of m is the HCF. The following is a sample run of program P5.17:
Enter two positive integers: 84 -7
Enter two positive integers: 46 0
Enter two positive integers: 200 16
The HCF of 200 and 16 is 8
5.15.2 Interest at the Bank
Consider the following problem:
A man deposits $1000 in a bank at an interest rate of 10% per year. At the end of each year, the interest earned is added to the amount on deposit and this becomes the new deposit for the next year. Write a program to determine the year in which the amount accumulated first exceeds $2000. For each year, print the deposit at the beginning of the year and the interest earned for that year until the target is reached.
A solution to this problem is given in Program P5.18.
Program P5.18
#include <stdio.h>
int main() {
int year;
double initialDeposit, interestRate, target, deposit, interest;
printf("Initial deposit? ");
scanf("%lf", &initialDeposit);
printf("Rate of interest? ");
scanf("%lf", &interestRate);
printf("Target deposit? ");
scanf("%lf", &target);
printf("\nYear Deposit Interest\n\n");
deposit = initialDeposit;
year = 0;
do {
year++;
interest = deposit * interestRate / 100;
printf("%3d %8.2f %8.2f\n", year, deposit, interest);
deposit += interest;
} while (deposit <= target);
printf("\nDeposit exceeds $%7.2f at the end of year %d\n", target, year);
}
The program uses the following variables:
initialDeposit |
1000, in the example |
interestRate |
10, in the example |
target |
2000, in the example |
deposit |
the deposit at any given time |
As long as the end-of-year deposit has not exceeded the target, we calculate interest for another year. Program P5.18 does not cater for the case when the initial deposit is greater than the target. If this is required, a while statement may be used (exercise!). The following is a sample run of P5.18:
Initial deposit? 1000 |
|||
Rate of interest? 10 |
|||
Target deposit? 2000 |
|||
Year |
Deposit |
Interest |
|
1 |
1000.00 |
100.00 |
|
2 |
1100.00 |
110.00 |
|
3 |
1210.00 |
121.00 |
|
4 |
1331.00 |
133.10 |
|
5 |
1464.10 |
146.41 |
|
1 |
1610.51 |
161.05 |
|
7 |
1771.56 |
177.16 |
|
8 |
1948.72 |
194.87 |
|
Deposit exceeds $2000.00 at the end of year 8 |
EXERCISES 5
1. What is an end-of-data marker? Give the other names for it.
2. Write a program to read data for several items from a file. For each item, the price and a discount percent is given. Choose an appropriate end-of-data marker. For each item, print the original price, the discount amount and the amount the customer must pay. At the end, print the number of items and the total amount the customer must pay.
3. An auto repair shop charges as follows. Inspecting the vehicle costs $75. If no work needs to be done, there is no further charge. Otherwise, the charge is $75 per hour for labour plus the cost of parts, with a minimum charge of $120. If any work is done, there is no charge for inspecting the vehicle.
4. Write a program to read several sets of hours worked and cost of parts and, for each, print the charge for the job. Choose an appropriate end-of-data marker. (You cannot choose 0 since either hours or parts could be 0.) At the end, print the total charge for all jobs.
5. Write a program to calculate electricity charges for several customers. The data for each customer consists of a name, the previous meter reading and the current meter reading. The difference in the two readings gives the number of units of electricity used. The customer pays a fixed charge of $25 plus 20 cents for each unit used. The data is stored in a file.
6. Assume that the fixed charge and the rate per unit are the same for all customers and are given on the first line. This is followed by the data for the customers. Each set of data consists of two lines: a name on the first line and the meter readings on the second line. The ‘name’ xxxx ends the data. Print the information for the customers under a suitable heading. Also,
§ Count how many customers were processed
§ Print the total due to the electricity company
§ Find the customer whose bill was the highest
10. A file contains data for several persons. The data for each person consists of their gross salary, deductions allowed and rate of tax (e.g. 25, meaning 25%). Tax is calculated by applying the rate of tax to (gross salary minus deductions). Net pay is calculated by gross salary minus tax.
11. Under an appropriate heading, print the gross salary, tax deducted, net pay and the percentage of the gross salary that was paid in tax.
12. For each person, the data consists of two lines: a name on the first line and gross salary, deductions allowed and rate of tax on the second line. The ‘name’ xxxx ends the data. Also,
§ Count how many persons were processed
§ Print totals for gross salary, tax deducted and net pay
§ Find the person who earned the highest net pay
16. Write a program which reads several lengths in inches and, for each, converts it to yards, feet and inches. (1 yard = 3 feet, 1 foot = 12 inches). For example, if a length is 100, the program should print 2 yd 2 ft 4 in. Choose an appropriate end-of-data marker.
17. Each line of data in a file consists of two lengths. Each length is given as two numbers representing feet and inches. A line consisting of 0 0 only ends the data. For each pair of lengths, print their sum. For example, if the lengths are 5 ft. 4 in. and 8 ft. 11 in., your program should print 14 ft. 3 in. The line of data for this example would be given as
18. 5 4 8 11
19. You are given a file containing an unknown amount of numbers. Each number is one of the numbers 1 to 9. A number can appear zero or more times and can appear anywhere in the file. The number 0 indicates the end of the data. Some sample data are:
20. 5 3 7 7 7 4 3 3 2 2 2 6 7 4 7 7
21. 2 2 9 6 6 6 6 6 8 5 5 3 7 9 9 9 0
22. Write a program to read the data once and print the number which appears the most in consecutive positions and the number of times it appears. Ignore the possibility of a tie. For the above data, output should be 6 5.
23. A contest was held for the promotion of SuperMarbles. Each contestant was required to guess the number of marbles in a jar. Write a program to determine the Grand Prize winner (ignoring the possibility of a tie) based on the following:
24. The first line of data contains a single integer (answer, say) representing the actual number of marbles in the jar. Each subsequent line contains a contestant’s ID number (an integer) and an integer representing that contestant’s guess. The data is terminated by a line containing 0 only.
25. The Grand Prize winner is that contestant who guesses closest to answer without exceeding it. There is no winner if all guesses are too big.
26. Assume all data are valid. Print the number of contestants and the ID number of the winner, if any.
27. The manager of a hotel wants to calculate the cost of carpeting the rooms in the hotel. All the rooms are rectangular in shape. He has a file, rooms.in, which contains data for the rooms. Each line of data consists of the room number, the length and breadth of the room (in metres), and the cost per square metre of the carpet for that room. For example, the data line:
28. 325 3.0 4.5 40.00
29. means that room 325 is 3.0 metres by 4.5 metres, and the cost of the carpet for that room is $40.00 per square metre. The last line of the file contains 0 only, indicating the end of the data.
30. Write a program to do the following, sending output to the file rooms.out:
§ Print a suitable heading and under it, for each room, print the room number, the area of the room and the cost of the carpet for the room;
§ Print the number of rooms processed;
§ Print the total cost of carpeting all the rooms;
§ Print the number of the room which will cost the most to carpet (ignore ties).
35. The price of an item is p dollars. Due to inflation, the price of the item is expected to increase by r% each year. For example, the price might be $79.50 and inflation might be 7.5%. Write a program which reads values for p and r, and, starting with year 1, prints a table consisting of year and year-end price. The table ends when the year-end price is at least twice the original price.
36. A fixed percentage of water is taken from a well each day. Request values for W and P where
§ W is the amount (in litres) of water in the well at the start of the first day
§ P is the percentage of the water in the well taken out each day
§ Write a program to print the number of the day, the amount taken for that day and the amount remaining at the end of the day. The output should be terminated when 30 days have been printed or the amount remaining is less than 100 litres, whichever comes first. For example, if W is 1000 and P is 10, the output should start as follows:
Day |
Amount |
Amount |
|
Taken |
Remaining |
||
1 |
100 |
900 |
|
2 |
90 |
810 |
|
3 |
81 |
729 |
40. Write a program to print the following 99 times:
41. When you have nothing to say, it is a time to be silent
42. Write a program to print 8 copies of your favourite song.
43. Write a program to print a table of squares from 1 to 10. Each line of the table consists of a number and the square of that number.
44. Write a program to request a value for n and print a table of squares from 1 to n.
45. Write a program to request values for first and last, and print a table of squares from first to last.
46. Write a program to print 100 mailing labels for
47. The Computer Store
48. 57 First Avenue
49. San Fernando
50. Write a program to print a conversion table from miles to kilometres. The table ranges from 5 to 100 miles in steps of 5. (1 mile = 1.61 km).
51. Write a program which requests a user to enter an amount of money. The program prints the interest payable per year for rates of interest from 5% to 12% in steps of 0.5%.
52. Write a program to request a value for n; the user is then asked to enter n numbers, one at a time. The program calculates and prints the sum of the numbers. The following is a sample run:
53. How many numbers? 3
54. Enter a number? 12
55. Enter a number? 25
56. Enter a number? 18
57.
58. The sum of the 3 numbers is 55
59. Write a program to request an integer n from 1 to 9 and print a line of output consisting of ascending digits from 1 to n followed by descending digits from n-1 to 1. For example, if n = 5, print the line
60. 123454321
61. Solve problem 10, above, assuming that the first line of data contains the number of rooms (n, say) to carpet. This is followed by n lines of data, one line for each room.
62. Solve problem 12, above, but this time print the table for exactly 30 days. If necessary, continue printing the table even if the amount of water falls below 100 litres.