【问题标题】:Better way to jump in and out a loop without goto?在没有 goto 的情况下跳入和跳出循环的更好方法?
【发布时间】:2014-09-13 01:56:22
【问题描述】:
do {
    int j, k;
    if (num_copy_array[0][0] == num_copy_array[0][1]) {
        goto next_num;
    } else {
        goto first_time;
    }
    for (j = 0; j < ARRAY_LEN; j++) {
        for (k = 0; k < NUM_LEN; k++) {
            goto skip;
        first_time:
            j = 0, k = 2;
        skip:
            for (int j_2 = 0; j_2 <= j; j_2++) {
                for (int k_2 = 0; k_2 <= NUM_LEN; k_2++) {
                    if (j_2 == j && k_2 == k) {
                        goto next_digit;
                    }
                    if (num_copy_array[j][k] == num_copy_array[j_2][k_2]) {
                        goto next_num;
                    }
                }
            }
        next_digit:;
        }
    }
} while (false);
/*
 * some
 * code
*/
:next_num // starts the bigger loop outside

这就是我在编写一个 c++ 程序以创建一个特殊的循环入口并打破多个循环时的结果。我必须在外面声明循环变量 'j, k' 才能在使用 goto 跳入循环之前声明它们,并使用假的 do-loop 来本地化它们。

很多人说“永远不要使用 goto”,我同意使用不必要的 goto 会产生不好的程序。然而,在这种情况下,我真的很难想出一个替代方案,而不是使用临时开关变量并在每个循环入口检查它们,我认为这种方式效率非常低且难以阅读。

您能想出一个更好的方法来使我的程序的这一部分变得更好,或者您有一般性的建议吗?

【问题讨论】:

  • 整个想法是编写不需要goto 的代码,而不是以不同的方式模拟goto,即从字面上避免它。很难在代码中说出你想要做什么。
  • @xiver77:您能否解释一下这段代码试图做什么,或者在问题中包含整个函数?理解这段代码的目标非常困难(正如您在 cmets 中看到的那样)。
  • 这段代码正是我们不鼓励goto的原因。这就是说goto 有非常值得尊敬的用途。这不是其中之一。
  • @OP,总而言之,你问这个问题一件非常好的事情。不过,它可能更适合 codereview.SE。
  • 我认为你需要在你的这个概念模型上做一些工作是公平的。

标签: c++ c goto


【解决方案1】:

简短回答

将您的代码分解为函数。您可以通过明智地使用辅助方法和快速返回来替换这些gotos。辅助方法是管理四重嵌套循环和蜿蜒gotos 复杂性的好方法。它们还将通过为各个移动部分命名来揭示算法的基本逻辑。

长答案:

正如@MooingDuck 在他的 cmets 中破译的那样,此代码的唯一外部影响是正常退出循环或通过跳转到 next_num 标签退出。

考虑到这一点,第一步是将循环移动到一个函数中,该函数返回一个truefalse 来控制是否执行“某些代码”。

if (should_execute_some_code()) {
    /*
     * some
     * code
     */
}

// starts the bigger loop outside

在函数内部,我们会将任何goto next_num 行转换为return true,如果控制权落在函数底部,则转换为return false

bool should_execute_some_code()
{
    int j, k;
    if (num_copy_array[0][0] == num_copy_array[0][1]) {
        return true;
    }

    goto first_time;

    for (j = 0; j < ARRAY_LEN; j++) {
        for (k = 0; k < NUM_LEN; k++) {
            goto skip;
        first_time:
            j = 0, k = 2;
        skip:
            for (int j_2 = 0; j_2 <= j; j_2++) {
                for (int k_2 = 0; k_2 <= NUM_LEN; k_2++) {
                    if (j_2 == j && k_2 == k) {
                        goto next_digit;
                    }
                    if (num_copy_array[j][k] == num_copy_array[j_2][k_2]) {
                        return true;
                    }
                }
            }
        next_digit:;
        }
    }

    return false;
}

这是一个开始。现在我们怎样才能摆脱剩下的 goto 呢?让我们解决first_time 标签。没有必要无条件地跳入循环。这并不漂亮,但我们可以用k 的条件初始化来替换跳转。在第一次迭代时将其设置为2,之后设置为0。这让我们摆脱了first_timeskip 标签。

bool should_execute_some_code()
{
    int j = 0, k = 2;

    if (num_copy_array[0][0] == num_copy_array[0][1]) {
        return true;
    }

    for (int j = 0; j < ARRAY_LEN; j++) {
        for (int k = (j == 0 ? 2 : 0); k < NUM_LEN; k++) {
            for (int j_2 = 0; j_2 <= j; j_2++) {
                for (int k_2 = 0; k_2 <= NUM_LEN; k_2++) {
                    if (j_2 == j && k_2 == k) {
                        goto next_digit;
                    }
                    if (num_copy_array[j][k] == num_copy_array[j_2][k_2]) {
                        return true;
                    }
                }
            }
        next_digit:;
        }
    }

    return false;
}

接下来是next_digit 标签。如果 j == j_2k == k_2 你想停止内部的两个循环。这可以通过更具选择性的循环条件而不是goto 来控制。

bool should_execute_some_code()
{
    if (num_copy_array[0][0] == num_copy_array[0][1]) {
        return true;
    }

    for (int j = 0; j < ARRAY_LEN; j++) {
        for (int k = (j == 0 ? 2 : 0); k < NUM_LEN; k++) {
            for (int j_2 = 0; j_2 <= j; j_2++) {
                for (int k_2 = 0; k_2 <= NUM_LEN && !(j_2 == j && k_2 == k); k_2++) {
                    if (num_copy_array[j][k] == num_copy_array[j_2][k_2]) {
                        return true;
                    }
                }
            }
        }
    }

    return false;
}

此时,在我看来,您正在检查二维数组中的任何条目是否等于二维数组中的任何其他条目。而且你小心忽略了检查同一个单元格本身。好吧。既然我们知道它的作用,让我们重命名该函数。让我们通过引入第二个辅助函数来分解四个嵌套循环。

bool has_duplicate_entry()
{
    if (num_copy_array[0][0] == num_copy_array[0][1]) {
        return true;
    }

    for (int j = 0; j < ARRAY_LEN; j++) {
        for (int k = (j == 0 ? 2 : 0); k < NUM_LEN; k++) {
            if (contains_entry(num_copy_array[j][k], j, k)) {
                return true;
            }
        }
    }

    return false;
}

bool contains_entry(int entry, int not_j, int not_k)
{
    for (int j = 0; j <= not_j; j++) {
        for (int k = 0; k <= NUM_LEN && !(j == not_j && k == not_k); k++) {
            if (num_copy_array[j][k] == entry) {
                return true;
            }
        }
    }

    return false;
}

【讨论】:

  • for (int k_2 = 0; k_2
【解决方案2】:

我建议类似(用更好的名字):

bool bar(int j, int k)
{
    for(int j_2 = 0; j_2 <= j; j_2++) {
        for(int k_2 = 0; k_2 <= NUM_LEN; k_2++) {
            if(j_2 == j && k_2 == k) {
                return true;
            }
            if(num_copy_array[j][k] == num_copy_array[j_2][k_2]) {
                return false;
            }
        }
    }
    return true;
}

bool foo()
{
    if(num_copy_array[0][0] == num_copy_array[0][1]) {
        return false;
    }
    for(int j = 0; j < ARRAY_LEN; j++) {
        for(int k = (j == 0) ? 2 : 0; k < NUM_LEN; k++) {
            if (bar(j, k) == false) {
                return false;
            }
        }
    }
    return true;
}

void foobar()
{
    if (foo()) {
    /*
     * some
     * code
    */
    }
    // Other code
}

【讨论】:

    【解决方案3】:

    您可以在循环中添加另一个检查,例如:

    for (i=0; i < x && !ready; i++)
    {
        for (j = 0; j < y && !ready; j++)
        {
            // Now you can maintain the ready variable to finish...
            if (whatever)
                ready = true; // Or, ready = NULL Or ready > 0 etc (whatever suits your situation) 
        }
    }
    

    如果您可以将代码分解为更多函数,那么不需要那么多嵌套循环,这也可能会有所帮助。

    【讨论】:

    • 就我个人而言,我发现一个 bool 变量被用来替换一个 goto 以打破嵌套循环同样糟糕,甚至更糟。
    • 我不是建议使用布尔值 - 我建议对两个循环进行额外检查。在某些情况下,布尔值确实有意义或可以使其更易于阅读。
    • 这就是我的意思。 ready 是一个布尔值,它的唯一目的是通过在内部循环中设置来确定何时停止外部循环。
    【解决方案4】:

    编程元素中,Stepanov 和 McJones 展示了一个经过优雅编码的状态机,即使是 gotos。他们提到明显的非goto 版本几乎没有那么优雅。 Knuth 花了一章 Literate Programming 来支持偶尔使用 goto。我不是goto 的粉丝,但我接受它有它的用途。但是,我总是尝试任何代码的非goto 版本。

    在这种情况下,我认为您正在尝试simulate named loops,但我认为您不需要这样做。一方面,外部的do ... while 循环看起来非常像一个残缺的函数,只有在某些变量处于正确状态时才能调用它。您可以(并且,我认为,应该)用函数替换它;当你这样做时,你会注意到你不再需要 goto skipfirst_time

    我开始尝试重构代码,但在不知道您真正想要做什么的情况下,我无能为力。

    【讨论】:

    • Knuth 的 Literate Programming 章节实际上是他的论文 Structured programming with go to statements 的存档形式,该论文可追溯到 1974 年。他讨论了go to 语句的几种用法(和消除)。
    猜你喜欢
    • 2013-09-03
    • 1970-01-01
    • 2014-09-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-15
    相关资源
    最近更新 更多