The problem of reading user input safely, limiting the input to a certain set of "allowed" inputs, while cleanly disregarding "disallowed" inputs, can be a surprisingly tricky one.
Also surprising, perhaps, is how poor a function scanf
is for performing this task, and how difficult it is to solve the problem completely using any algorithm built around scanf
.
You asked why this code didn't "directly do it the easy way". By "the easy way" I assume you mean something like
scanf("%d", &choice);
The problem here is that, yes, it can be remarkably difficult to proceed correctly if the user types some non-numeric input.
There are two general avenues to take when trying to handle the possibility that the user types something wrong:
- Continue to call
scanf("%d")
to read the input, but then, try to patch things up if scanf
fails. (Obviously the first step here is to check scanf
's return value.)
- Read a line of input, as text, using something other than
scanf
. Then attempt to validate that line, and convert it to the desired form.
In my opinion, there is only one choice here, and it is #2. This answer will become far too long if I discuss all the reasons, but the bottom line is that approach #1 is futile. The scanf
function has one virtue and one virtue only, and that is that a call like scanf("%d", &choice)
is indeed very simple. But the error handling is almost useless. By the time you've built up a reasonable amount of error handling around it, the amount of work you'll have had to do is about three times as much as for approach #2, and you still won't have fully satisfactory results.
So most experienced C programmers will agree that #2 is the only viable approach in the long run. There's a central question advising on good ways of doing input using something other than scanf
.
The problem with the code you've posted, IMO, is that it manages to combine the worst of both worlds. It does try to read a line of input as text, then process it later, but the way it reads that line of input is with... the dreaded scanf
! And despite trying to be careful in several other ways, this code doesn't even check scanf
's return value, so there are some classic problems (like premature EOF) that this code is still vulnerable to.
This code also contains a mysterious extra call to getchar
, which is typical of scanf
-using code, since stray newlines are almost always a problem.
This code also uses %[...]
, which is my least favorite scanf
format. As I said, scanf
's only virtue is simplicity, but a locution like "%[^\n]"
is anything but simple. Yes, I know what it does, but IMO it completely defeats the purpose of using scanf
for dirt-simple (if less than robust) user input.
But, yes, the primary intent of writing the code this way is probably "so that if the user enters a character instead of a number, the code doesn't malfunction". The code reads a line of text, as text, then attempts to convert the text to a number. You asked what the atoi
function does with alphabetic input, and the answer is that (most of the time, anyway) it quietly returns 0. Since 0 isn't a valid input, this code will reject it, so in that sense it works.
To improve this function, the first thing to do would be to replace the calls to scanf
and getchar
with fgets
. The next thing to do would be to replace atoi
with strtol
. And then it wouldn't be too bad.