1

Although it is an extremely simple question, I am writing a program that is getting stuck in an infinite "while" loop because the argument that breaks the loop is determined by a switch statement that requires user input. The problem is that the program never stops for input so it constantly chooses the default case, causing an infinite loop.

This is my code thus far:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    float nickels, dimes, quarters, total, price;
    char input;
    price = 1.00;
    total = 0;

    while(total < price){
        printf("n = nickel, d = dime, q = quarter: ");
        scanf("%c", &input);

        switch(input){
        case 'n': case 'N':
            total += 0.05;
            printf("\nTotal: %f\n", total);
            break;
        case 'd': case 'D':
            total += 0.10;
            printf("\nTotal: %f\n", total);
            break;
        case 'q': case 'Q':
            total += 0.25;
            printf("\nTotal: %f\n", total);
            break;
        default:
            printf("\nWrong input\n");
            break;
        }
    }

    printf("You're done! Total is %f", total);

    return 0;
}

I know this is an extremely simple question. I apologize for that. It could quite possibly be something I'm just overlooking.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
beckah
  • 1,543
  • 6
  • 28
  • 61
  • Do not use floats for currency - get rounding errors – Ed Heal Dec 11 '13 at 21:48
  • Could this be a "not gobbling up whitespace" issue? – Dennis Meng Dec 11 '13 at 21:49
  • 1
    Don't `scanf()`. **Just don't.** `fgetc()` or `fgets()` for user input. –  Dec 11 '13 at 21:50
  • 2
    `"%c"` change to `" %c"` – BLUEPIXY Dec 11 '13 at 21:50
  • 1
    @DennisMeng As always. Could we **please** remove `scanf()` & co. from C14? –  Dec 11 '13 at 21:50
  • 1
    @PhillipKinkade I know it doesn't exit the while loop, nor do i want it to. I want it to go back to the beginning of the while loop and wait for user input. – beckah Dec 11 '13 at 21:52
  • @BLUEPIXY worked like a charm! :) – beckah Dec 11 '13 at 21:53
  • @H2CO3, in doing so, do you have an recommendations for a logic structure? :) – beckah Dec 11 '13 at 21:55
  • @rsheeler "logic structure" in what sense? –  Dec 11 '13 at 21:56
  • @BLUEPIXY That's a good temporary fix, but I wouldn't suggest it as a *solution.* It only hides the real problem (i. e. the fact that OP is using `scanf()`). –  Dec 11 '13 at 21:56
  • @H2CO3 - also do you mean getc? I'm not reading from a file.. – beckah Dec 11 '13 at 21:57
  • @H2CO3 - As in when you said that my code "deserves" to be broken, do you mean that there is a better logic structure rather than a nested switch statement in a while loop for my purposes? :) – beckah Dec 11 '13 at 22:00
  • 1
    @rsheeler `getc()` if you like, or `fgetc(stdout)`, which is equivalent (as far as C is concerned, standard I/O streams **are** "files" -- that is, streams.) –  Dec 11 '13 at 22:00
  • 1
    @rsheeler Ah OK, I see! :) No, that part is not with which I have the problem - that's exclusively the `scanf()` part. But there definitely are alternatives which one may consider better practice, e. g. from the "separation of data and code" point of view. Consider, for example, making an array of values which acts as a "dictionary" or "map" from characters to money values. Also, you could use fixed-point arithmetics (i. e. integers) instead of floating-point to get rid off some really nasty rounding errors. –  Dec 11 '13 at 22:02
  • 1
    I just stumbled upon this question. Sorry I haven't done C for a while. Can anyone explain why adding that space before %c fixes the problem " %c". Is `scanf()` just badly written? – Naveed Dec 11 '13 at 22:10
  • 2
    @Naveed (and rsheeler): I think **[this](http://stackoverflow.com/questions/7166197/why-does-a-space-in-my-scanf-statement-make-a-difference)** post will answer your question. – ryrich Dec 11 '13 at 22:16
  • Apparently pennies aren't allowed in change? – RBerteig Dec 11 '13 at 23:14
  • @RBerteig Nope! Right now I'm trying to create a model for a soda machine or bus fair machine in C. Most that I've used don't take pennies. It wouldn't be hard to add a new case and variable though. Please be nice! – beckah Dec 11 '13 at 23:23
  • 1
    True, the vending industry hates pennies. Life is simpler if you round to a nickle, and I suspect they'd get rid of nickles too if they could. I haven't met a machine in a long time that accepted 50 cent pieces (or classic silver dollars) either, but the newer dollar coins are partly the product of the vending industry lobbying for better size for a dollar coin. – RBerteig Dec 11 '13 at 23:31
  • @RBerteig absolutely. i don't even know if the coin slots are big enough for 50 cent pieces! – beckah Dec 11 '13 at 23:37

1 Answers1

2

In short, scanf() as you've used it consumed the user's input character, but did not consume the newline he also had to type, leaving it for the next iteration of the loop to trip over. The easiest workaround is to just ignore newline characters from the user's input, by adding

    case '\n': break;

to the switch statement so that they are silently consumed rather than triggering the default case.

That said, here is a more verbose explanation of what is going on under the hood:

In general, I/O operations are more complicated then beginner books and courses make them out to be. The natural form of I/O that the C runtime library promotes has the benefit that it hides most of the details of access to data behind the flexible concept of a "stream".

A stream is usually buffered, meaning that reads and writes to a stream actually act on an in-memory buffer so that the expensive system calls that actually move data to and from memory can happen less often and on more bytes at a time. When processing a large file, this is a huge benefit, and makes it practical to process entire files apparently a character at a time.

A fully buffered stream is really inconvenient for interactive use, however, so a typical implementation will make the stdin, stdout, and stderr streams be "line buffered" when they are associated with an interactive terminal. When an output stream is line buffered, it flushes the buffer to the terminal when a newline is printed. When an input stream is line buffered, it fills the input buffer a line at a time.

That means in particular that the first call to scanf() (or getchar() or any other read operation on stdin) will apparently block execution until the user has typed a newline, then proceed to return what it is expected to return.

In your case, scanf("%c", &input); will block until the user typed a newline, then copy the first character typed to input, leaving any additional characters and the newline in the buffer. The next time around the loop, scanf("%c", &input); will take the next character from the buffer without waiting for any further input. We know there is at least one such character, because the first loop waited for say "d\n" to be typed but then consumed only the "d", leaving the "\n" for later.

The generally accepted best practice today would be to not use scanf() at all. It would be much better to use fgets() and then parse the entire line of input appropriately. For your example, you could read a line, then count all of the 'n', 'd', and 'q' occurrences. The details, of course, would depend on the specification of what you are supposed to implement.

I mentioned "buffered" and "line buffered" streams. There is a third kind of stream that is more suitable to interactive use. It is usually possible to make the stream itself be "unbuffered", such that every call to a stdio function takes effect immediately in the device or file. Unbuffered operation is only recommended for interactive programs, so to use it you should verify that stdin really is attached to a terminal and not redirected from a file. You also need to deal with the fact that the terminal device driver itself is almost certainly going to try to at minimum line buffer its input. There will be a system-specific way to put the terminal driver into a raw mode where each character types will cause an immediate return from the read(2) system call allowing a program to respond to individual keypresses without waiting for the carriage return key to be pressed. Actually configuring this mode of operation is tricky, platform specific, and well beyond the scope of this question.

RBerteig
  • 41,948
  • 7
  • 88
  • 128
  • I have another question (sorry!) I am working with an integer input within a loop by creating a bubble sort. My original problem was that the loop would not wait for input so I fixed this by using the c++ cin.ignore() function. However, the array[0] will always appear as 22; I have no idea way. it also will not fill in the remainder of the array unless i input 2 numbers for the first position or 0 index. This is my code thus far [link](http://codepad.org/YrBjahKI)I thought it might be related to some kind of input nuances – beckah Dec 15 '13 at 20:51
  • @rsheeler I'd strongly encourage you to ask a separate Question, and to include the relevant code fragments in the question. You will get a far better response, and from people who actually use C++ which I personally do not. – RBerteig Dec 17 '13 at 01:43