📄 gets_flush2.html
字号:
<html><!-- Mirrored from c-faq.com/stdio/gets_flush2.html by HTTrack Website Copier/3.x [XR&CO'2008], Sat, 14 Mar 2009 08:01:06 GMT --><head><title>fflush vs gets</title></head><body>From: Steve Summit<br>Subject: Re: <TT>fflush</TT> vs <TT>gets</TT><br>Date: 2000/02/12<br>Message-ID: <clcm-20000211-0019@plethora.net><br>Newsgroups: comp.lang.c,comp.lang.c.moderated<p>Peter S. Shenkin wrote:<br>> Why would you possibly want to discard the user's input,<br>> and how in the world would you know what part to discard?<p>It's likely that you've never tried to call <TT>scanf</TT> and <TT>gets</TT> in thesame program. If you haven't, you're blissfully unaware of thismessy little problem.<p>Suppose you write this trivial program:<p><pre> #include <stdio.h> int main() { int i; char string[80]; printf("enter an integer:\n"); scanf("%d", &i); printf("enter a string:\n"); gets(string); printf("You typed %d and \"%s\"\n", i, string); return 0; }</pre><p>Looks perfectly straightforward, right? But if you compile andrun it (which I encourage you to do, if you're still unfamiliarwith this problem), you'll see something weird, and you willfind yourself (I guarantee it) asking <a href="scanfinterlace.html">question 12.18</a> in thecomp.lang.c FAQ list: “I'm reading a number with <TT>scanf %d</TT> andthen a string with <TT>gets()</TT>, but the compiler seems to be skippingthe call to <TT>gets()</TT>!” (We'll have more to say later about using<TT>gets</TT> at all, but hold that thought.)<p>Let's look very carefully at what happens. The first <TT>printf</TT> callprints the first prompt, and we type “<TT>123</TT>” and hit the returnkey. The input stream now contains<pre> 1 2 3 \n</pre>Now we hit the <TT>scanf</TT> call, and <TT>scanf</TT> sees the format string <TT>%d</TT>indicating that we've asked it to read an int. It reads thecharacter '<TT>1</TT>' from the input stream and says to itself “okay,that's a digit, so it can be part of an int.” It reads thecharacters '<TT>2</TT>' and '<TT>3</TT>', and they're digits, too. The nextcharacter is '<TT>\n</TT>', which is not a digit. So <TT>scanf</TT> does twothings:<ol><p><li>It terminates processing of the <TT>%d</TT> directive; it nowknows that the complete integer it has read is 123;it stores this value as requested into the locationof the variable <TT>i</TT>.<p><li>(This is the key point.) It pushes the <TT>\n</TT> character,which terminated the digit string but which it didn'totherwise use, back onto the input stream.</ol><p>So after the first <TT>scanf</TT> call, the input stream contains<pre> \n</pre>Now the second <TT>printf</TT> call prints the second prompt. Suppose wetype “<TT>test</TT>” and hit the enter key. The input stream now contains<pre> \n t e s t \n</pre>So now we come to the <TT>gets</TT> call. <TT>gets</TT>'s job, of course, is toread one line of input, up to the next newline. But the veryfirst character gets sees is a <TT>\n</TT>, so as far as it's concernedit's just read a blank line. It returns that blank line (anempty string, since <TT>gets</TT> always deletes the newline from theline before returning it to you), and the input stream is leftcontaining<pre> t e s t \n</pre>The third <TT>printf</TT> call prints the (somewhat surprising) resultsof the two inputs, and the program terminates, with the string“test” and the final newline unconsumed.<p>If this still isn't quite making sense, try running the programagain, and typing something more than a number in response to thefirst prompt. That is, try typing “<TT>123 abc</TT>” or “<TT>123abc</TT>”, andthen hitting the return key, when asked to “enter an integer”.<p>(Actually, the above description isn't quite right. No matterwhat you type on the first line, the input stream after the firstscanf call still contains a \n, so the <TT>gets</TT> call reads it rightaway, without pausing for you to enter anything more. So youdon't really get a chance to type “test” at all. To answer theFAQ list's question another way, the problem is not that thecompiler somehow “skips” the <TT>gets</TT> call, the problem is that the<TT>gets</TT> call satisfies its need for input in an unexpected way, andskips the part about pausing the program to wait for you to typeanything more.)<p>With the scenario above as background, we can now answer yourquestion, “Why would you possibly want to discard the user'sinput?” For better or worse, many beginning programmers use<TT>scanf</TT> to read numbers and <TT>gets</TT> to read strings. This is in largepart, of course, because these functions are taught early in manybooks and programming classes. And this, in turn, is becausethese functions are superficially and seductively attractive;they seem very easy and convenient to use. But they don't playat all well together (plus they have some other problems, whichwe'll get to).<p>When the beginning programmer writes a program like the aboveand discovers that it doesn't work quite right, he is likely toreceive the handwaving explanation (from the instructor or thetextbook author) that there is some “garbage left behind on theinput stream by <TT>scanf</TT>”. (We, who understand the situation moreaccurately, now know that the “garbage” is, in the example wewalked through, simply the <TT>\n</TT> that resulted from our hitting thereturn key after entering the requested number.) To allowfurther input to proceed as expected, these instructors andauthors go on to explain, the “garbage” must be “discarded”.One all-too-popular (and, again, superficially attractive) way ofdoing this is to call <TT>fflush(stdin)</TT>, despite the fact that thisis a misguided application of the standard <TT>fflush</TT> function, anapplication that is not guaranteed to (and in fact most certainlydoes not) work everywhere. But it “works” under a large numberof popular PC C compilers, so the “idiom” is, unfortunately,widespread.<p>What's the right solution? It's extremely easy to get stuckon the fact that <TT>fflush(stdin)</TT>, for some presumably stupidand pedantic reason, is not guaranteed to work everywhere.One then starts casting about looking for a “portable”replacement. The problem is that, depending on precisely whatone is trying to do, there are quite a few different tacks onemight take in attempting to write some well-defined or portablecode to “discard garbage from stdin”. (In the general case, asyou correctly ask, “How in the world would [one] know what partto discard?”)<p>If the definition of the “garbage from stdin” that we're tryingto discard is “input from the previous line which wasn't consumedby <TT>scanf</TT>”, it turns out that there are a couple of not entirelyunreasonable approaches. We could write the loop<p><pre> while((c = getchar()) != '\n' && c != EOF) /* discard the character */;</pre><p>to read and discard characters up to the next newline. (Noticethat the comment “<TT>/* discard the character */</TT>” in this fragmentdoes not stand in for some code I didn't write yet -- it standsin for some code I deliberately didn't write at all. The body ofthe loop is empty; we do nothing with the characters we'rereading, thus discarding them. The <TT>\n</TT> which terminates theloop is discarded, too.)<p>Since that loop would clutter our code pretty badly if we hadto interpose it after every <TT>scanf</TT> call, we might encapsulate itinto a function we could call, perhaps called “flushline” orsomething. Or, recognizing that “read characters up to anewline” is precisely what the Standard functions <TT>gets</TT> and <TT>fgets</TT>already do, we might simply interpose calls to <TT>gets</TT> or <TT>fgets</TT>,reading into a dummy buffer which we ignore (and hence discard),perhaps accompanied by comments explaining that these dummy callsare to “get rid of the garbage left behind by scanf”. But theseare still ugly, unclean, unsatisfying solutions. It won't belong before one of our scanf calls, for some reason, <em>does</em>consume a newline character after all, such that our compensating“read and discard characters up to the next newline” code willread and discard the next line of input, a real line of input,which will then be lost to the input-reading code which expectedit. We could try to predict which <TT>scanf</TT> calls will and which<TT>scanf</TT> calls won't leave “garbage behind”, and sprinkle “flushline”calls after only those <TT>scanf</TT> calls which need them, but this is ahit-or-miss proposition, and a later reader will never be able tounderstand precisely what we're up to. There's got to be abetter way.<p>The “better way”, as indicated in the FAQ list, is either toabandon <TT>scanf</TT> entirely, or to use it exclusively. If yourinput is intended to be line-based, you can read <em>all</em> linesof input as strings, using <TT>fgets</TT> or the like, and for thosethat were supposed to be numeric, convert the strings to numbersusing functions like <TT>atoi</TT>, <TT>strtol</TT>, <TT>atof</TT>, or maybe even <TT>sscanf</TT>.(This is the general approach I recommend.) Or, since theproblem is that it's <TT>scanf</TT> that does Not Play Well With Others,you can switch to a scheme where you use <TT>scanf</TT> for everything,using it to read your strings, too (with <TT>%s</TT> or the like).<p>Finally, I should add a couple of postscripts. It turns outthat <TT>scanf</TT> has other problems besides the fact that it tends toleave little undigested “surprises” on the input stream, so thereare other reasons to consider abandoning it. And, of course,as discussed at length in comp.lang.c of late, <TT>gets</TT> has a fatalproblem which counterindicates its use for much of anything.<p>-- <address><a href="http://www.eskimo.com/~scs/">Steve Summit</a><br><a href="mailto:scs@eskimo.com">scs@eskimo.com</a></address><br>Programming Challenge #5: Love your abstractions.<br>See <a href="http://www.eskimo.com/~scs/challenge/">http://www.eskimo.com/~scs/challenge/</a>.</body><!-- Mirrored from c-faq.com/stdio/gets_flush2.html by HTTrack Website Copier/3.x [XR&CO'2008], Sat, 14 Mar 2009 08:01:06 GMT --></html>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -