如何在C++中避免使用GOTO

I p*_*000 0 c c++ basic goto

我读过GOTO很糟糕,但我该如何避免呢?我不知道如何在没有GOTO的情况下进行编程.在BASIC中,我使用了GOTO.我应该在C和C++中使用什么?

我在BASIC中使用了GOTO,如下所示:

MainLoop:
INPUT string$   
IF string$ = "game" THEN   
GOTO game
ENDIF
Run Code Online (Sandbox Code Playgroud)

kfs*_*one 11

考虑以下C++代码:

void broken()
{
    int i = rand() % 10;
    if (i == 0) // 1 in 10 chance.
        goto iHaveABadFeelingAboutThis;

    std::string cake = "a lie";

    // ...
    // lots of code that prepares the cake
    // ...

iHaveABadFeelingAboutThis:
    // 1 time out of ten, the cake really is a lie.
    eat(cake);

    // maybe this is where "iHaveABadFeelingAboutThis" was supposed to be?
    std::cout << "Thank you for calling" << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

最终,"goto"与C++的其他流控制关键字没有太大区别:"break","continue","throw"等; 从功能上讲,它引入了一些与范围相关的问题,如上所述.

依靠goto将教会你生成难以阅读,难以调试和难以维护的代码的坏习惯,并且通常会导致错误.为什么?因为goto以最糟糕的方式是自由格式的,它允许您绕过语言中内置的结构控件,例如范围规则等.

很少有替代方案特别直观,其中一些可以说与"goto"一样含糊不清,但至少你是在语言结构中运作 - 回顾上面的例子,我们做的事情要难得多上面的例子除了goto之外什么都没有(当然,使用指针时你仍然可以使用for/while/throw射击自己的脚).

您可以选择避免它并使用语言的自然流控制结构来保持代码的可读性和可维护性:

  • 将代码分解为子例程.

不要害怕小的,离散的,有名的函数,只要你不是永远拖着大量的参数列表(如果你是,那么你可能想看看用类封装).

许多新手使用"goto",因为他们编写了可笑的长函数,然后发现他们想要从3000行函数的第2行到第2998行.在上面的代码中,如果你拆分,goto创建的bug更难创建功能分为两个有效负载,逻辑和功能.

void haveCake() {
    std::string cake = "a lie";

    // ...
    // lots of code that prepares the cake
    // ...

    eat(cake);
}

void foo() {
    int i = rand() % 10;
    if (i != 0) // 9 times out of 10
        haveCake();
    std::cout << "Thanks for calling" << std::endl;
}   
Run Code Online (Sandbox Code Playgroud)

有些人将此称为"吊装"(我将所有需要将'cake'作为范围提升到haveCake函数中的东西).

  • 一次性循环.

对于程序员来说,这些并不总是显而易见的,它说它是for/while/do循环,但它实际上只打算运行一次.

for ( ; ; ) { // 1-shot for loop.
    int i = rand() % 10;
    if (i == 0) // 1 time in 10
         break;
    std::string cake = "a lie";
    // << all the cakey goodness.

    // And here's the weakness of this approach.
    // If you don't "break" you may create an infinite loop.
    break;
}

std::cout << "Thanks for calling" << std::endl;
Run Code Online (Sandbox Code Playgroud)
  • 例外.

这些可能非常强大,但它们也可能需要大量的锅炉板.另外,您可以抛出异常以进一步备份调用堆栈或根本不执行(并退出程序).

struct OutOfLuck {};

try {
    int i = rand() % 10;
    if (i == 0)
        throw OutOfLuck();
    std::string cake = "a lie";
    // << did you know: cake contains no fat, sugar, salt, calories or chemicals?
    if (cake.size() < MIN_CAKE)
        throw CakeError("WTF is this? I asked for cake, not muffin");
}
catch (OutOfLuck&) {} // we don't catch CakeError, that's Someone Else's Problem(TM).

std::cout << "Thanks for calling" << std::endl;
Run Code Online (Sandbox Code Playgroud)

在形式上,你应该尝试从std :: exception派生你的异常,但我有时会偏向于抛出const char*字符串,枚举和偶尔使用struct Rock.

try {
    if (creamyGoodness.index() < 11)
        throw "Well, heck, we ran out of cream.";
} catch (const char* wkoft /*what kind of fail today*/) {
    std::cout << "CAKE FAIL: " << wkoft << std::endl;
    throw std::runtime_error(wkoft);
}
Run Code Online (Sandbox Code Playgroud)

这里最大的问题是异常用于处理错误,如上面两个例子中的第二个例子.


Mat*_* M. 6

使用有几个原因goto,主要是:条件执行,循环和"退出"例程.

条件执行由if/ else通常管理,它应该足够了

循环是通过管理for,whiledo while; 并进一步加强continuebreak

最困难的是"退出"例程,在C++中,但它被析构函数的确定性执行所取代.因此,为了让您在退出函数时调用例程,您只需创建一个对象,该对象将在其析构函数中执行您需要的操作:直接的优点是您不能忘记在添加一个操作时执行该操作,return并且它甚至可以在存在异常.