Reputation: 271
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
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
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,
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