1

When I enter a letter the loop runs infinitely. Does a letter store as a zero when it is input as an int? How can I reject a non digit answer, just I have rejected an answer outside the range?

int main(int argc, const char * argv[]) {
// insert code here...

int categoryToScore;
int categoryScores = 6;

printf("Enter category to save score: ");
scanf("%d", &categoryToScore);

while (categoryToScore >= categoryScores || categoryToScore <= 0) {
    printf("Error: invalid command. Enter 1-5 to save to an unused category\n");
    printf("Enter category to save score: ");
    scanf("%d", &categoryToScore);
}

return 0;

}

Just for background

I want to:

  1. print a request an input that is between 1 and an upper bound
  2. scanf for the input
  3. check if the input is of a correct type and within the correct range
  4. if it isn't then print an error message and go back to 1.
  5. if it is then proceed
  • 1
    Scanf returns a value - the number of successfully scanned elements -- in addition to scribbling into whatever its arguments point to. If you check the return value when you enter something that's not a valid number, it ought to be 0. – Mark Plotnick Feb 05 '17 at 18:13
  • 1
    Also, when `scanf()` does fail due to invalid input, you'll have to clear out (read and discard) the invalid characters somehow before trying again. – Dmitri Feb 05 '17 at 18:19
  • so are you saying that when an invalid character (a letter) is input, then it is stored, and scanf reads it, but then as soon as the loop comes back to scanf it reads the same one again? And to avoid this i need to clear the space where scanf is checking, so that it needs another inputbefore it can scan? – Alex Butterfield Feb 05 '17 at 22:07

2 Answers2

2

You are asking scanf to read a number from standard input. Scanf finds a non-digit character in the standard input and does not remove it from the standard input. Scanf fails and returns 0 (the number of fields successfully processed).

The next time you call scanf, it finds the same character at the start of standard input. So the process repeats indefinitely.

One solution is to read stdin one character at a time.

Another solution is to read (and discard) the one character from stdin before calling scanf again.

int main(int argc, const char * argv[]) {
// insert code here...

int categoryToScore;
int categoryScores = 6;
int scantRetVal;

printf("Enter category to save score: ");
scantRetVal = scanf("%d", &categoryToScore);
if (scantRetVal != 1) {
    getchar(); // read and discard one character from stdin
    categoryToScore = 0;
}

while (categoryToScore >= categoryScores || categoryToScore <= 0) {
    printf("Error: invalid command. Enter 1-5 to save to an unused category\n");
    printf("Enter category to save score: ");
    scantRetVal = scanf("%d", &categoryToScore);
    if (scantRetVal != 1) {
        getchar(); // read and discard one character from stdin
        categoryToScore = 0;
    }
}

return 0;
}
George
  • 2,436
  • 4
  • 15
  • 30
  • Thanks for reply. I am new to 'getc()' and I'm receiving an error when I use this approach. "Too few arguments to function call, expected 1, have 0" – Alex Butterfield Feb 05 '17 at 21:23
  • @AlexButterfield Use getchar() instead of getc(). I've corrected the answer. – George Feb 06 '17 at 00:20
0

Rather than fix this particular program I will show how to solve ANY similar problem using a concept called an "exit condition".

The idea of an exit condition is that you have an infinite loop and it has various exit conditions. Often there are two exit conditions: one for success and one for an error.

while( true ){   /* infinite loop */
   char c = ...  /* get the character you want */
   if( c < '0' || c > '9' ){ 
      printf( "invalid character, not a digit\n" );
      continue; // get another character
   }
   ... /* do whatever you with valid data */
   if( c == '3' ) break; /* your exit condition, whatever it is */
   if( c == '7' ) exit(0); /* exit the whole program */
}

Note: If you are accepting free form input (numbers and strings), scanf is probably not a good idea. scanf accepts very specific, well-formatted input. So if you ask for a %d, then there better be a %d (decimal number) in the input or you will have problems.

For example, if you accept numbers and strings, you should take everything as strings using fgets or something like that.


Here is a complete program that does what you want:

#include <stdio.h>
#include <stdbool.h>

int main(int argc, const char * argv[]) {
  int iMaxScore = 6;
  int charInput = 0;
  int iInputValue = 0;

  while( true ){
      printf("Enter category to save score: ");
GetInput:
      charInput = getchar();
      if( charInput == 10 || charInput == 13 ) goto GetInput; /* ignore enter key */
      if( charInput == 'q' ) break;
      if( charInput < '0' || charInput > '9' ){
          printf( "invalid entry, not a digit %d\n", charInput );
          break;
      }
      iInputValue = charInput - '0';
      if( iInputValue > iMaxScore ){
          printf( "Error, input value exceeds maximum category %d\n", iMaxScore );
          continue; /* try again */
      }
      printf( "you entered category %d\n", iInputValue );
      /* continue ... */
  }

  return 0;
}
Tyler Durden
  • 11,156
  • 9
  • 64
  • 126
  • I tried this approach just now. i had an error with the 'exit(0)' and so I tried without that, and I'm getting the same infinite loop behavior when I enter a letter instead of a number – Alex Butterfield Feb 05 '17 at 22:05
  • @AlexButterfield That is probably because you are using scanf(). scanf() is usually used to read structured input, whole records, not individual characters, so it is waiting until it gets a "record" which in the case of %d would be a decimal integer. If you just need characters maybe try using getchar(). There is an SO question about this: http://stackoverflow.com/questions/2507082/getc-vs-getchar-vs-scanf-for-reading-a-character-from-stdin – Tyler Durden Feb 05 '17 at 22:13
  • @AlexButterfield I realize there are some nuances with dealing with newline characters and stuff like that, so to get you started I created an entire program. Notice how I use an infinite loop and then just have various exit conditions, that is a good way to do things. – Tyler Durden Feb 05 '17 at 22:59
  • This `goto` in a `while` loop and your indentation style, I know we are not in code review but that look strange. Why not use `do {} while (expression)`? and you don't handle `EOF` and use magic number. – Stargateur Feb 05 '17 at 23:05
  • @Stargateur Its not the most elegant solution, but it is straightforward and easy to understand. The benefit of this kind of solution is that it is standardized. I write all my loops this this way, with a while(true) and multiple exit conditions. It greatly simplifies things. Also, considering that this guy is a beginner, that's what he needs: a standard, easy way to do any loop. – Tyler Durden Feb 05 '17 at 23:10
  • Please use `'\n'` instead of 10. – Roland Illig Feb 05 '17 at 23:27