15

Here is some C code trying simply to prevent the user from typing a character or an integer less than 0 or more than 23.

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

int main(void)
{
    const char *input;
    char *iPtr;
    int count = 0;
    int rows;

    printf("Enter an integer: ");
    scanf("%s", input);
    rows = strtol(input, &iPtr, 0);
    while( *iPtr != '\0') // Check if any character has been inserted
    {
        printf("Enter an integer between 1 and 23: ");
        scanf("%s", input);
    }
    while(0 < rows && rows < 24) // check if the user input is within the boundaries
    {
        printf("Select an integer from 1 to 23: ");
        scanf("%s", input);
    }  
    while (count != rows)  
    {  
        /* Do some stuff */  
    }  
    return 0;  
}

I made it halfway through and a small push up will be appreciated.

Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
7kemZmani
  • 658
  • 1
  • 8
  • 21
  • I've noticed an obvious problem in third scanf command, it should be scanf("%i", &rows); but still the code is broken :( – 7kemZmani Dec 31 '12 at 08:54
  • Have you considered allocating memory for those `scanf` calls? as it stands now, they're reading into the address being held in an uninitialized pointer (`input`) which is undefined behavior. I'm pretty sure if its `int` values your looking for you should be using `%d` and scanning into the address of an `int` variable. Also, check the return values of your `scanf` calls, which will tell you how many fields were *successfully* obtained. – WhozCraig Dec 31 '12 at 09:12
  • I changed the 'input' pointer into an array 'char input[100];' – 7kemZmani Dec 31 '12 at 09:26
  • Why are you reading this into a text buffer *at all* ?? You're looking for an integer value in `[0..23]` correct? Just scan to an `int` and check to for a successful parse and a value in-range, unless there is some special characters you're also interested in getting. Perhaps reading more about [`scanf()`](http://www.cplusplus.com/reference/cstdio/scanf/) may be warranted? – WhozCraig Dec 31 '12 at 09:34
  • there are two types of inputs I do not want the user to enter, characters and outrange integers and this is why I used text buffer. – 7kemZmani Dec 31 '12 at 09:44
  • @AbdulelahAl-Jeffery: in this case you can use `scanf("%d")` to get your integer and if the user enter no numeric characters then you can scanf with `scanf("%s)` again in order to free your `stdin` before the next `scanf("%d")`. Refer to my anwer I updated it with this – MOHAMED Dec 31 '12 at 10:04
  • @AbdulelahAl-Jeffery: enter some example of your inputs – MOHAMED Dec 31 '12 at 10:34
  • remove the `while (count != rows);`. this is blocking your program – MOHAMED Dec 31 '12 at 10:35
  • Enter an integer from 1 to 23: 123asd (invalid), erwea(invalid), -823 (invalid), 30 (invalid), 8 (valid) – 7kemZmani Dec 31 '12 at 10:37
  • @AbdulelahAl-Jeffery: I updated the code in my answer. and I tested it and it works – MOHAMED Dec 31 '12 at 11:18
  • Yes that works but it seems like if there is something wrong here scanf("%[^\n]", input)?! as if the editor expect something after % symbol?!!! would you explain this pice only please. thank you (Jazak Allah Keer). – 7kemZmani Dec 31 '12 at 12:03
  • `"%[^\n]"` means: expecting string input containing what ever charcters except `"\n"` (newline). That means read and clean the whole content of the stdin (beraka Allah fik) – MOHAMED Dec 31 '12 at 13:27
  • @AbdulelahAl-Jeffery I used a `clean_stdin` function instead of the `scanf("%[^\n]",input)` inorder to avoid buffer overflow of `input`. Please refer to the answer to see the update – MOHAMED Jan 02 '13 at 08:21
  • @MohamedKALLEL OK, now it becomes clearer to me. One thing I noticed was you got ride of `input` array and replace it with `c` of char type?! is it because we want to save as much memory as possible? – 7kemZmani Jan 04 '13 at 00:36
  • if I were you, I won't put more than three conditions in `while` loop ; I would rather break it down into more than one and print the appropriate message that has to do with the invalid user-input. I think this will improve the code readability as will as user experience. you may correct me if I'm wrong. Thank you brother. – 7kemZmani Jan 04 '13 at 01:11
  • @AbdulelahAl-Jeffery There is a minor risk in the `scanf("%[^\n]", input)` which is the case if the user enter a string bigger than the input buffer size so you will get a buffer overflow. So that's why I replaced this by a `while` loop and `getchar()`. `getchar()` allows to read from stdin 1 character. and if you repeated the `getchar()` many times until you get `\n` you will get the stdin cleaned – MOHAMED Jan 04 '13 at 08:24
  • @AbdulelahAl-Jeffery Concerning the conditions of the `while` loop. I updated my answer for more explanation. Please refer to the answer. You are welcome Brother – MOHAMED Jan 04 '13 at 09:35

6 Answers6

39

Use scanf("%d",&rows) instead of scanf("%s",input)

This allow you to get direcly the integer value from stdin without need to convert to int.

If the user enter a string containing a non numeric characters then you have to clean your stdin before the next scanf("%d",&rows).

your code could look like this:

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

int clean_stdin()
{
    int c;
    while ((c = getchar()) != '\n' && c != EOF)
        ;
    return 1;
}

int main(void)  
{ 
    int rows =0;  
    char c;
    do
    {  
        printf("\nEnter an integer from 1 to 23: ");
  
    } while (((scanf("%d%c", &rows, &c)!=2 || c!='\n') && clean_stdin()) || rows<1 || rows>23);

    return 0;  
}

Explanation

1)

