问题是你陷入了这个困境while
永远循环:
while (FManOption != 3)
此循环仅在您位于“Fisherman”子菜单内时才有意义,但在用户选择“返回主菜单”后,您应该离开此循环并将程序返回到之前的状态。
与其尝试以程序的状态(例如,用户当前是否在主菜单还是子菜单中)由程序的控制流暗示的方式编写代码,通常更容易存储程序在变量中显式声明,例如如下所示:
enum menu_state
{
MENUSTATE_MAIN,
MENUSTATE_FISHERMAN,
MENUSTATE_FISH,
MENUSTATE_TOURNAMENT_CATCH,
MENUSTATE_CLOSE_TOURNAMENT
};
int main( void )
{
[...]
if (userChoice != 1)
printf("Thank you for wasting my time! Have a great day!");
else
{
enum menu_state ms = MENUSTATE_MAIN;
for (;;) //infinite loop, equivalent to while(true)
{
switch ( ms )
{
case MENUSTATE_MAIN:
switch ( mainMenu() )
{
case 1:
printf( "opening fisherman menu\n" );
ms = MENUSTATE_FISHERMAN;
break;
case 2:
printf( "opening fish menu\n" );
ms = MENUSTATE_FISH;
break;
case 3:
printf( "opening tournament(catch) menu\n" );
ms = MENUSTATE_TOURNAMENT_CATCH;
break;
case 4:
printf( "opening close tournament menu\n" );
ms = MENUSTATE_CLOSE_TOURNAMENT;
break;
case 5:
//quit program
exit( EXIT_SUCCESS );
default:
fprintf( stderr, "unexpected error\n" );
exit( EXIT_FAILURE );
}
break;
case MENUSTATE_FISHERMAN:
switch ( FishermanMenu() )
{
case 1:
printf( "Register fisherman not yet implemented.\n" );
break;
case 2:
printf( "Search fisherman not yet implemented.\n" );
break;
case 3:
//change program state back to main menu
ms = MENUSTATE_MAIN;
break;
default:
fprintf( stderr, "unexpected error\n" );
exit( EXIT_FAILURE );
}
break;
case MENUSTATE_FISH:
printf( "Fish menu not yet implemented, returning to main menu.\n" );
ms = MENUSTATE_MAIN;
break;
case MENUSTATE_TOURNAMENT_CATCH:
printf( "Tournament(catch) menu not yet implemented, returning to main menu.\n" );
ms = MENUSTATE_MAIN;
break;
case MENUSTATE_CLOSE_TOURNAMENT:
printf( "Close tournament not yet implemented, returning to main menu.\n" );
ms = MENUSTATE_MAIN;
break;
default:
fprintf( stderr, "unexpected error\n" );
exit( EXIT_FAILURE );
}
}
}
}
还值得注意的是,你的功能mainMenu
and FishermanMenu
包含不必要的代码重复。您可以简化功能mainMenu
通过以下方式:
int mainMenu( void )
{
for (;;) //repeat forever, until input is valid
{
int option;
printf("\n-------Welcome to the Fishing Tournament Main Menu!-------\n");
printf("1 - Fisherman menu\n");
printf("2 - Fish menu\n");
printf("3 - Tournament(Catch) menu\n");
printf("4 - Close Tournament (determine winner)\n");
printf("5 - Quit Program\n\n");
printf("Please select a menu option: ");
scanf("%d", &option);
if ( 1 <= option && option <= 5 )
return option;
printf("\nInvalid selection! Please select from one of the menu options\n");
}
}
然而,重要的是要始终检查该功能是否scanf
在尝试使用结果之前成功scanf
。这可以通过检查返回值来完成scanf
。另外,使用后scanf
,丢弃该行其余部分的输入非常重要。否则,如果用户输入"12dfghoh"
,然后所有后续调用scanf
将会失败,因为它无法删除dfghoh
尝试读取数字时从输入流中读取。
因此,这是我检查返回值的代码scanf
并丢弃该行中所有剩余的输入:
int mainMenu( void )
{
for (;;) //repeat forever, until input is valid
{
int option, c;
printf("\n-------Welcome to the Fishing Tournament Main Menu!-------\n");
printf("1 - Fisherman menu\n");
printf("2 - Fish menu\n");
printf("3 - Tournament(Catch) menu\n");
printf("4 - Close Tournament (determine winner)\n");
printf("5 - Quit Program\n\n");
printf("Please select a menu option: ");
if (
scanf("%d", &option) == 1 && //make sure scanf succeeded
1 <= option && option <= 5
)
{
return option;
}
printf("\nInvalid selection! Please select from one of the menu options\n");
//discard remainder of line, which may contain bad input
//and prevent the next call of scanf to succeed
do
{
c = getchar();
}
while ( c != EOF && c != '\n' );
}
}
另一方面,对于基于行的输入,最好使用fgets https://en.cppreference.com/w/c/io/fgets代替scanf
,因为这样您就不必处理从输入流中删除错误输入的问题。请参阅此链接了解更多信息:
远离 scanf() 的初学者指南 http://sekrit.de/webdocs/c/beginners-guide-away-from-scanf.html
使用fgets
函数,函数mainMenu
看起来像这样:
//NOTE: the following header must be added
#include <ctype.h>
int mainMenu( void )
{
for (;;) //repeat forever, until input is valid
{
char buffer[1024], *p;
long option;
printf("\n-------Welcome to the Fishing Tournament Main Menu!-------\n");
printf("1 - Fisherman menu\n");
printf("2 - Fish menu\n");
printf("3 - Tournament(Catch) menu\n");
printf("4 - Close Tournament (determine winner)\n");
printf("5 - Quit Program\n\n");
printf("Please select a menu option: ");
if ( fgets( buffer, sizeof buffer, stdin ) == NULL )
{
printf( "unexpected input error!\n" );
//since this type of error is probably not recoverable,
//don't try again, but instead exit program
exit( EXIT_FAILURE );
}
option = strtol( buffer, &p, 10 );
if ( p == buffer )
{
printf( "error converting string to number\n" );
continue;
}
//make sure remainder of line contains only whitespace,
//so that input such as "12dfghoh" gets rejected
for ( ; *p != '\0'; p++ )
{
if ( !isspace( (unsigned char)*p ) )
{
printf( "unexpected input encountered!\n" );
continue;
}
}
//make sure input is in the desired range
if ( option < 1 || option > 5 )
{
printf( "input must be between 1 and 5\n" );
continue;
}
return option;
}
}
但是,您不能简单地替换该功能mainMenu
在你的代码和我上面的代码中,因为混合scanf
and fgets
在你的代码中不会很好地工作。这是因为scanf
不会一次从输入流中读取一行,而只会提取读取数字所需的内容,并将该行的其余部分(包括换行符)保留在缓冲区中。因此,如果您使用fgets
之后立马scanf
, fgets
将读取该行的其余部分scanf
没有提取,这通常是一个只包含换行符的字符串。
因此,如果您决定使用fgets
(我推荐),那么你应该在程序中的任何地方使用它,而不是将它与scanf
,例如这样:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include <errno.h>
int mainMenu(void);
int FishermanMenu(void);
int get_int_from_user( const char *prompt );
enum menu_state
{
MENUSTATE_MAIN,
MENUSTATE_FISHERMAN,
MENUSTATE_FISH,
MENUSTATE_TOURNAMENT_CATCH,
MENUSTATE_CLOSE_TOURNAMENT
};
int main( void )
{
int user_choice;
for (;;) //loop forever until input is valid
{
user_choice = get_int_from_user(
"Please select 1 to start the program or 0 to quit: "
);
if ( user_choice == 0 )
{
printf("Thank you for wasting my time! Have a great day!");
exit( EXIT_SUCCESS );
}
if ( user_choice == 1 )
{
//input is valid, so break infinite loop
break;
}
printf( "Invalid selection!\n" );
}
enum menu_state ms = MENUSTATE_MAIN;
for (;;) //main program loop
{
switch ( ms )
{
case MENUSTATE_MAIN:
switch ( mainMenu() )
{
case 1:
printf( "opening fisherman menu\n" );
ms = MENUSTATE_FISHERMAN;
break;
case 2:
printf( "opening fish menu\n" );
ms = MENUSTATE_FISH;
break;
case 3:
printf( "opening tournament(catch) menu\n" );
ms = MENUSTATE_TOURNAMENT_CATCH;
break;
case 4:
printf( "opening close tournament menu\n" );
ms = MENUSTATE_CLOSE_TOURNAMENT;
break;
case 5:
//quit program
exit( EXIT_SUCCESS );
default:
fprintf( stderr, "unexpected error\n" );
exit( EXIT_FAILURE );
}
break;
case MENUSTATE_FISHERMAN:
switch ( FishermanMenu() )
{
case 1:
printf( "Register fisherman not yet implemented.\n" );
break;
case 2:
printf( "Search fisherman not yet implemented.\n" );
break;
case 3:
//change program state back to main menu
ms = MENUSTATE_MAIN;
break;
default:
fprintf( stderr, "unexpected error\n" );
exit( EXIT_FAILURE );
}
break;
case MENUSTATE_FISH:
printf( "Fish menu not yet implemented, returning to main menu.\n" );
ms = MENUSTATE_MAIN;
break;
case MENUSTATE_TOURNAMENT_CATCH:
printf( "Tournament(catch) menu not yet implemented, returning to main menu.\n" );
ms = MENUSTATE_MAIN;
break;
case MENUSTATE_CLOSE_TOURNAMENT:
printf( "Close tournament not yet implemented, returning to main menu.\n" );
ms = MENUSTATE_MAIN;
break;
default:
fprintf( stderr, "unexpected error\n" );
exit( EXIT_FAILURE );
}
}
}
int mainMenu( void )
{
for (;;) //repeat forever, until input is in desired range
{
int option;
option = get_int_from_user(
"\n-------Welcome to the Fishing Tournament Main Menu!-------\n"
"1 - Fisherman menu\n"
"2 - Fish menu\n"
"3 - Tournament(Catch) menu\n"
"4 - Close Tournament (determine winner)\n"
"5 - Quit Program\n\n"
"Please select a menu option: "
);
//make sure input is in the desired range
if ( option < 1 || option > 5 )
{
printf( "input must be between 1 and 5\n" );
continue;
}
return option;
}
}
int FishermanMenu()
{
for (;;) //repeat forever, until input is in desired range
{
int option;
option = get_int_from_user(
"\n-------Fisherman Menu-------\n"
"1 - Register fisherman\n"
"2 - Search fisherman\n"
"3 - Go back to main menu\n"
"Please select a menu option: "
);
//make sure input is in the desired range
if ( option < 1 || option > 3 )
{
printf( "input must be between 1 and 3\n" );
continue;
}
return option;
}
}
int get_int_from_user( const char *prompt )
{
for (;;) //loop forever until user enters a valid number
{
char buffer[1024], *p;
long l;
puts( prompt );
if ( fgets( buffer, sizeof buffer, stdin ) == NULL )
{
fprintf( stderr, "unrecoverable error reading from input\n" );
exit( EXIT_FAILURE );
}
//make sure that entire line was read in (i.e. that
//the buffer was not too small)
if ( strchr( buffer, '\n' ) == NULL )
{
int c;
printf("line input was too long!\n");
//discard remainder of line
do
{
c = getchar();
if ( c == EOF)
{
fprintf( stderr, "unrecoverable error reading from input\n" );
exit( EXIT_FAILURE );
}
} while ( c != '\n' );
continue;
}
errno = 0;
l = strtol( buffer, &p, 10 );
if ( p == buffer )
{
printf( "error converting string to number\n" );
continue;
}
if ( errno == ERANGE || l < INT_MIN || l > INT_MAX )
{
printf( "number out of range error\n" );
continue;
}
//make sure remainder of line contains only whitespace,
//so that input such as "12dfghoh" gets rejected
for ( ; *p != '\0'; p++ )
{
if ( !isspace( (unsigned char)*p ) )
{
printf( "unexpected input encountered!\n" );
//cannot use `continue` here, because that would go to
//the next iteration of the innermost loop, but we
//want to go to the next iteration of the outer loop
goto next_outer_loop_iteration;
}
}
return l;
next_outer_loop_iteration:
continue;
}
}
在上面的代码中,我创建了一个新函数get_int_from_user
,它执行广泛的输入验证。
另一个问题是它对于菜单处理函数没有意义mainMenu
and FishermanMenu
只需将用户输入的号码传递回main
功能。对于这些函数来说,自己解释和处理输入会更有意义。
正如其他答案中已经建议的,您可以更改功能mainMenu
and FishermanMenu
相反,将程序的新状态返回到main
,因为这将是唯一的信息main
需要,假设输入由函数解释和处理mainMenu
and FishermanMenu
.
在这种情况下,您的程序的代码将如下所示:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include <errno.h>
int mainMenu(void);
enum menu_state FishermanMenu(void);
enum menu_state get_int_from_user( const char *prompt );
enum menu_state
{
MENUSTATE_MAIN,
MENUSTATE_FISHERMAN,
MENUSTATE_FISH,
MENUSTATE_TOURNAMENT_CATCH,
MENUSTATE_CLOSE_TOURNAMENT,
MENUSTATE_QUIT
};
int main( void )
{
int user_choice;
for (;;) //loop forever until input is valid
{
user_choice = get_int_from_user(
"Please select 1 to start the program or 0 to quit: "
);
if ( user_choice == 0 )
{
printf("Thank you for wasting my time! Have a great day!");
exit( EXIT_SUCCESS );
}
if ( user_choice == 1 )
{
//input is valid, so break infinite loop
break;
}
printf( "Invalid selection!\n" );
}
enum menu_state ms = MENUSTATE_MAIN;
for (;;) //main program loop
{
switch ( ms )
{
case MENUSTATE_MAIN:
ms = mainMenu();
break;
case MENUSTATE_FISHERMAN:
ms = FishermanMenu();
break;
case MENUSTATE_FISH:
printf( "Fish menu not yet implemented, returning to main menu.\n" );
ms = MENUSTATE_MAIN;
break;
case MENUSTATE_TOURNAMENT_CATCH:
printf( "Tournament(catch) menu not yet implemented, returning to main menu.\n" );
ms = MENUSTATE_MAIN;
break;
case MENUSTATE_CLOSE_TOURNAMENT:
printf( "Close tournament not yet implemented, returning to main menu.\n" );
ms = MENUSTATE_MAIN;
break;
case MENUSTATE_QUIT:
return;
default:
fprintf( stderr, "unexpected error\n" );
exit( EXIT_FAILURE );
}
}
}
enum menu_state mainMenu( void )
{
for (;;) //repeat forever, until input is in desired range
{
int option;
option = get_int_from_user(
"\n-------Welcome to the Fishing Tournament Main Menu!-------\n"
"1 - Fisherman menu\n"
"2 - Fish menu\n"
"3 - Tournament(Catch) menu\n"
"4 - Close Tournament (determine winner)\n"
"5 - Quit Program\n\n"
"Please select a menu option: "
);
switch (option)
{
case 1:
printf( "opening fisherman menu\n" );
return MENUSTATE_FISHERMAN;
case 2:
printf( "opening fish menu\n" );
return MENUSTATE_FISH;
case 3:
printf( "opening tournament(catch) menu\n" );
return MENUSTATE_TOURNAMENT_CATCH;
case 4:
printf( "opening close tournament menu\n" );
return MENUSTATE_CLOSE_TOURNAMENT;
case 5:
printf( "quitting program\n" );
return MENUSTATE_QUIT;
default:
printf( "input must be between 1 and 5\n" );
continue;
}
}
}
enum menu_state FishermanMenu()
{
for (;;) //repeat forever, until input is in desired range
{
int option;
option = get_int_from_user(
"\n-------Fisherman Menu-------\n"
"1 - Register fisherman\n"
"2 - Search fisherman\n"
"3 - Go back to main menu\n"
"Please select a menu option: "
);
switch ( option )
{
case 1:
printf( "Register fisherman not yet implemented.\n" );
return MENUSTATE_FISHERMAN;
case 2:
printf( "Search fisherman not yet implemented.\n" );
return MENUSTATE_FISHERMAN;
case 3:
//change program state back to main menu
return MENUSTATE_MAIN;
break;
default:
printf("input must be between 1 and 3\n");
continue;
}
}
}
int get_int_from_user( const char *prompt )
{
for (;;) //loop forever until user enters a valid number
{
char buffer[1024], *p;
long l;
puts( prompt );
if ( fgets( buffer, sizeof buffer, stdin ) == NULL )
{
fprintf( stderr, "unrecoverable error reading from input\n" );
exit( EXIT_FAILURE );
}
//make sure that entire line was read in (i.e. that
//the buffer was not too small)
if ( strchr( buffer, '\n' ) == NULL )
{
int c;
printf("line input was too long!\n");
//discard remainder of line
do
{
c = getchar();
if ( c == EOF)
{
fprintf( stderr, "unrecoverable error reading from input\n" );
exit( EXIT_FAILURE );
}
} while ( c != '\n' );
continue;
}
errno = 0;
l = strtol( buffer, &p, 10 );
if ( p == buffer )
{
printf( "error converting string to number\n" );
continue;
}
if ( errno == ERANGE || l < INT_MIN || l > INT_MAX )
{
printf( "number out of range error\n" );
continue;
}
//make sure remainder of line contains only whitespace,
//so that input such as "12dfghoh" gets rejected
for ( ; *p != '\0'; p++ )
{
if ( !isspace( (unsigned char)*p ) )
{
printf( "unexpected input encountered!\n" );
//cannot use `continue` here, because that would go to
//the next iteration of the innermost loop, but we
//want to go to the next iteration of the outer loop
goto next_outer_loop_iteration;
}
}
return l;
next_outer_loop_iteration:
continue;
}
}
转念一想,我不确定我之前的建议是否正确。因为您似乎有严格的菜单层次结构,所以在您的情况下,不将程序的状态存储在单独的变量中,而是让菜单状态由程序的控制流隐含,可能会更简单。在这种情况下,您的程序将如下所示:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include <errno.h>
void MainMenu( void );
void FishermanMenu( void );
void FishMenu( void );
void TournamentCatchMenu( void );
void CloseTournamentMenu( void );
int get_int_from_user( const char *prompt );
int main(void)
{
int user_choice;
for (;;) //loop forever until input is valid
{
user_choice = get_int_from_user(
"Please select 1 to start the program or 0 to quit: "
);
if (user_choice == 0)
{
printf("Thank you for wasting my time! Have a great day!");
exit(EXIT_SUCCESS);
}
if (user_choice == 1)
{
//input is valid, so break infinite loop
break;
}
printf("Invalid selection!\n");
}
MainMenu();
}
void MainMenu(void)
{
for (;;) //repeat forever, until input is in desired range
{
int option;
option = get_int_from_user(
"\n-------Welcome to the Fishing Tournament Main Menu!-------\n"
"1 - Fisherman menu\n"
"2 - Fish menu\n"
"3 - Tournament(Catch) menu\n"
"4 - Close Tournament (determine winner)\n"
"5 - Quit Program\n\n"
"Please select a menu option: "
);
switch (option)
{
case 1:
FishermanMenu();
break;
case 2:
FishMenu();
break;
case 3:
TournamentCatchMenu();
break;
case 4:
CloseTournamentMenu();
break;
case 5:
return;
default:
printf( "input must be between 1 and 5\n" );
continue;
}
}
}
void FishermanMenu()
{
for (;;) //repeat forever, until input is in desired range
{
int option;
option = get_int_from_user(
"\n-------Fisherman Menu-------\n"
"1 - Register fisherman\n"
"2 - Search fisherman\n"
"3 - Go back to main menu\n"
"Please select a menu option: "
);
switch (option)
{
case 1:
printf( "Register fisherman not yet implemented.\n" );
break;
case 2:
printf( "Search fisherman not yet implemented.\n" );
break;
case 3:
printf( "Returning to main menu.\n" );
return;
default:
printf( "input must be between 1 and 5\n" );
continue;
}
}
}
void FishMenu()
{
printf( "Fish Menu not yet implemented, please select another menu item.\n" );
}
void TournamentCatchMenu()
{
printf( "Tournament(Catch) Menu not yet implemented, please select another menu item.\n" );
}
void CloseTournamentMenu()
{
printf( "Close Tournament Menu not yet implemented, please select another menu item.\n" );
}
int get_int_from_user( const char *prompt )
{
for (;;) //loop forever until user enters a valid number
{
char buffer[1024], *p;
long l;
puts( prompt );
if ( fgets( buffer, sizeof buffer, stdin ) == NULL )
{
fprintf( stderr, "unrecoverable error reading from input\n" );
exit( EXIT_FAILURE );
}
//make sure that entire line was read in (i.e. that
//the buffer was not too small)
if ( strchr( buffer, '\n' ) == NULL )
{
int c;
printf("line input was too long!\n");
//discard remainder of line
do
{
c = getchar();
if ( c == EOF)
{
fprintf( stderr, "unrecoverable error reading from input\n" );
exit( EXIT_FAILURE );
}
} while ( c != '\n' );
continue;
}
errno = 0;
l = strtol( buffer, &p, 10 );
if ( p == buffer )
{
printf( "error converting string to number\n" );
continue;
}
if ( errno == ERANGE || l < INT_MIN || l > INT_MAX )
{
printf( "number out of range error\n" );
continue;
}
//make sure remainder of line contains only whitespace,
//so that input such as "12dfghoh" gets rejected
for ( ; *p != '\0'; p++ )
{
if ( !isspace( (unsigned char)*p ) )
{
printf( "unexpected input encountered!\n" );
//cannot use `continue` here, because that would go to
//the next iteration of the innermost loop, but we
//want to go to the next iteration of the outer loop
goto next_outer_loop_iteration;
}
}
return l;
next_outer_loop_iteration:
continue;
}
}
然而,即使这个解决方案更简单、更干净,它也不像以前的解决方案那么灵活。如果您后来决定放松菜单层次结构的严格性(例如,允许直接从一个菜单跳转到菜单层次结构中完全不同位置的另一个菜单),那么事情很快就会变得混乱。