这个goto语句有多糟糕?

8

我创建了一个俄罗斯方块游戏,在游戏结束后可以重新开始。我使用goto语句实现了这个功能(请参见代码)。Game类依赖析构函数,这些析构函数是否会在使用goto语句时被调用?这个goto语句有多糟糕?它是可以接受的吗?还是应该用其他方法代替?

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    // initiate sdl
    sdl_init();

    // seed rng
    srand(time(NULL));

    newgame: // new game label
    Game game(GAME_WIDTH, GAME_HEIGHT, 1, screen);

    // keydowns
    bool fastfall = false;
    bool gamerunning = true;
    Uint32 lastupdate = 0;

    while (gamerunning && game.isalive()) {
        // game running stuff here
    }

    // game over stuff here

    while (gamerunning) {
        if (SDL_PollEvent(&event)) {
            if (event.type == SDL_QUIT) {
                gamerunning = false;
            } else if (event.type == SDL_KEYDOWN) {
                if (event.key.keysym.sym == SDLK_r) goto newgame; // yay a new game!
            }
        }
    }

    TTF_Quit();
    SDL_Quit();
    return 0;
}

5
恶心...如果只是为了goto就好了。 - Alexandre C.
4
当然你可以使用goto,但你必须承担可能带来的后果:http://xkcd.com/292/ - bitmask
7个回答

16

通过将大部分函数放入while循环中并设置一个标志来跳出循环,您可以轻松避免这种情况。

在C语言中,唯一真正“可接受”的goto用法是在出现错误时跳转到通用清理代码。 在C ++中,您甚至可以使用异常避免这种情况。 因此,真的没有借口!


我知道,但这感觉很不合适,使用goto似乎更合适。这就是为什么我想知道是否可以接受这个goto的原因。 - orlp
1
@night:为什么循环似乎比快速而肮脏的“goto”更不合适?您可以循环玩游戏,直到您不再想玩游戏。这对我来说似乎完全自然... - Oliver Charlesworth
因为重新启动似乎意味着我们回到了代码的开头,这正是goto所做的。 - orlp
@nightcracker:不,它不是。三个if的级别也不是。event全局变量也很奇怪。 - Alexandre C.
@night:使用循环可以达到完全相同的效果,而且更加清晰(因为它是结构化的)。 - Oliver Charlesworth
@Alexandre C.:是的,那个事件全局变量仍然是我复制的一些基础代码中的怪癖,我会修复它。 - orlp

13
为了回答关于析构函数的问题,其他人似乎没有涉及到。根据6.6/2,析构函数将自动为您调用。引用如下:
离开作用域(无论如何完成),都将为该作用域中声明的具有自动存储期(3.7.2)(命名对象或临时对象)的所有已构造对象调用析构函数(12.4),按其声明的相反顺序进行。从循环退出、从块中退出或从已初始化具有自动存储期的变量回退都涉及在转移点处处于作用域内但不在转移点处的具有自动存储期的变量的销毁。
然而,在这种情况下,我仍然不建议使用goto。 它并没有清楚地表明正在发生什么。 您应该只是使用一个while循环,并让它基于条件运行。
即使像这样简单的东西也应该更加清晰明了(虽然可能有一种方法可以在没有内部break的情况下重写它)。 像这样在while循环中使用本地变量的清除工作非常显然:
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    // initiate sdl
    sdl_init();

    // seed rng
    srand(time(NULL));

    bool gamerunning = true;
    while(gamerunning)
    {
        Game game(GAME_WIDTH, GAME_HEIGHT, 1, screen);

        // keydowns
        bool fastfall = false;
        Uint32 lastupdate = 0;

        while (gamerunning && game.isalive()) {
            // game running stuff here
        }

        // game over stuff here

        while (gamerunning) {
            if (SDL_PollEvent(&event)) {
                if (event.type == SDL_QUIT) {
                    gamerunning = false;
                } else if (event.type == SDL_KEYDOWN) {
                    if (event.key.keysym.sym == SDLK_r) break; // yay a new game - get out of the "what to do next" loop.
                }
            }
        }
    }

    TTF_Quit();
    SDL_Quit();
    return 0;
}

3
+1 对于解构函数问题的回答(我完全错过了...) - Oliver Charlesworth

6

不要使用goto,你可以将从newgame标签到while循环结束的所有内容放入一个函数中。该函数的返回值将告诉你是否需要再次运行。因此,代码应该类似于:

...
srand(time(NULL));

while (runGame())
{
}

TTF_Quit();
...

在你的主函数中,你需要传递任何在游戏代码中使用的参数到runGame()函数,并在代码使用goto时返回1,在最后一局游戏结束时返回0。


5

将重要的代码块分解成函数,而不是使用 goto 语句,直接调用函数。


2

使用goto很少是好的选择。唯一的例外似乎是用于清理,当您需要快速退出多个嵌套循环,释放一些内存并退出时。这里可以轻松地用while循环替换。如果保留原样,则只会使调试和维护更加困难。


0

有些时候使用goto是很好的选择(例如:实现状态机),但我不确定这是否真的是其中之一。

如果是我的话,我会将“game”代码放在子程序中,在完成后退出它,然后让更高层次的例程选择开始新游戏或其他操作。


-1
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    // initiate sdl
    sdl_init();

    // seed rng
    srand(time(NULL));


    while (1) { 
    Game game(GAME_WIDTH, GAME_HEIGHT, 1, screen);

    // keydowns
    bool fastfall = false;
    bool gamerunning = true;
    Uint32 lastupdate = 0;

    while (gamerunning && game.isalive()) {
        // game running stuff here
    }

    // game over stuff here
    restart_game = false; 
    while (gamerunning) {
        if (SDL_PollEvent(&event)) {
            if (event.type == SDL_QUIT) {
                gamerunning = false;
            } else if (event.type == SDL_KEYDOWN) {
                if (event.key.keysym.sym == SDLK_r) { 
                      restart_game = true; break; 
                } 
            }
        }
    }
    if (!restart_game) break; 
    }

    TTF_Quit();
    SDL_Quit();
    return 0;
}

这甚至更糟。Breaks,continue和goto都是同样的“邪恶”。 - Alexandre C.

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