Chapter 6: Basic I/O
So far, we've been using printf to do output, and we haven't had a way of doing any input. In this chapter, we'll learn a bit more about printf, and we'll begin learning about character-based input and output.
6.1 printf
printf's name comes from print formatted. It generates output under the control of a format string (its first argument) which consists of literal characters to be printed and also special character sequences--format specifiers--which request that other arguments be fetched, formatted, and inserted into the string. Our very first program was nothing more than a call to printf, printing a constant string:
printf("Hello, world!\n");
Our second program also featured a call to printf:
printf("i is %d\n", i);
In that case, whenever printf ``printed'' the string "i is %d", it did not print it verbatim; it replaced the two characters %d with the value of the variable i.
There are quite a number of format specifiers for printf. Here are the basic ones :
%d print an int argument in decimal
%ld print a long int argument in decimal
%c print a character
%s print a string
%f print a float or double argument
%e same as %f, but use exponential notation
%g use %e or %f, whichever is better
%o print an int argument in octal (base 8)
%x print an int argument in hexadecimal (base 16)
%% print a single %
It is also possible to specify the width and precision of numbers and strings as they are inserted (somewhat like FORTRAN format statements); we'll present those details in a later chapter. (Very briefly, for those who are curious: a notation like %3d means to print an int in a field at least 3 spaces wide; a notation like %5.2f means to print a float or double in a field at least 5 spaces wide, with two places to the right of the decimal.)
To illustrate with a few more examples: the call
printf("%c %d %f %e %s %d%%\n", '1', 2, 3.14, 56000000., "eight", 9);
would print
1 2 3.140000 5.600000e+07 eight 9%
The call
printf("%d %o %x\n", 100, 100, 100);
would print
100 144 64
Successive calls to printf just build up the output a piece at a time, so the calls
printf("Hello, ");
printf("world!\n");
would also print Hello, world! (on one line of output).
Earlier we learned that C represents characters internally as small integers corresponding to the characters' values in the machine's character set (typically ASCII). This means that there isn't really much difference between a character and an integer in C; most of the difference is in whether we choose to interpret an integer as an integer or a character. printf is one place where we get to make that choice: %d prints an integer value as a string of digits representing its decimal value, while %c prints the character corresponding to a character set value. So the lines
char c = 'A';
int i = 97;
printf("c = %c, i = %d\n", c, i);
would print c as the character A and i as the number 97. But if, on the other hand, we called
printf("c = %d, i = %c\n", c, i);
we'd see the decimal value (printed by %d) of the character 'A', followed by the character (whatever it is) which happens to have the decimal value 97.
You have to be careful when calling printf. It has no way of knowing how many arguments you've passed it or what their types are other than by looking for the format specifiers in the format string. If there are more format specifiers (that is, more % signs) than there are arguments, or if the arguments have the wrong types for the format specifiers, printf can misbehave badly, often printing nonsense numbers or (even worse) numbers which mislead you into thinking that some other part of your program is broken.
Because of some automatic conversion rules which we haven't covered yet, you have a small amount of latitude in the types of the expressions you pass as arguments to printf. The argument for %c may be of type char or int, and the argument for %d may be of type char or int. The string argument for %s may be a string constant, an array of characters, or a pointer to some characters (though we haven't really covered strings or pointers yet). Finally, the arguments corresponding to %e, %f, and %g may be of types float or double. But other combinations do not work reliably: %d will not print a long int or a float or a double; %ld will not print an int; %e, %f, and %g will not print an int.
6.2 Character Input and Output
[This section corresponds to K&R Sec. 1.5]
Unless a program can read some input, it's hard to keep it from doing exactly the same thing every time it's run, and thus being rather boring after a while.
The most basic way of reading input is by calling the function getchar. getchar reads one character from the ``standard input,'' which is usually the user's keyboard, but which can sometimes be redirected by the operating system. getchar returns (rather obviously) the character it reads, or, if there are no more characters available, the special value EOF (``end of file'').
A companion function is putchar, which writes one character to the ``standard output.'' (The standard output is, again not surprisingly, usually the user's screen, although it, too, can be redirected. printf, like putchar, prints to the standard output; in fact, you can imagine that printf calls putchar to actually print each of the characters it formats.)
Using these two functions, we can write a very basic program to copy the input, a character at a time, to the output:
#include
/* copy input to output */
main()
{
int c;
c = getchar();
while(c != EOF)
{
putchar(c);
c = getchar();
}
return 0;
}
This code is straightforward, and I encourage you to type it in and try it out. It reads one character, and if it is not the EOF code, enters a while loop, printing one character and reading another, as long as the character read is not EOF. This is a straightforward loop, although there's one mystery surrounding the declaration of the variable c: if it holds characters, why is it an int?
We said that a char variable could hold integers corresponding to character set values, and that an int could hold integers of more arbitrary values (up to +-32767). Since most character sets contain a few hundred characters (nowhere near 32767), an int variable can in general comfortably hold all char values, and then some. Therefore, there's nothing wrong with declaring c as an int. But in fact, it's important to do so, because getchar can return every character value, plus that special, non-character value EOF, indicating that there are no more characters. Type char is only guaranteed to be able to hold all the character values; it is not guaranteed to be able to hold this ``no more characters'' value without possibly mixing it up with some actual character value. (It's like trying to cram five pounds of books into a four-pound box, or 13 eggs into a carton that holds a dozen.) Therefore, you should always remember to use an int for anything you assign getchar's return value to.
When you run the character copying program, and it begins copying its input (your typing) to its output (your screen), you may find yourself wondering how to stop it. It stops when it receives end-of-file (EOF), but how do you send EOF? The answer depends on what kind of computer you're using. On Unix and Unix-related systems, it's almost always control-D. On MS-DOS machines, it's control-Z followed by the RETURN key. Under Think C on the Macintosh, it's control-D, just like Unix. On other systems, you may have to do some research to learn how to send EOF.
(Note, too, that the character you type to generate an end-of-file condition from the keyboard is not the same as the special EOF value returned by getchar. The EOF value returned by getchar is a code indicating that the input system has detected an end-of-file condition, whether it's reading the keyboard or a file or a magnetic tape or a network connection or anything else. In a disk file, at least, there is not likely to be any character in the file corresponding to EOF; as far as your program is concerned, EOF indicates the absence of any more characters to read.)
Another excellent thing to know when doing any kind of programming is how to terminate a runaway program. If a program is running forever waiting for input, you can usually stop it by sending it an end-of-file, as above, but if it's running forever not waiting for something, you'll have to take more drastic measures. Under Unix, control-C (or, occasionally, the DELETE key) will terminate the current program, almost no matter what. Under MS-DOS, control-C or control-BREAK will sometimes terminate the current program, but by default MS-DOS only checks for control-C when it's looking for input, so an infinite loop can be unkillable. There's a DOS command,
break on
which tells DOS to look for control-C more often, and I recommend using this command if you're doing any programming. (If a program is in a really tight infinite loop under MS-DOS, there can be no way of killing it short of rebooting.) On the Mac, try command-period or command-option-ESCAPE.
Finally, don't be disappointed (as I was) the first time you run the character copying program. You'll type a character, and see it on the screen right away, and assume it's your program working, but it's only your computer echoing every key you type, as it always does. When you hit RETURN, a full line of characters is made available to your program. It then zips several times through its loop, reading and printing all the characters in the line in quick succession. In other words, when you run this program, it will probably seem to copy the input a line at a time, rather than a character at a time. You may wonder how a program could instead read a character right away, without waiting for the user to hit RETURN. That's an excellent question, but unfortunately the answer is rather complicated, and beyond the scope of our discussion here. (Among other things, how to read a character right away is one of the things that's not defined by the C language, and it's not defined by any of the standard library functions, either. How to do it depends on which operating system you're using.)
Stylistically, the character-copying program above can be said to have one minor flaw: it contains two calls to getchar, one which reads the first character and one which reads (by virtue of the fact that it's in the body of the loop) all the other characters. This seems inelegant and perhaps unnecessary, and it can also be risky: if there were more things going on within the loop, and if we ever changed the way we read characters, it would be easy to change one of the getchar calls but forget to change the other one. Is there a way to rewrite the loop so that there is only one call to getchar, responsible for reading all the characters? Is there a way to read a character, test it for EOF, and assign it to the variable c, all at the same time?
There is. It relies on the fact that the assignment operator, =, is just another operator in C. An assignment is not (necessarily) a standalone statement; it is an expression, and it has a value (the value that's assigned to the variable on the left-hand side), and it can therefore participate in a larger, surrounding expression. Therefore, most C programmers would write the character-copying loop like this:
while((c = getchar()) != EOF)
putchar(c);
What does this mean? The function getchar is called, as before, and its return value is assigned to the variable c. Then the value is immediately compared against the value EOF. Finally, the true/false value of the comparison controls the while loop: as long as the value is not EOF, the loop continues executing, but as soon as an EOF is received, no more trips through the loop are taken, and it exits. The net result is that the call to getchar happens inside the test at the top of the while loop, and doesn't have to be repeated before the loop and within the loop (more on this in a bit).
Stated another way, the syntax of a while loop is always
while( expression ) ...
A comparison (using the != operator) is of course an expression; the syntax is
expression != expression
And an assignment is an expression; the syntax is
expression = expression
What we're seeing is just another example of the fact that expressions can be combined with essentially limitless generality and therefore infinite variety. The left-hand side of the != operator (its first expression) is the (sub)expression c = getchar(), and the combined expression is the expression needed by the while loop.
The extra parentheses around
(c = getchar())
are important, and are there because because the precedence of the != operator is higher than that of the = operator. If we (incorrectly) wrote
while(c = getchar() != EOF) /* WRONG */
the compiler would interpret it as
while(c = (getchar() != EOF))
That is, it would assign the result of the != operator to the variable c, which is not what we want.
(``Precedence'' refers to the rules for which operators are applied to their operands in which order, that is, to the rules controlling the default grouping of expressions and subexpressions. For example, the multiplication operator * has higher precedence than the addition operator +, which means that the expression a + b * c is parsed as a + (b * c). We'll have more to say about precedence later.)
The line
while((c = getchar()) != EOF)
epitomizes the cryptic brevity which C is notorious for. You may find this terseness infuriating (and you're not alone!), and it can certainly be carried too far, but bear with me for a moment while I defend it.
The simple example we've been discussing illustrates the tradeoffs well. We have four things to do:
- call getchar,
- assign its return value to a variable,
- test the return value against EOF, and
- process the character (in this case, print it out again).
We can't eliminate any of these steps. We have to assign getchar's value to a variable (we can't just use it directly) because we have to do two different things with it (test, and print). Therefore, compressing the assignment and test into the same line is the only good way of avoiding two distinct calls to getchar. You may not agree that the compressed idiom is better for being more compact or easier to read, but the fact that there is now only one call to getchar is a real virtue.
Don't think that you'll have to write compressed lines like
while((c = getchar()) != EOF)
right away, or in order to be an ``expert C programmer.'' But, for better or worse, most experienced C programmers do like to use these idioms (whether they're justified or not), so you'll need to be able to at least recognize and understand them when you're reading other peoples' code.
6.3 Reading Lines
It's often convenient for a program to process its input not a character at a time but rather a line at a time, that is, to read an entire line of input and then act on it all at once. The standard C library has a couple of functions for reading lines, but they have a few awkward features, so we're going to learn more about character input (and about writing functions in general) by writing our own function to read one line. Here it is:
#include
/* Read one line from standard input, */
/* copying it to line array (but no more than max chars). */
/* Does not place terminating \n in line array. */
/* Returns line length, or 0 for empty line, or EOF for end-of-file. */
int getline(char line[], int max)
{
int nch = 0;
int c;
max = max - 1; /* leave room for '\0' */
while((c = getchar()) != EOF)
{
if(c == '\n')
break;
if(nch < max)
{
line[nch] = c;
nch = nch + 1;
}
}
if(c == EOF && nch == 0)
return EOF;
line[nch] = '\0';
return nch;
}
As the comment indicates, this function will read one line of input from the standard input, placing it into the line array. The size of the line array is given by the max argument; the function will never write more than max characters into line.
The main body of the function is a getchar loop, much as we used in the character-copying program. In the body of this loop, however, we're storing the characters in an array (rather than immediately printing them out). Also, we're only reading one line of characters, then stopping and returning.
There are several new things to notice here.
First of all, the getline function accepts an array as a parameter. As we've said, array parameters are an exception to the rule that functions receive copies of their arguments--in the case of arrays, the function does have access to the actual array passed by the caller, and can modify it. Since the function is accessing the caller's array, not creating a new one to hold a copy, the function does not have to declare the argument array's size; it's set by the caller. (Thus, the brackets in ``char line[]'' are empty.) However, so that we won't overflow the caller's array by reading too long a line into it, we allow the caller to pass along the size of the array, which we promise not to exceed.
Second, we see an example of the break statement. The top of the loop looks like our earlier character-copying loop--it stops when it reaches EOF--but we only want this loop to read one line, so we also stop (that is, break out of the loop) when we see the \n character signifying end-of-line. An equivalent loop, without the break statement, would be
while((c = getchar()) != EOF && c != '\n')
{
if(nch < max)
{
line[nch] = c;
nch = nch + 1;
}
}
We haven't learned about the internal representation of strings yet, but it turns out that strings in C are simply arrays of characters, which is why we are reading the line into an array of characters. The end of a string is marked by the special character, '\0'. To make sure that there's always room for that character, on our way in we subtract 1 from max, the argument that tells us how many characters we may place in the line array. When we're done reading the line, we store the end-of-string character '\0' at the end of the string we've just built in the line array.
Finally, there's one subtlety in the code which isn't too important for our purposes now but which you may wonder about: it's arranged to handle the possibility that a few characters (i.e. the apparent beginning of a line) are read, followed immediately by an EOF, without the usual \n end-of-line character. (That's why we return EOF only if we received EOF and we hadn't read any characters first.)
In any case, the function returns the length (number of characters) of the line it read, not including the \n. (Therefore, it returns 0 for an empty line.) Like getchar, it returns EOF when there are no more lines to read. (It happens that EOF is a negative number, so it will never match the length of a line that getline has read.)
Here is an example of a test program which calls getline, reading the input a line at a time and then printing each line back out:
#include
extern int getline(char [], int);
main()
{
char line[256];
while(getline(line, 256) != EOF)
printf("you typed \"%s\"\n", line);
return 0;
}
The notation char [] in the function prototype for getline says that getline accepts as its first argument an array of char. When the program calls getline, it is careful to pass along the actual size of the array. (You might notice a potential problem: since the number 256 appears in two places, if we ever decide that 256 is too small, and that we want to be able to read longer lines, we could easily change one of the instances of 256, and forget to change the other one. Later we'll learn ways of solving--that is, avoiding--this sort of problem.)
6.4 Reading Numbers
The getline function of the previous section reads one line from the user, as a string. What if we want to read a number? One straightforward way is to read a string as before, and then immediately convert the string to a number. The standard C library contains a number of functions for doing this. The simplest to use are atoi(), which converts a string to an integer, and atof(), which converts a string to a floating-point number. (Both of these functions are declared in the header
#include
char line[256];
int n;
printf("Type an integer:\n");
getline(line, 256);
n = atoi(line);
Now the variable n contains the number typed by the user. (This assumes that the user did type a valid number, and that getline did not return EOF.)
Reading a floating-point number is similar:
#include
char line[256];
double x;
printf("Type a floating-point number:\n");
getline(line, 256);
x = atof(line);
(atof is actually declared as returning type double, but you could also use it with a variable of type float, because in general, C automatically converts between float and double as needed.)
Another way of reading in numbers, which you're likely to see in other books on C, involves the scanf function, but it has several problems, so we won't discuss it for now. (Superficially, scanf seems simple enough, which is why it's often used, especially in textbooks. The trouble is that to perform input reliably using scanf is not nearly as easy as it looks, especially when you're not sure what the user is going to type.)
No comments:
Post a Comment