返回主菜单保持循环菜单
Go Back To Main Menu Keeps Looping Menus
程序首次启动时,我可以 select 从主菜单成功选择任何选项。但是,当我 select 从任何子菜单返回主菜单选项时,它会返回主菜单,但无论我之后再次按什么选项,它都会继续循环该菜单。只允许我 select 返回主菜单选项。如何将 selection 重置为不再继续循环的位置?我已经尽可能地缩短了代码,以便它仍然可以编译但也可以演示错误。提前谢谢你。
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
int main()
{
//declare all working variables: mOption, FManOption, COption...etc...
int MOption = 0;
int FManOption = 0;
int FOption = 0;
int COption = 0;
int userChoice = 0;
//declarations for all arrays of struct
//declare a pointer to an array of struct using malloc() for fisherman, fish, catch
//process:
printf("Please select 1 to start the program or 0 to quit: ");
scanf("%d", &userChoice);
while(userChoice != 1 && userChoice != 0)
{
printf("Invalid selection! Please type a 1 or a 0: ");
scanf("%d", &userChoice);
}//end (userChoice != 1 && userChoice != 0)
if(userChoice != 1)
printf("Thank you for wasting my time! Have a great day!");
else
{
MOption = mainMenu();
switch(MOption)
{
case 1: FManOption = FishermanMenu();
while(FManOption != 3)
{
switch(FManOption)
{
case 1: getFisherman();//get a fisherman
//count fisherman
break;
case 2: //prompt for a ssn, validate, search
//if found display everything about this fisherman
break;
case 3: FManOption = mainMenu();
//reset FManOption
break;
default: printf("\nInvalid selection! Please select from one of the menu options\n");
}//end switch(FManOption)
}//end while(FManOption != 3)
break;
}
}
}
int mainMenu()
{
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(Option > 5 || Option < 1)
do /* check scanf() return value for input errors */
{
printf("\nInvalid selection! Please select from one of the menu options\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);
}
while(Option > 5 || Option < 1);
return Option; /* finally return the final correct option */
}//end main menu
int FishermanMenu()
{
int ManOption;
printf("\n-------Fisherman Menu-------\n");
printf("1 - Register fisherman\n");
printf("2 - Search fisherman\n");
printf("3 - Go back to main menu\n");
printf("Please select a menu option: ");
scanf("%d", &ManOption);
if(ManOption > 5 || ManOption < 1)
do /* check scanf() return value for input errors */
{
printf("\nInvalid selection! Please select from one of the menu options\n");/* handle input error */
printf("1 - Register fisherman\n");
printf("2 - Search fisherman\n");
printf("3 - Go back to main menu\n");
printf("Please select a menu option: ");
scanf("%d", &ManOption);
}
while(ManOption > 5 || ManOption < 1);
return ManOption; /* finally return the final correct option */
}//end Fisherman Menu
这依赖于一种称为隐式函数声明的特性; this is considered harmful 并在 C99
中被删除。人们可能会重新排列菜单使其位于顶部,或者在调用它们之前对函数进行原型设计,
int mainMenu(void);
int FishermanMenu(void);
并将隐式 int
参数更改为 returned,
的局部变量
int mainMenu(void) {
int MOption;
...
}
int FishermanMenu(void) {
int FManOption;
...
}
编辑
让我建议更改应用程序结构,而不是弄清楚只会让调试变得更糟的开关混乱。这段代码可以是一个没有太多变化的状态机,只是 return 函数的下一个状态。
enum states {
START, MAIN, FISHPERSON, REGISTER, SEARCH, FISH, CATCH, CLOSE, END
};
enum states StartMenu(void);
enum states MainMenu(void);
enum states FishermanMenu(void);
/* ... */
typedef enum states (*state_function)(void);
static const state_function states[] = { StartMenu, MainMenu,
FishermanMenu, /*...*/0, 0, 0, 0, 0, /* END is actually 0 == NULL */0 };
int main(void)
{
enum states state = START;
while(states[state]) state = states[state]();
return 0;
}
然后 return 菜单中的状态,
enum states StartMenu(void) {
int userChoice = 0;
//process:
printf("Please select 1 to start the program or 0 to quit: ");
scanf("%d", &userChoice);
while(userChoice != 1 && userChoice != 0)
{
printf("Invalid selection! Please type a 1 or a 0: ");
scanf("%d", &userChoice);
}//end (userChoice != 1 && userChoice != 0)
if(userChoice != 1) {
printf("Thank you for wasting my time! Have a great day!");
return END;
} else {
return MAIN;
}
}
为了减少重复,考虑在菜单上使用 do {} while()
而不是 while() {}
来预先显示信息。
我明白了。我的教授能够帮助我,因为他比平时晚回复了我的电子邮件。我的问题是我需要使用 MOption = mainMenu();
为整个菜单集添加一个 while 循环,并将每个菜单选项分配给它们各自的功能。谢谢所有回复的人我非常感谢你的时间!这是更新后的代码:
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
int main()
{
//declare all working variables: mOption, FManOption, COption...etc...
int MOption = 0;
int FManOption = 0;
int FOption = 0;
int COption = 0;
int userChoice = 0;
//declarations for all arrays of struct
//declare a pointer to an array of struct using malloc() for fisherman, fish, catch
//process:
printf("Please select 1 to start the program or 0 to quit: ");
scanf("%d", &userChoice);
while(userChoice != 1 && userChoice != 0)
{
printf("Invalid selection! Please type a 1 or a 0: ");
scanf("%d", &userChoice);
}//end (userChoice != 1 && userChoice != 0)
if(userChoice != 1)
{
printf("Thank you for wasting my time! Have a great day!\n");
return 0;
}
while(MOption != 5)
{
MOption = mainMenu();
switch(MOption)
{
case 1: FManOption = FishermanMenu();
while(FManOption != 3)
{
switch(FManOption)
{
case 1: //get a fisherman
//count fisherman
break;
case 2: //prompt for a ssn, validate, search
//if found display everything about this fisherman
break;
case 3: FManOption = mainMenu();
//reset FManOption
break;
default: printf("\nInvalid selection! Please select from one of the menu options\n");
}//end switch(FManOption)
}//end while(FManOption != 3)
break;
}
}
}
int mainMenu(MOption)
{
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", &MOption);
if(MOption > 5 || MOption < 1)
do /* check scanf() return value for input errors */
{
printf("\nInvalid selection! Please select from one of the menu options\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", &MOption);
}
while(MOption > 5 || MOption < 1);
return MOption; /* finally return the final correct option */
}//end main menu
int FishermanMenu(FManOption)
{
printf("\n-------Fisherman Menu-------\n");
printf("1 - Register fisherman\n");
printf("2 - Search fisherman\n");
printf("3 - Go back to main menu\n");
printf("Please select a menu option: ");
scanf("%d", &FManOption);
if(FManOption > 5 || FManOption < 1)
do /* check scanf() return value for input errors */
{
printf("\nInvalid selection! Please select from one of the menu options\n");/* handle input error */
printf("1 - Register fisherman\n");
printf("2 - Search fisherman\n");
printf("3 - Go back to main menu\n");
printf("Please select a menu option: ");
scanf("%d", &FManOption);
}
while(FManOption > 5 || FManOption < 1);
return FManOption; /* finally return the final correct option */
}//end Fisherman Menu
问题是你会永远陷入这个 while
循环:
while (FManOption != 3)
只有当您位于“Fisherman”子菜单中时,此循环才有意义,但您应该离开此循环,并且 return 在用户选择“返回主菜单”后,程序会恢复到之前的状态".
与其尝试以程序的控制流暗示程序状态(例如,用户当前是在主菜单还是子菜单中)的方式编写代码,不如将程序状态显式存储在变量中通常更容易,例如:
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
和 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
的 return 值来完成。此外,在使用 scanf
之后,丢弃该行其余部分的输入也很重要。否则,例如,如果用户输入 "12dfghoh"
,那么所有对 scanf
的后续调用都将失败,因为它无法在尝试读取时从输入流中删除 dfghoh
数.
因此,这是我检查 scanf
的 return 值并丢弃行中所有剩余输入的代码:
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
而不是 scanf
,因为这样你就不必处理从输入流。有关详细信息,请参阅此 link:
A beginners' guide away from scanf()
使用 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 != '[=13=]'; 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
和 fgets
将无法很好地工作。这是因为 scanf
不会一次从输入流中读取一行,而只会提取读取数字所需的内容,并将该行的其余部分(包括换行符)留在缓冲。因此,如果您在 scanf
之后立即使用 fgets
,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 != '[=14=]'; 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
和FishermanMenu
将用户输入的数字简单地传递回main
函数没有意义。这些函数自己解释和处理输入会更有意义。
正如另一个答案中已经建议的那样,您可以将函数 mainMenu
和 FishermanMenu
更改为 return 程序的新状态返回 main
,因为这将是 main
唯一需要的信息,假设输入由函数 mainMenu
和 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 != '[=15=]'; 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 != '[=16=]'; 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;
}
}
然而,即使这个解决方案更简单、更简洁,它也没有以前的解决方案灵活。如果您以后决定放松菜单层次结构的严格性(例如,允许在菜单层次结构中完全不同的位置从一个菜单直接跳转到另一个菜单),那么事情很快就会变得混乱。
程序首次启动时,我可以 select 从主菜单成功选择任何选项。但是,当我 select 从任何子菜单返回主菜单选项时,它会返回主菜单,但无论我之后再次按什么选项,它都会继续循环该菜单。只允许我 select 返回主菜单选项。如何将 selection 重置为不再继续循环的位置?我已经尽可能地缩短了代码,以便它仍然可以编译但也可以演示错误。提前谢谢你。
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
int main()
{
//declare all working variables: mOption, FManOption, COption...etc...
int MOption = 0;
int FManOption = 0;
int FOption = 0;
int COption = 0;
int userChoice = 0;
//declarations for all arrays of struct
//declare a pointer to an array of struct using malloc() for fisherman, fish, catch
//process:
printf("Please select 1 to start the program or 0 to quit: ");
scanf("%d", &userChoice);
while(userChoice != 1 && userChoice != 0)
{
printf("Invalid selection! Please type a 1 or a 0: ");
scanf("%d", &userChoice);
}//end (userChoice != 1 && userChoice != 0)
if(userChoice != 1)
printf("Thank you for wasting my time! Have a great day!");
else
{
MOption = mainMenu();
switch(MOption)
{
case 1: FManOption = FishermanMenu();
while(FManOption != 3)
{
switch(FManOption)
{
case 1: getFisherman();//get a fisherman
//count fisherman
break;
case 2: //prompt for a ssn, validate, search
//if found display everything about this fisherman
break;
case 3: FManOption = mainMenu();
//reset FManOption
break;
default: printf("\nInvalid selection! Please select from one of the menu options\n");
}//end switch(FManOption)
}//end while(FManOption != 3)
break;
}
}
}
int mainMenu()
{
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(Option > 5 || Option < 1)
do /* check scanf() return value for input errors */
{
printf("\nInvalid selection! Please select from one of the menu options\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);
}
while(Option > 5 || Option < 1);
return Option; /* finally return the final correct option */
}//end main menu
int FishermanMenu()
{
int ManOption;
printf("\n-------Fisherman Menu-------\n");
printf("1 - Register fisherman\n");
printf("2 - Search fisherman\n");
printf("3 - Go back to main menu\n");
printf("Please select a menu option: ");
scanf("%d", &ManOption);
if(ManOption > 5 || ManOption < 1)
do /* check scanf() return value for input errors */
{
printf("\nInvalid selection! Please select from one of the menu options\n");/* handle input error */
printf("1 - Register fisherman\n");
printf("2 - Search fisherman\n");
printf("3 - Go back to main menu\n");
printf("Please select a menu option: ");
scanf("%d", &ManOption);
}
while(ManOption > 5 || ManOption < 1);
return ManOption; /* finally return the final correct option */
}//end Fisherman Menu
这依赖于一种称为隐式函数声明的特性; this is considered harmful 并在 C99
中被删除。人们可能会重新排列菜单使其位于顶部,或者在调用它们之前对函数进行原型设计,
int mainMenu(void);
int FishermanMenu(void);
并将隐式 int
参数更改为 returned,
int mainMenu(void) {
int MOption;
...
}
int FishermanMenu(void) {
int FManOption;
...
}
编辑
让我建议更改应用程序结构,而不是弄清楚只会让调试变得更糟的开关混乱。这段代码可以是一个没有太多变化的状态机,只是 return 函数的下一个状态。
enum states {
START, MAIN, FISHPERSON, REGISTER, SEARCH, FISH, CATCH, CLOSE, END
};
enum states StartMenu(void);
enum states MainMenu(void);
enum states FishermanMenu(void);
/* ... */
typedef enum states (*state_function)(void);
static const state_function states[] = { StartMenu, MainMenu,
FishermanMenu, /*...*/0, 0, 0, 0, 0, /* END is actually 0 == NULL */0 };
int main(void)
{
enum states state = START;
while(states[state]) state = states[state]();
return 0;
}
然后 return 菜单中的状态,
enum states StartMenu(void) {
int userChoice = 0;
//process:
printf("Please select 1 to start the program or 0 to quit: ");
scanf("%d", &userChoice);
while(userChoice != 1 && userChoice != 0)
{
printf("Invalid selection! Please type a 1 or a 0: ");
scanf("%d", &userChoice);
}//end (userChoice != 1 && userChoice != 0)
if(userChoice != 1) {
printf("Thank you for wasting my time! Have a great day!");
return END;
} else {
return MAIN;
}
}
为了减少重复,考虑在菜单上使用 do {} while()
而不是 while() {}
来预先显示信息。
我明白了。我的教授能够帮助我,因为他比平时晚回复了我的电子邮件。我的问题是我需要使用 MOption = mainMenu();
为整个菜单集添加一个 while 循环,并将每个菜单选项分配给它们各自的功能。谢谢所有回复的人我非常感谢你的时间!这是更新后的代码:
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
int main()
{
//declare all working variables: mOption, FManOption, COption...etc...
int MOption = 0;
int FManOption = 0;
int FOption = 0;
int COption = 0;
int userChoice = 0;
//declarations for all arrays of struct
//declare a pointer to an array of struct using malloc() for fisherman, fish, catch
//process:
printf("Please select 1 to start the program or 0 to quit: ");
scanf("%d", &userChoice);
while(userChoice != 1 && userChoice != 0)
{
printf("Invalid selection! Please type a 1 or a 0: ");
scanf("%d", &userChoice);
}//end (userChoice != 1 && userChoice != 0)
if(userChoice != 1)
{
printf("Thank you for wasting my time! Have a great day!\n");
return 0;
}
while(MOption != 5)
{
MOption = mainMenu();
switch(MOption)
{
case 1: FManOption = FishermanMenu();
while(FManOption != 3)
{
switch(FManOption)
{
case 1: //get a fisherman
//count fisherman
break;
case 2: //prompt for a ssn, validate, search
//if found display everything about this fisherman
break;
case 3: FManOption = mainMenu();
//reset FManOption
break;
default: printf("\nInvalid selection! Please select from one of the menu options\n");
}//end switch(FManOption)
}//end while(FManOption != 3)
break;
}
}
}
int mainMenu(MOption)
{
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", &MOption);
if(MOption > 5 || MOption < 1)
do /* check scanf() return value for input errors */
{
printf("\nInvalid selection! Please select from one of the menu options\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", &MOption);
}
while(MOption > 5 || MOption < 1);
return MOption; /* finally return the final correct option */
}//end main menu
int FishermanMenu(FManOption)
{
printf("\n-------Fisherman Menu-------\n");
printf("1 - Register fisherman\n");
printf("2 - Search fisherman\n");
printf("3 - Go back to main menu\n");
printf("Please select a menu option: ");
scanf("%d", &FManOption);
if(FManOption > 5 || FManOption < 1)
do /* check scanf() return value for input errors */
{
printf("\nInvalid selection! Please select from one of the menu options\n");/* handle input error */
printf("1 - Register fisherman\n");
printf("2 - Search fisherman\n");
printf("3 - Go back to main menu\n");
printf("Please select a menu option: ");
scanf("%d", &FManOption);
}
while(FManOption > 5 || FManOption < 1);
return FManOption; /* finally return the final correct option */
}//end Fisherman Menu
问题是你会永远陷入这个 while
循环:
while (FManOption != 3)
只有当您位于“Fisherman”子菜单中时,此循环才有意义,但您应该离开此循环,并且 return 在用户选择“返回主菜单”后,程序会恢复到之前的状态".
与其尝试以程序的控制流暗示程序状态(例如,用户当前是在主菜单还是子菜单中)的方式编写代码,不如将程序状态显式存储在变量中通常更容易,例如:
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
和 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
的 return 值来完成。此外,在使用 scanf
之后,丢弃该行其余部分的输入也很重要。否则,例如,如果用户输入 "12dfghoh"
,那么所有对 scanf
的后续调用都将失败,因为它无法在尝试读取时从输入流中删除 dfghoh
数.
因此,这是我检查 scanf
的 return 值并丢弃行中所有剩余输入的代码:
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
而不是 scanf
,因为这样你就不必处理从输入流。有关详细信息,请参阅此 link:
A beginners' guide away from scanf()
使用 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 != '[=13=]'; 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
和 fgets
将无法很好地工作。这是因为 scanf
不会一次从输入流中读取一行,而只会提取读取数字所需的内容,并将该行的其余部分(包括换行符)留在缓冲。因此,如果您在 scanf
之后立即使用 fgets
,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 != '[=14=]'; 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
和FishermanMenu
将用户输入的数字简单地传递回main
函数没有意义。这些函数自己解释和处理输入会更有意义。
正如另一个答案中已经建议的那样,您可以将函数 mainMenu
和 FishermanMenu
更改为 return 程序的新状态返回 main
,因为这将是 main
唯一需要的信息,假设输入由函数 mainMenu
和 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 != '[=15=]'; 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 != '[=16=]'; 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;
}
}
然而,即使这个解决方案更简单、更简洁,它也没有以前的解决方案灵活。如果您以后决定放松菜单层次结构的严格性(例如,允许在菜单层次结构中完全不同的位置从一个菜单直接跳转到另一个菜单),那么事情很快就会变得混乱。