scanf("%d%c", &rows, &c)

This means expecting from the user input an integer and close to it a non numeric character.

Example1: If the user enter aaddk and then ENTER, the scanf will return 0. Nothing capted

Example2: If the user enter 45 and then ENTER, the scanf will return 2 (2 elements are capted). Here %d is capting 45 and %c is capting \n

Example3: If the user enter 45aaadd and then ENTER, the scanf will return 2 (2 elements are capted). Here %d is capting 45 and %c is capting a

2)

(scanf("%d%c", &rows, &c)!=2 || c!='\n')

In the example1: this condition is TRUE because scanf return 0 (!=2)

In the example2: this condition is FALSE because scanf return 2 and c == '\n'

In the example3: this condition is TRUE because scanf return 2 and c == 'a' (!='\n')

3)

((scanf("%d%c", &rows, &c)!=2 || c!='\n') && clean_stdin())

clean_stdin() is always TRUE because the function return always 1

In the example1: The (scanf("%d%c", &rows, &c)!=2 || c!='\n') is TRUE so the condition after the && should be checked so the clean_stdin() will be executed and the whole condition is TRUE

In the example2: The (scanf("%d%c", &rows, &c)!=2 || c!='\n') is FALSE so the condition after the && will not checked (because what ever its result is the whole condition will be FALSE ) so the clean_stdin() will not be executed and the whole condition is FALSE

In the example3: The (scanf("%d%c", &rows, &c)!=2 || c!='\n') is TRUE so the condition after the && should be checked so the clean_stdin() will be executed and the whole condition is TRUE

So you can remark that clean_stdin() will be executed only if the user enter a string containing non numeric character.

And this condition ((scanf("%d%c", &rows, &c)!=2 || c!='\n') && clean_stdin()) will return FALSE only if the user enter an integer and nothing else

And if the condition ((scanf("%d%c", &rows, &c)!=2 || c!='\n') && clean_stdin()) is FALSE and the integer is between and 1 and 23 then the while loop will break else the while loop will continue

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
MOHAMED
  • 41,599
  • 58
  • 163
  • 268
  • For starters, there is no check to see if whatever `scanf()` did (if anything) actually populated `rows`. Therefore you're potentially checking an indeterminate value. you should at least validate that `scanf()` returned 1. – WhozCraig Dec 31 '12 at 09:17
  • I used the `clean_stdin` function instead of the `scanf("%[^\n]",input)` inorder to avoid buffer overflow of `input` – MOHAMED Jan 02 '13 at 08:20
  • can you make this also repeat when the user only presses enter? – nyxaria Aug 12 '17 at 12:23
3
#include <stdio.h>
main()
{
    char str[100];
    int num;
    while(1) {
        printf("Enter a number: ");
        scanf("%[^0-9]%d",str,&num);
        printf("You entered the number %d\n",num);
    }
    return 0;
}

%[^0-9] in scanf() gobbles up all that is not between 0 and 9. Basically it cleans the input stream of non-digits and puts it in str. Well, the length of non-digit sequence is limited to 100. The following %d selects only integers in the input stream and places it in num.

1

You could create a function that reads an integer between 1 and 23 or returns 0 if non-int

e.g.

int getInt()
{
  int n = 0;
  char buffer[128];
  fgets(buffer,sizeof(buffer),stdin);
  n = atoi(buffer); 
  return ( n > 23 || n < 1 ) ? 0 : n;
}
AndersK
  • 35,813
  • 6
  • 60
  • 86
1
char check1[10], check2[10];
int foo;

do{
  printf(">> ");
  scanf(" %s", check1);
  foo = strtol(check1, NULL, 10); // convert the string to decimal number
  sprintf(check2, "%d", foo); // re-convert "foo" to string for comparison
} while (!(strcmp(check1, check2) == 0 && 0 < foo && foo < 24)); // repeat if the input is not number

If the input is number, you can use foo as your input.

Yonggoo Noh
  • 1,811
  • 3
  • 22
  • 37
0

You will need to repeat your call to strtol inside your loops where you are asking the user to try again. In fact, if you make the loop a do { ... } while(...); instead of while, you don't get a the same sort of repeat things twice behaviour.

You should also format your code so that it's possible to see where the code is inside a loop and not.

Mats Petersson
  • 126,704
  • 14
  • 140
  • 227
-1

this is a reply to prevent users from entering wrong data types

If you're dealing with just numbers declare

char catchpan;

and then right after your scanf for a number input put

scanf("%c", &catchpan);

its not a complete fix but if you have code that handles out of bounds stuff (assuming zero isn't a part of the bounds) it'll clear right up.

it ain't perfect and it ain't the general solution.

Harry Mu
  • 111
  • 6
  • So you're answering a closed question on another question? That is - well - simply not the right way to do it. :) – klutt Aug 22 '22 at 18:23
  • This does not provide an answer to the question. Once you have sufficient [reputation](https://stackoverflow.com/help/whats-reputation) you will be able to [comment on any post](https://stackoverflow.com/help/privileges/comment); instead, [provide answers that don't require clarification from the asker](https://meta.stackexchange.com/questions/214173/why-do-i-need-50-reputation-to-comment-what-can-i-do-instead). - [From Review](/review/late-answers/32510098) – lee-m Aug 24 '22 at 19:08