The fundamental question here is, Do you want to use scanf
?
scanf
is everyone's favorite library function for easily reading in values. scanf
has an input specifier, %d
, for reading in integers.
And it has a different input specifier, %s
, for reading in arbitrary strings.
But scanf
does not have any single input specifier that means, "Read in an integer as an integer if the user types a valid integer, but if the user types something like "q"
, have a way so I can get my hands on that string instead."
Unless you want to move mountains and implement your own general-purpose input library from scratch, I think you have basically three options:
- Use
scanf
with %d
to read integers as integers, but check scanf
's return value, and if scanf
fails to read an integer, use that failure to terminate input.
- Use
scanf
with %s
to read the user's input as a string, so you can then explicitly test if it's a "q"
or not. If not, convert it to an integer by hand. (More on this below.)
- Don't use
scanf
at all. Use fgets
to read the user's input as a whole line of text. Then see if it's a "q"
or not. If not, convert it to an integer by hand.
Number 1 looks something like this:
while(some loop condition) {
printf("enter next integer, or 'q' to quit:\n");
if(scanf("%d", &i) != 1) {
/* end of input detected */
break;
}
do something with i value just read;
}
The only problem with this solution is that it won't just stop if the user types "q"
, as your original problem statement stipulated. It will also stop if the user types "x"
, or "hello"
, or control-D, or anything else that's not a valid integer. But that's also a good thing, in that your loop won't get confused if the user types something unexpected, that's neither "q"
nor a valid integer.
My point is that explicitly checking scanf
's return value like this is an excellent idea, in any program that uses scanf
. You should always check to see that scanf
succeeded, and do something different if it fails.
Number 2 would look something like this:
char tmpstr[20];
while(some loop condition) {
printf("enter next integer, or 'q' to quit:\n");
if(scanf("%19s", tmpstr) != 1) {
printf("input error\n");
exit(1);
}
if(strcmp(tmpstr, "q") == 0) {
/* end of input detected */
break;
}
i = atoi(tmpstr); /* convert string to integer */
do something with i value just read;
}
This will work well enough, although since it uses atoi
it will have certain problems if the user types something other than "q" or a valid integer. (More on this below.)
Number 3 might look like this:
char tmpstr[20];
while(some loop condition) {
printf("enter next integer, or 'q' to quit:\n");
if(fgets(tmpstr, 20, stdin) == NULL) {
printf("input error\n");
exit(1);
}
if(strcmp(tmpstr, "q\n") == 0) {
/* end of input detected */
break;
}
i = atoi(tmpstr); /* convert string to integer */
do something with i value just read;
}
One thing to note here is that fgets
includes the newline that the user typed in the string it returns, so if the user types "q" followed by the Enter key, you'll get a string back of "q\n"
, not just "q"
. You can take care of that either by explicitly looking for the string "q\n"
, which is kind of lame (although it's what I've done here), or by stripping the newline back off.
Finally, for both #2 and #3, there's the question of, what's the right way to convert the user's string to an integer, and what if it wasn't a valid integer? The easiest way to make the conversion is to call atoi
, as my examples so far have shown, but it has the problem that its behavior on invalid input is undefined. In practice, it will usually (a) ignore trailing nonnumeric input and (b) if there's no numeric input at all, return 0. (That is, it will read "123x"
as 123, and "xyz"
as 0.) But this behavior is not guaranteed, so these days, most experts recommend not using atoi
.
The recommended alternative is strtol
, which looks like this:
char *endp;
i = strtol(tmpstr, &endp, 10); /* convert string to integer */
Unlike atoi
, strtol
has guaranteed behavior on invalid input. Among other things, after it returns, it leaves your auxiliary pointer endp
pointing at the first character in the string it didn't use, which is one way you can determine whether the input was fully valid or not. Unfortunately, properly dealing with all of the ways the input might be invalid (including trailing garbage, leading garbage, and numbers too big to convert) is a surprisingly complicated challenge, which I am not going to belabor this answer with.