返回主菜单时一直循环菜单。

3
当程序首次启动时,我可以成功地从主菜单中选择任何选项。但是,当我从任何子菜单中选择返回主菜单选项时,它会返回到主菜单,但无论之后我再按什么选项,它都将继续循环该菜单,仅允许我选择返回主菜单选项。我应该如何重置选择以使其不再继续循环?我尽力缩短了代码,使其仍然可以编译,但也能演示错误。先行致谢。
#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


2
只是顺便提一下:使用 scanf 而不检查返回值是不安全的。请参阅此页面以获取更多信息:远离 scanf() 的初学者指南 - Andreas Wenzel
2
你发布的代码无法编译。在声明函数mainMenuFishermanMenu之前,你已经使用了它们(缺少原型声明),并且在这两个函数的定义中,你只指定了参数的名称,但没有指定参数的类型。此外,虽然你似乎试图让这些函数接受一个参数,但你在调用这两个函数时没有传递任何参数。 - Andreas Wenzel
谢谢Andreas。我正在阅读这个现在。它没有编译?我只是将它复制粘贴到代码块中,它对我来说运行得很好,当然除了错误之外。好的,我会添加那个并看看是否有帮助。非常感谢! - tlbiro
1
自从C99以来,隐式声明不再是宽容的。有关更多信息,请参见此问题。然而,gcc编译器仍然默认接受它们。如果您使用-std=c18 -pedantic-errors进行编译,则不会接受它们。 - Andreas Wenzel
我只需要弄清楚如何从子菜单返回一个选项,以便能够再次访问主菜单并在需要时选择不同的菜单。当访问 int FishermanMenu(); 时,它现在不返回我选择的任何选项,尽管我已将其声明为原型。我需要它返回一个值,以便我可以访问其他菜单选项。 - tlbiro
显示剩余7条评论
3个回答

1
这依赖于一个叫做隐式函数声明的特性;这被认为是有害的,并在C99中被移除了。你可以重新排列菜单使其置于顶部,或者在调用函数之前先进行原型设计。
int mainMenu(void);
int FishermanMenu(void);

并将隐式的int参数更改为一个本地变量,然后返回该变量。
int mainMenu(void) {
    int MOption;
...
}
int FishermanMenu(void) {
    int FManOption;
...
}

编辑

不要费力去解决越来越难以调试的开关混乱问题,让我建议一下应用程序结构的改变。只需进行少量更改,这段代码就可以成为一个状态机,从函数中返回下一个状态即可。

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;
}

然后从菜单返回状态。
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() {}

"1,1,3" 的运行结果符合预期;我看到 "1,2" 退出了。不过我认为这是另一个问题。 - Neil
我确定有很多问题,但我无法确定它们发生在哪里。 1,1 可以正常工作,但当我选择 3 返回主菜单时,程序就会结束。我需要它返回主菜单并重置数值。 - tlbiro
您有一系列嵌套的switch语句,这是不寻常的,值得怀疑。 - Neil
1
我认为在这种情况下,do {} while 循环是不够的。在我的解决方案中,为了保持 OP 在错误输入时的错误消息,我被迫将循环更改为无限循环。据我所知,只有在删除该错误消息(除非您将 printf 调用移动到 while 条件中并使用逗号运算符,这将非常丑陋)时,才能使用 do {} while - Andreas Wenzel
@AndreasWenzel 我喜欢逗号,但你说得很好。 - Neil
显示剩余2条评论

1
我想通了。我的教授回复了我比平时晚,帮助了我。我的问题在于我需要为整个菜单集添加一个 while 循环,并使用 MOption = mainMenu(); 为每个菜单选项分配其相应的函数。感谢所有回复我的人,非常感激你们的时间!以下是更新后的代码:
#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

2
我不喜欢 scanf 的一件事是,如果您输入了错误格式的输入,例如 "12dfghoh",那么第一次调用 scanf 将成功返回值 12,但是所有后续的 scanf 调用都将失败(除非您手动丢弃错误的输入)。因此,在我的答案中,我已经重写了您的整个程序,改为使用 fgets - Andreas Wenzel

1
问题在于您会一直陷入这个while循环中:

while (FManOption != 3)

只有当您在“渔夫”子菜单中时,这个循环才有意义,但在用户选择“返回主菜单”后,您应该离开此循环并将程序返回到其先前的状态。

与其试图以编程的控制流暗示程序状态(例如,用户当前是在主菜单还是子菜单中),通常更容易地将程序状态显式地存储在变量中,例如像这样:

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 );
            }
        }
    }
}

值得注意的是,您的函数mainMenuFishermanMenu包含不必要的代码重复。 您可以按以下方式简化函数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后,重要的是丢弃剩余行中的输入。否则,例如,如果用户输入"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而不是scanf,因为这样你就不必处理从输入流中删除错误输入的问题。请参阅此链接以获取更多信息:

远离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 != '\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函数,因为在你的代码中混合使用scanffgets会导致不好的结果。这是因为scanf无法从输入流一次读取一行,它只能提取所需的内容读取数字,并将剩余的内容(包括换行符)留在缓冲区中。因此,如果在scanf之后立即使用fgetsfgets将读取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,它执行广泛的输入验证。
另一个问题是,对于菜单处理函数mainMenuFishermanMenu来说,仅仅把用户输入的数字传回main函数是没有意义的。这些函数更有意义的做法是解释和处理输入。
正如其他答案中已经建议的那样,你可以将mainMenuFishermanMenu函数改为返回程序的新状态给main函数,因为假设输入由mainMenuFishermanMenu函数解释和处理,那么这将是main函数所需的唯一信息。
在这种情况下,你的程序代码将如下所示:
#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;
    }
}

然而,即使这种解决方案更简单、更清晰,它也不如之前的解决方案灵活。如果您以后决定放宽菜单层次结构的严格性(例如允许直接从一个菜单跳转到另一个完全不同位置的菜单),那么事情很快就会变得混乱。

哇谢谢你Andreas。这非常有帮助,我感激你花时间写了所有的代码。我会尝试实现它,并查看是否可以将其与程序的其他部分一起运行。我正在为课程编写的这个程序还实现了结构和传递指针,我的最大困难是理解如何传递指针以及如何在不引起无限循环或错误的情况下从一个函数传递到另一个函数。 - tlbiro
抱歉,我以为我已经接受了一个解决方案,但我猜我根本没有点击它。谢谢你提供的替代方案!我已经成功解决了我的问题,但是正如我所预料的,我遇到了另一个问题。我会在另一个问题中发布它。再次感谢! - tlbiro
我看了你的回答,但我不确定枚举是什么或者它的含义,因为我们在课堂上还没有学过。我们的教授要求我们只使用我们在课堂上学过的方法。这就是为什么我的代码如此混乱的原因。我正在尽力理解所有东西应该如何流程,但由于代码长度,我没有发布其他方面的程序。 - tlbiro
1
请参阅此文档,了解enum的作用。基本上,编译器为每个enum成员分配一个唯一的值。 MENUSTATE_MAIN将自动分配值为0MENUSTATE_FISHERMAN将自动分配值为1等等。虽然你可以直接使用01而不是使用这些enum标识符,但如果使用它们,你的代码会更容易阅读,因为值的意义立即显而易见。 - Andreas Wenzel
很多时候在中途结束会很方便,这时你可以用break。重复的代码会引起维护问题,而本答案小心地避免了这个问题。 - Neil
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接