如何在Go语言中跳出嵌套循环?

88

我有一个外层循环和一个内层循环,每个循环都在一定范围内迭代。当内部循环中满足条件时,我想退出外部循环。

我有一个解决方案,使用两个'break'来实现,在内部循环中使用一个,在外部循环中的内部循环之外使用另一个(仅用于演示的非常简化的情况):

package main

import (
    "fmt"
)

func main() {

    word := ""
    for _, i := range("ABCDE") {
        for _,j := range("ABCDE") {
            word = string(i) + string(j)
            fmt.Println(word)
            if word == "DC" {
                break
            }
        }
        if word == "DC" {
            break
        }
    }
    // More logic here that needs to be executed
}

Go Playground

这个解决方案没有问题,但我觉得它看起来很丑陋。有更好的方法吗?

我可以尝试在前一个解决方案的外部再加上一个条件循环并使用标签和 continue,但正如你所看到的,这种方法并不比使用 break 更优雅。

package main

import (
    "fmt"
)

func main() {

    word := ""

Exit:
    for word != "DC" {
        for _, i := range "ABCDE" {
            for _, j := range "ABCDE" {
                word = string(i) + string(j)
                fmt.Println(word)
                if word == "DC" {
                    continue Exit
                }
            }
        }
    }
    // More logic here that needs to be executed
}

Go Playground

