Kristina
Kristina

Reputation: 271

CMD will be frozen and will not respond anymore when I input character as input

Please help me to fix my code. CMD will be frozen and will not respond anymore When I entered character as input. I think the problem is %i in scanf("%i",&c); or while(loop==0).

I know the c should be int but how can I change my code to a better code. I mean if user enter character, CMD shows the "default" in "switch case"

int GetMenuChoice()
{
    int c;
    int loop=0;
    while(loop==0)
    {

        scanf("%i",&c);
        if(c>5 || c<1)
        printf("You did not entered between 1 to5.Try Again\n");
        else
        switch(c) {
            case 1:
               printf("You enterd valid choice 1");
               loop=1;
               break;
            case 2:
               printf("You enterd valid choice 2");
               loop=1;
               break;
            case 3:
               printf("You enterd valid choice 3");
               loop=1;
               break;
            case 4:
               printf("You enterd valid choice 4");
               loop=1;
               break;
            case 5:
               printf("You enterd valid choice 5");
               loop=1;
               break;
            default:
                 printf("You have enterd invalid choice,Try Again\n");
               break;
        }
    }
}

int main()
{

    printf("Welcome To the sorting Program\n\t1)Title\n\t2)Rank\n\t3)Date\n\t4)Stars\n\t5)Like\n\n\n");
    printf("Please Enter your choice between 1 to 5:\n\t");
    GetMenuChoice();

    return 0;
}

Upvotes: 0

Views: 82

Answers (2)

AndersK
AndersK

Reputation: 36092

As far as I can see from your code there is no reason to convert to an integer, you can read the menu alternatives as characters instead.

Some test code:

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

int main(/*int argc, char* argv[]*/)
{
  char c;
  do
  {
    c = getchar();
    if (!isspace(c)) /* to ignore \n \r and such */
    {
      if ( c >= '1' && c <= '5')
      {
        printf( "alt. %c\n", c);
        /* here you can place your switch or convert to int and return */
        /* int n = c - '0'; */
      }
      else
      {
        printf("invalid option: %c\ntry again 1-5:", c);
      }
    }
  }
  while (c < '1' || c > '5');
  
 return 0;
}

or replace the if statement with your switch although I would instead move your handling of the returned value outside that function and do a function specifically to return a valid value.

Upvotes: 0

David C. Rankin
David C. Rankin

Reputation: 84642

You have a large number of things that "are just not 'quite' right". Let's start with scanf where people generally fall into the trap of failing to account for all characters in the input buffer (e.g. stdin) after an invalid entry is made.

To properly use scanf you must Always check the return! Always. scanf returns the match count representing the number of items successfully converted and assigned, which can be "fewer than provided for, or even zero in the event of an early matching failure." Further "EOF is returned if the end of input is reached before either the first successful conversion or a matching failure occurs."

Of most import is understanding "If processing of a directive fails, no further input is read, and scanf() returns." (leaving all characters from the point of failure to the end-of-line in the input buffer)

It is up to you to remove any characters that remain in the input buffer after a failure to prevent endlessly looping over the same character. (therein was your primary problem)

To remove all characters remaining in stdin after a failure occurs, you can simply use getchar() to read until a '\n' or EOF is encountered. You can write a simple function e.g.

void empty_stdin()
{
    int c = getchar();

    while (c != '\n' && c != EOF) 
        c = getchar();
}

Note: you must only call empty_stdin when characters remain to be read, otherwise, getchar() will simply block waiting on input....

You can also write empty_stdin using a for loop, or just use the for loop inline, without a function call to empty_stdin, it's just a shorter way of doing the same thing, e.g.

void empty_stdin()
{
    for (int c = getchar(); c != '\n' && c != EOF; c = getchar()) {}
}

Next, checking the return of stdin for the number of successful conversions and assignments means you are looking for a return of 1 to indicate success, anything else indicates failure. However, you must check whether the values is EOF indicating an input failure (or the user intentionally canceling input with a Ctrl+D or Ctrl+z (on windoze)). So your code logic will check

        if ((rtn = scanf ("%d", &c)) != 1 || (c < 1 || 5 < c)) {
            /* handle error */
            empty_stdin();
        else {
            switch....
        }

Next, since your function GetMenuChoice is supposed to handle the menu, then have it do the work of presenting the menu. It's rather awkward to have part of the menu displayed in main and the other part in GetMenuChoice. If you need two functions because you only want to display the list once and then loop over input, create a second function to display the menu which then calls GetMenuChoice.

Still further, you need a way of indicating that the user canceled input by generating an EOF in the return from GetMenuChoice. Since your menu is positive 1-5, then having the function return EOF (or -1 which is generally used for EOF) will allow you to test the return back in main() (the calling function) to determine whether to further process the menu choice.

A simple implementation allows you to only process the return if it is in your expected range, e.g.

int main (void) {

    int choice;

    if ((choice = GetMenuChoice()) != -1)
        printf ("GetMenuChoice returned : %d\n", choice);

    return 0;
}

Putting it altogether, you could rearrange your GetMenuChoice just a bit and do something similar to the following to address all possible input and only return 1-5 or -1 on error:

#include <stdio.h>

void empty_stdin()
{
    for (int c = getchar(); c != '\n' && c != EOF; c = getchar()) {}
}

int GetMenuChoice()
{
    int c, rtn;

    while (1) {

        printf ("\nWelcome To the sorting Program\n\t1)Title\n\t2)"
                "Rank\n\t3)Date\n\t4)Stars\n\t5)Like\n\n");

        printf ("Please Enter your choice between 1 to 5: ");

        if ((rtn = scanf ("%d", &c)) != 1 || (c < 1 || 5 < c)) {
            c = -1;
            if (rtn == EOF) {
                fprintf (stderr, "input canceled.\n");
                break;
            }
            printf ("You did not entered between 1 to 5. Try Again\n");
            empty_stdin();
            continue;
        }
        else {
            switch (c) {
                case 1:
                    printf ("You enterd valid choice 1\n");
                    break;
                case 2:
                    printf ("You enterd valid choice 2\n");
                    break;
                case 3:
                    printf ("You enterd valid choice 3\n");
                    break;
                case 4:
                    printf ("You enterd valid choice 4\n");
                    break;
                case 5:
                    printf ("You enterd valid choice 5\n");
                    break;
                default:
                    printf ("You should never get here.....\n");
                    break;
            }
            break;;  /* exit loop */
        }
    }
    return c;
}

int main (void) {

    int choice;

    if ((choice = GetMenuChoice()) != -1)
        printf ("GetMenuChoice returned : %d\n", choice);

    return 0;
}

Example Use/Output

$ ./bin/getmenu

Welcome To the sorting Program
        1)Title
        2)Rank
        3)Date
        4)Stars
        5)Like

