Reading Data with scanf
Textbook Background
The C standard library allows us to read formatted data in much the same way we display it. The textbook provides background:
- King: Sections 3.2, 13.3, pages 42-46, 284-287
Review of scanf
The scanf function (in the stdio.h library)
directs the machine to read values for one or more variables from the
keyboard. For each desired value, scanf requires
- information in a format string indicates how the characters typed by the user will be interpreted (e.g., as an integer, as a double, etc.), and
- the address of a variable where the value read is to be stored.
Notes:
-
As with
printf, format information is collected together as an initial string parameter. -
Using our experience from previous work with functions and
parameters, parameters to
scanfinvolve reference parameters (e.g., with an ampersand &), so the functionscanfcan place data at the location of anintordouble.
When a user enters information into a program, the user types a sequence of characters. Sometimes this information is intended to be a string of characters, such as a name or an address. In other applications, a sequence of characters, such as 123.45, should be interpreted as a number.
When characters are to be considered as numbers, input can follow either of two basic forms:
-
The program can proceed in two steps:
- read the information as a sequence of characters
- convert the character sequence to a number
- the program can rely upon a library function to perform both steps as one logical operation.
The library function scanf is commonly used for the latter
approach. Using scanf involves several elements. The
basics of this work are illustrated in the following code segment:
double a, b;
int numAssigned;
numAssigned = scanf ("%lf", &a);
numAssigned = scanf ("%lf", &b);
As illustrated in this segment,
-
The first parameter for
scanfindicates the format of data to be read:-
%lffor double precision real numbers (lfstands for "long float") -
%ffor single precision real numbers (floating point numbers) -
%dfor decimal integers -
%cfor reading a character -
%sfor reading a string and%Ns, where N is a positive integer, for reading up to N characters
-
-
Use an ampersand
&before the variable to represent the "address" of the variable (the location where the value should be stored). -
When reading a number,
scanfskips initial whitespace (spaces, tabs, newline characters). -
After skipping whitespace,
scanfreads as long a string of digits as it can to obtain a number. Thus, if one enters123Walker
thescanfstatement will read the number as123;"Walker"is not part of a number, so reading of the number cannot proceed.numAssignedhas the value1, because one value was assigned (a). -
If
scanftries to read a number but encounters non-numeric data after whitespace, then reading stops and the number is not assigned a new value. -
The above code segment skips whitespace (if any), reads a first
number (up to whitespace or non-numeric data), and assigns the
number read to variable
a. This process then is repeated in reading variableb. -
If we had indeed entered
123Walkeras before, withahaving been assigned123, then the input would be left withWalkerwaiting to be read, which is not a number, sobwould not be assigned a new value andnumAssignedwould be0, since no values were assigned.
scanf allows the two reading operations above to be
combined within a single statement as follows:
double a, b;
int numAssigned;
numAssigned = scanf ("%lf%lf", &a, &b);
With this example, typing the input
123.4 56.7gives
numAssigned the value 2, since
both a and b would be assigned
to 123.4 and 56.7, respectively.
Beyond the identification of variables and formats for reading, the
scanf can specify other characters that must be part of
the input. For example, suppose a program is supposed to read hours
and minutes in the format hour:minutes:seconds, such as
12:34:56 or 5:8:27. In this setting, the
user is supposed to enter the colon character between integer numbers.
The following code segment would perform such a read operation:
int hr, min, sec;
int numAssigned;
numAssigned = scanf ("%d:%d:%d", &hr, &min, &sec);
To verify the input format was correct, we would
check numAssigned == 3.
Two Safety-Critical Notes
On Reading Strings
As both textbook readings point out, the %s conversion
specification requires a variable of
type char* to accompany it. As we
should now know well, a pointer to a character may refer to a sequence
of available memory addresses, but it does not convey any information
about how much memory is available. Thus, only using %s
puts your program at risk of causing serious damage through a buffer
overflow, because it does not tell scanf how much data it
may safely read.
Thus, you should never use the %s
conversion specification without an accompanying space limit. While we
strongly prefer to use
the #define preprocessor directive to
establish buffer sizes in a single, clear manner, this dogma does not
work well with a conversion string, which must be hard coded.
One way around this is to use a closely coupled pair
of #define directives, as in the
following example.
#include <stdio.h>
/* Length and format string for character buffers (plus a null terminator) */
#define MAXSTR 127
#define SCNSTR "%127s"
int
main (void)
{
int foo, bar;
char buf[MAXSTR+1]; /* Allocate an extra space for the null terminator */
int numAssigned;
/* C concatenates string literals */
numAssigned = scanf ("%d " SCNSTR " %d", &foo, buf, &bar);
if (numAssigned != 3) {
printf ("Invalid input\n");
return 1; /* Signal an error to caller */
}
printf ("%d %s %d\n",foo,buf,bar);
return 0;
} // main
This approach also is potentially poor practice, because a subsequent programmer may change only one of the two, but it's less likely to be missed than if a hard-coded value is strewn throughout the program source code.
It turns out we can use the preprocessor's function macro "stringification" to keep these values consistent. In our program's prefatory material, we can define the following.
/* Length of a mutable character buffer (string) */
#define MAXSTR 127
/* Stringify a literal without macro expansion */
#define STR(EXPR) #EXPR
/* Construct a size-limited scanf format string for a character buffer */
#define SCANSTR(LEN) "%" STR(LEN) "s"
Then, because the C compiler concatenates all these string literals
together, we can invoke scanf using the preprocessor
macro.
numAssigned = scanf ("%d " SCANSTR(MAXSTR) " %d", &foo, buf, &bar);
On Return Values
Note that scanf returns the number of input items
assigned, which could be fewer than expected (even zero). Thus, it is
generally useful (and highly recommended) to check the
return value to make sure that your program received the amount of
input it expected and take appropriate action if it did not.
Examples
We may discuss some of the following examples in class