我在这里看到了类似于其他编程语言(如C,C#,Python等)的相关问题。但我真正感兴趣的是是否有任何关于Go构造的技巧,例如'for select'。


3
针对这个具体情况,只需要一个 return 是否合适? - Richard Chambers
1
它可以在我过于简化的示例中工作。然而,后续还有关键的逻辑对于函数是必要的。抱歉没有表达清楚。我会修改问题以避免混淆。 - Jay
如果您使用函数呢?可以返回 truefalse,甚至是单词本身。您可以在该函数中使用 return - kkesley
你考虑过使用goto吗? - aultimus
你的选项是标记变量、标签或返回。就这些了。 - Adrian
6个回答

193

使用break {label}可以跳出任何嵌套循环。只需将标签放在for循环之前即可跳出该循环。这与执行goto {label}的代码非常相似,但我认为更加优雅,不过这只是个人意见。

package main

func main() {
    out:
    for i := 0; i < 10; i++ {
        for j := 0; j < 10; j++ {
            if i + j == 20 {
                break out
            }
        }
    }
}

更多细节:https://www.ardanlabs.com/blog/2013/11/label-breaks-in-go.html


6
这显然是正确的答案。 "返回" 解决方案多少算是一种变通方法(如果在函数内部可能有用,但并非总是如此)。 - evilReiko

37

使用函数

package main

import (
    "fmt"
)

func getWord() string {
    word := ""
    for word != "DC" {
        for _, i := range "ABCDE" {
            for _, j := range "ABCDE" {
                word = string(i) + string(j)
                fmt.Println(word)
                if word == "DC" {
                    return word
                }
            }
        }
    }
    return word
}

func main(){
    word := getWord()
}

编辑:感谢@peterSO指出一些细节上的错误,并提供了这个游乐场https://play.golang.org/p/udcJptBW9pQ


1
一个不错的选择。但是代码块不够通用/在其他地方使用,不能称之为函数。会点赞支持这个想法。 - Jay
5
您之所以使用函数,并不是因为它可以在其他地方或者是通用的,而是因为它解决了您的问题。+1 - atakanyenel
@peterSO,你说得对!我编辑了我的答案,并在答案中包含了你的游乐场链接。谢谢! - kkesley
在仔细权衡了各种选择后,我在我的代码中采用了这个。非常感谢您的建议。 - Jay

12

goto 怎么样?

package main

import (
    "fmt"
)

func main() {

    word := ""

        for _, i := range "ABCDE" {
            for _, j := range "ABCDE" {
                word = string(i) + string(j)
                fmt.Println(word)
                if word == "DC" {
                    goto Exit
                }
            }
        }
    Exit: // More logic here that needs to be executed
}

它可以工作,但是像我说的那样,如果可能的话,我想避免使用标签。 - Jay
4
跳转语句是有害的,但在这种情况下,我认为它是最简单的解决方案。 - vitams

6
最直接的方法似乎是这样的:
func main() {
    word := ""
    isDone := false
    for _, i := range("ABCDE") {
        for _,j := range("ABCDE") {
            word = string(i) + string(j)
            fmt.Println(word)
            isDone = word == "DC"
            if isDone {
                break
            }
        }
        if isDone {
            break
        }
    }
    //  other stuff
}

使用生成器的替代方法

然而,您也可以使用生成器来创建单词序列,如下所示:

func makegen () chan string {
    c:= make(chan string)
    go func () {
        for _, i := range ("ABCDE") {
            for _, j := range ("ABCDE") {
                c <- string(i) + string(j)
            }
        }
        close (c)
    }()

    return c
}


func main() {
    word := ""
    for word = range makegen() {
        fmt.Println (word)
        if word == "DC" {
          break
        }
    }
    // other code
}

这是一个改进版本的生成器函数,可以清理下面评论中识别出的资源泄漏问题。

func makegen () chan string {
    c:= make(chan string)
    go func () {
        word := ""
        for _, i := range ("ABCDE") {
            for _, j := range ("ABCDE") {
                word = string(i) + string(j)
                c <- word
                if word == "DC" {
                    close (c)
                    return
                }
            }
        }
        close (c)
    }()

    return c
}


func main() {
    word := ""
    for word = range makegen() {
        fmt.Println (word)
    }
    // other code
}

2
@Jayachandran 是的,只要改变一下中断条件的评估方式,这样就不会有重复检查了。我的意思是你的第一个解决方案看起来很好,但如果可能的话,我会做出改变,只评估退出条件一次,然后只需检查已评估的条件。这样可以减少未来更改在所有正确位置都没有被执行的风险。 - Richard Chambers
1
这段代码存在资源泄漏。在makegen()中启动的goroutine永远不会退出:它将尝试向通道发送内容,但在从循环中断开后,没有人会从该通道读取内容。 (当然,在这种情况下,主函数main()退出,这并不重要,但在长时间运行的程序中,这样的泄漏可能很难跟踪。) - jcsahnwaldt Reinstate Monica
1
@jcsahnwaldtReinstateMonica 您是在说makegen()正在生成一系列组合并将它们发送到main()中的rangemain()只会从该通道中读取,直到看到特定字符串然后停止从该通道中读取。然而,makegen()中的goroutine将继续创建序列并将阻塞。结果是整个单词序列集不会生成,也不会让goroutine close()该通道并退出。结果是一个开放通道和goroutine的资源泄漏? - Richard Chambers
1
@jcsahnwaldtReinstateMonica 我添加了一个微调版本的生成器。如果这解决了您的问题,请告诉我。感谢您让我知道资源泄漏的问题。 - Richard Chambers
1
看起来不错!您可以使用 defer 简化一下代码。例如,请参考 https://go.dev/play/p/7TaliSGtd-K 但我猜这是代码易读性的问题...... - jcsahnwaldt Reinstate Monica
显示剩余5条评论

6

将for循环包含在匿名自执行函数中,需要时使用return语句跳出循环。

package main

func main() {
    func() {
        for i:= 0; i < 100; i++ {
            for j:= 0; j < 100; j++ {
                if (i == 5 && j == 5) {
                    return
                }
            }
        }
    }()
}

5

只需推迟需要完成的任何事情,然后按照正常流程返回。

package main

import (
    "fmt"
)

func main() {
    defer func() {
        // More logic here that needs to be executed
    }()

    word := ""

    for _, i := range "ABCDE" {
        for _, j := range "ABCDE" {
            word = string(i) + string(j)
            fmt.Println(word)
            if word == "DC" {
                return
            }
        }
    }
}

1
那会导致可怕的代码。 - David Schmitt

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