Please Enter your choice between 1 to 5: foo
You did not entered between 1 to 5. Try Again

Welcome To the sorting Program
        1)Title
        2)Rank
        3)Date
        4)Stars
        5)Like

Please Enter your choice between 1 to 5: 28
You did not entered between 1 to 5. Try Again

Welcome To the sorting Program
        1)Title
        2)Rank
        3)Date
        4)Stars
        5)Like

Please Enter your choice between 1 to 5: 4
You enterd valid choice 4
GetMenuChoice returned : 4

Look things over and let me know if you have any further questions.


Reaching the default case

Per your comment, if you need to reach the default case in your switch statement, then you need to remove the (c < 1 || 5 < c) test (which is effectively preventing you from reaching the default case.) Think about what you are testing for. Testing the return of scanf you are testing for three cases,

  1. is the input valid? (e.g., does the return equal the match-count)
  2. is the return less than the match-count? (indicating an input or matching failure)
  3. is the return EOF?

Your test of if ((rtn = scanf ("%d", &c)) != 1) covers both (2) and (3) above, so within that if block you test if (rtn == EOF) to distinguish between (2) and (3).

Your switch statement with cases 1 - 5 are making sure c is 1 - 5 and if not, the default case is serving the same purpose as (c < 1 || 5 < c). So if you want to hit your default case instead of testing and excluding (c < 1 || 5 < c) before that point in your code, then remove (c < 1 || 5 < c), e.g.

int GetMenuChoice()
{
    int c, rtn;

    while (1) {

        printf ("\nWelcome To the sorting Program\n\t1)Title\n\t2)"
                "Rank\n\t3)Date\n\t4)Stars\n\t5)Like\n\n");

        printf ("Please Enter your choice between 1 to 5: ");

        if ((rtn = scanf ("%d", &c)) != 1) {
            c = -1;
            if (rtn == EOF) {
                fprintf (stderr, "input canceled.\n");
                break;
            }
            printf ("You did not enter an integer, try again...\n");
            empty_stdin();
            continue;
        }
        else {
            switch (c) {
                case 1:
                    printf ("You enterd valid choice 1\n");
                    break;
                case 2:
                    printf ("You enterd valid choice 2\n");
                    break;
                case 3:
                    printf ("You enterd valid choice 3\n");
                    break;
                case 4:
                    printf ("You enterd valid choice 4\n");
                    break;
                case 5:
                    printf ("You enterd valid choice 5\n");
                    break;
                default:
                    printf ("Invalid choice, integer not between 1 & 5, "
                            "try again....\n");
                    empty_stdin(); /* optional here since %d skips whitespace */
                    continue;
                    break;
            }
            break;;  /* exit loop */
        }
    }
    return c;
}

note: I've changed your error messages to accurately reflect the conditional generating the message. It's not magic, just logic. One of the best ways to work though these type problem is to read How to debug small programs and talk to the duck... (really it listens and really works :)

Do you understand the empty_stdin(); /* optional here since %d skips whitespace */ comment?

Example Use/Output

$ ./bin/getmenu3

Welcome To the sorting Program
        1)Title
        2)Rank
        3)Date
        4)Stars
        5)Like

Please Enter your choice between 1 to 5: foo
You did not enter an integer, try again...

Welcome To the sorting Program
        1)Title
        2)Rank
        3)Date
        4)Stars
        5)Like

Please Enter your choice between 1 to 5: 6
Invalid choice, integer not between 1 & 5, try again....

Welcome To the sorting Program
        1)Title
        2)Rank
        3)Date
        4)Stars
        5)Like

Please Enter your choice between 1 to 5: 1
You enterd valid choice 1
GetMenuChoice returned : 1

Or, of course, when the user cancels input:

$ ./bin/getmenu3

Welcome To the sorting Program
        1)Title
        2)Rank
        3)Date
        4)Stars
        5)Like

Please Enter your choice between 1 to 5: input canceled.

Upvotes: 1

Related Questions