如何在Go中构建For循环

简介

在计算机编程中,循环是一种代码结构,它会重复执行一段代码,通常直到满足某个条件为止。在计算机编程中使用循环可以让你自动化并多次重复相似的任务。想象一下,如果你需要处理一个文件列表,或者你想计算一篇文章中的行数,你会在代码中使用循环来解决这些问题。

在Go语言中,for 循环实现了基于循环计数器或循环变量的代码重复执行。与其他具有多种循环构造,如whiledo等编程语言不同,Go只有一种for 循环。这使得你的代码更加清晰易读,因为你无需担心使用多种策略来实现相同的循环结构。这种提高的可读性和降低的认知负担也会使你的代码比其他语言更容易避免错误。

在本教程中,你将学习Go语言中for 循环的工作原理,包括其使用的三大变体。我们将首先展示如何创建不同类型的for 循环,然后是如何遍历Go中的顺序数据类型。最后,我们将解释如何使用嵌套循环。

声明ForClause和条件循环

为了应对各种用例,Go语言中有三种创建for循环的方式,每种方式都有其自身的特点。这些是创建具有条件ForClauseRangeClausefor循环。在本节中,我们将解释如何声明和使用ForClause和条件变体。

首先让我们看看如何使用带有ForClause的for循环。

带有ForClause的ForClause循环定义为具有初始声明,后跟条件,然后是后声明。它们按照以下语法排列:

for [ Initial Statement ] ; [ Condition ] ; [ Post Statement ] {
    [Action]
}

为了解释前面组件的作用,让我们看看使用ForClause语法的for循环如何通过指定范围递增值:

for i := 0; i < 5; i++ {
	fmt.Println(i)
}

让我们分解这个循环并识别每个部分。

循环的第一部分是i := 0。这是初始声明:

for i := 0; i < 5; i++ {
	fmt.Println(i)
}

它声明了一个名为i的变量,并将初始值设置为0

接下来是条件:

for i := 0; i < 5; i++ {
	fmt.Println(i)
}

在这个条件中,我们表示,只要i小于5的值,循环应该继续循环。

最后,我们有后声明:

for i := 0; i < 5; i++ {
	fmt.Println(i)
}

在for循环的声明中,我们使用i++增量操作符每次迭代时将循环变量i增加1。

当我们运行这个程序时,输出结果如下:

Output
0 1 2 3 4

循环运行了5次。最初,它将i设置为0,然后检查i是否小于5。由于i的值小于5,循环执行了,并执行了fmt.Println(i)动作。循环结束后,调用了i++的后置声明,i的值增加了1。

注意:请记住,在编程中我们通常从索引0开始,这就是为什么虽然输出了5个数字,但它们的范围是0-4。

我们不仅限于从0开始或以指定的值结束。我们可以为我们的初始声明分配任何值,在后置声明中也可以停止任何值。这允许我们创建任何所需的范围来循环遍历:

for i := 20; i < 25; i++ {
	fmt.Println(i)
}

在这里,迭代从20(包含)到25(不包含),所以输出结果如下:

Output
20 21 22 23 24

我们还可以使用后置声明以不同的值增加。这与其他语言中的步长类似:

首先,我们使用带有正值的后置声明:

for i := 0; i < 15; i += 3 {
	fmt.Println(i)
}

在这个例子中,for循环设置为从0到15打印数字,但以3为增量,所以只打印每第三个数字,如下所示:

Output
0 3 6 9 12

我们也可以使用负值作为我们的post语句参数来倒序迭代,但是我们必须相应地调整我们的初始声明和条件参数:

for i := 100; i > 0; i -= 10 {
	fmt.Println(i)
}

在这里,我们将i设置为初始值为100,使用条件i < 0来在i达到0时停止,并在post语句中使用-=操作符将i减少10。循环从100开始,结束于0,每次迭代减少10。我们可以通过输出看到这一点:

Output
100 90 80 70 60 50 40 30 20 10

您也可以省略初始声明和后置语句,只使用条件。这被称为条件循环

i := 0
for i < 5 {
	fmt.Println(i)
	i++
}

这一次,我们在循环之前单独声明了变量i。循环只有检查i是否小于5的条件。只要条件评估为true,循环就会继续执行。

有时候你可能不知道你需要完成多少次迭代才能完成某个任务。在这种情况下,你可以省略所有的语句,并使用break关键字退出执行:

for {
	if someCondition {
break
}
	// do action here
}

一个例子可能是如果我们正在从大小不确定的结构(如缓冲区)中读取,并且我们不知道何时会读完:

buffer.go
package main

import (
	"bytes"
	"fmt"
	"io"
)

func main() {
	buf := bytes.NewBufferString("one\ntwo\nthree\nfour\n")

	for {
		line, err := buf.ReadString('\n')
		if err != nil {
			if err == io.EOF {

				fmt.Print(line)
				break
			}
			fmt.Println(err)
			break
		}
		fmt.Print(line)
	}
}

在前面的代码中,buf :=bytes.NewBufferString("one\ntwo\nthree\nfour\n") 声明了一个包含一些数据的缓冲区。因为我们不知道何时会读完缓冲区,所以我们创建了一个没有子句的 for 循环。在 for 循环内部,我们使用 line, err := buf.ReadString('\n') 从缓冲区中读取一行,并检查是否有从缓冲区读取时的错误。如果有的话,我们处理错误,并使用 break 关键字退出 for 循环。有了这些 break 点,你不需要包含一个停止循环的条件。

在本节中,我们学习了如何声明 ForClause 循环并使用它来遍历一个已知的值范围。我们还学习了如何使用 Condition 循环在满足特定条件时进行迭代。接下来,我们将学习如何使用 RangeClause 遍历顺序数据类型。

使用 RangeClause 遍历顺序数据类型

在Go语言中,通常使用for循环来遍历序列或集合数据类型,如切片(slices)、数组和字符串(strings)。为了更容易地进行这种操作,我们可以使用带有RangeClause语法的for循环。尽管你可以使用ForClause语法遍历顺序数据类型,但RangeClause更简洁,也更容易阅读。

在我们查看如何使用RangeClause之前,让我们先看看如何使用ForClause语法遍历切片:

main.go
package main

import "fmt"

func main() {
	sharks := []string{"hammerhead", "great white", "dogfish", "frilled", "bullhead", "requiem"}

	for i := 0; i < len(sharks); i++ {
		fmt.Println(sharks[i])
	}
}

运行这段代码将会输出以下内容,打印出切片的每个元素:

Output
hammerhead great white dogfish frilled bullhead requiem

现在,让我们使用RangeClause来执行相同的操作:

main.go
package main

import "fmt"

func main() {
	sharks := []string{"hammerhead", "great white", "dogfish", "frilled", "bullhead", "requiem"}

	for i, shark := range sharks {
		fmt.Println(i, shark)
	}
}

在这个例子中,我们打印出列表中的每个项目。虽然我们使用了ishark这两个变量,但我们本可以将变量命名为其他任何有效的变量名,并且我们仍然会得到相同的输出:

Output
0 hammerhead 1 great white 2 dogfish 3 frilled 4 bullhead 5 requiem

当对切片使用range时,它总是返回两个值。第一个值是当前循环迭代的索引,第二个值是该索引处的值。在这个例子中,第一次迭代的索引是0,值是hammerhead

有时,我们只想打印切片中的元素值,而不想要索引。如果我们将前面的代码更改为只打印值,则会收到编译时错误:

main.go
package main

import "fmt"

func main() {
	sharks := []string{"hammerhead", "great white", "dogfish", "frilled", "bullhead", "requiem"}

	for i, shark := range sharks {
		fmt.Println(shark)
	}
}
Output
src/range-error.go:8:6: i declared and not used

因为i是在循环中声明的,但从未使用过,因此编译器会响应i declared and not used(i已声明但未使用)。这是Go中任何时间你声明一个变量而没有使用它时都会出现的错误。

由于这个原因,Go有一个空白标识符(空格),在for循环中,你可以使用空白标识符忽略range关键字返回的任何值。在这种情况下,我们想要忽略索引,这是range关键字返回的两个参数中的第一个。

main.go
package main

import "fmt"

func main() {
	sharks := []string{"hammerhead", "great white", "dogfish", "frilled", "bullhead", "requiem"}

	for _, shark := range sharks {
		fmt.Println(shark)
	}
}
Output
hammerhead great white dogfish frilled bullhead requiem

这显示了循环迭代了字符串切片,并打印了切片中的每个项目而没有索引。

你也可以使用range来向列表添加项:

main.go
package main

import "fmt"

func main() {
	sharks := []string{"hammerhead", "great white", "dogfish", "frilled", "bullhead", "requiem"}

	for range sharks {
		sharks = append(sharks, "shark")
	}

	fmt.Printf("%q\n", sharks)
}
Output
['hammerhead', 'great white', 'dogfish', 'frilled', 'bullhead', 'requiem', 'shark', 'shark', 'shark', 'shark', 'shark', 'shark']

这里,我们在sharks切片的长度上为每个项目添加了一个占位符字符串"shark"

注意,如果我们不需要使用range操作符返回的任何值,我们可以完全省略range语句的声明部分。Go允许我们省略range语句的整个声明部分,如果不需要使用两个返回值的话。

我们还可以使用range运算符来填充切片:

main.go
package main

import "fmt"

func main() {
	integers := make([]int, 10)
	fmt.Println(integers)

	for i := range integers {
		integers[i] = i
	}

	fmt.Println(integers)
}

在这个示例中,切片 integers 初始化为十个空值,但是 for 循环设置了列表中的所有值:

Output
[0 0 0 0 0 0 0 0 0 0] [0 1 2 3 4 5 6 7 8 9]

第一次打印切片 integers 的值时,我们看到全部是零。然后我们遍历每个索引,并将值设置为当前索引。然后当我们第二次打印 integers 的值时,显示它们现在都有从 09 的值。

我们还可以使用 range 操作符来遍历字符串中的每个字符:

main.go
package main

import "fmt"

func main() {
	sammy := "Sammy"

	for _, letter := range sammy {
		fmt.Printf("%c\n", letter)
	}
}
Output
S a m m y

当遍历一个 map 时,range 将返回

main.go
package main

import "fmt"

func main() {
	sammyShark := map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}

	for key, value := range sammyShark {
		fmt.Println(key + ": " + value)
	}
}
Output
color: blue location: ocean name: Sammy animal: shark

注意:请注意,地图返回的顺序是随机的。每次运行此程序时,您可能得到不同的结果。

现在我们已经学会了如何使用 range for 循环遍历顺序数据,让我们来看看如何在循环中使用循环。

嵌套循环

在Go语言中,循环可以像在其他编程语言中一样嵌套。嵌套是指我们将一个结构放在另一个结构内部。在这种情况下,嵌套循环是发生在另一个循环内部的循环。当您希望对数据集中的每个元素执行循环操作时,嵌套循环非常有用。

嵌套循环在结构上与嵌套if语句相似。它们的结构如下:

for {
    [Action]
    for {
        [Action]  
    }
}

程序首先遇到外层循环,执行其第一次迭代。这次迭代触发了内嵌的、嵌套循环,然后它运行到完成。然后程序返回到外层循环的顶部,完成第二次迭代并再次触发嵌套循环。同样,嵌套循环运行到完成,程序再返回到外层循环的顶部,直到序列完成或break或其他语句打断过程。

让我们实现一个嵌套的for循环,以便我们可以更仔细地观察。在这个例子中,外层循环将遍历一个名为numList的整数切片,内层循环将遍历一个名为alphaList的字符串切片。

main.go
package main

import "fmt"

func main() {
	numList := []int{1, 2, 3}
	alphaList := []string{"a", "b", "c"}

	for _, i := range numList {
		fmt.Println(i)
		for _, letter := range alphaList {
			fmt.Println(letter)
		}
	}
}

当我们运行这个程序时,我们将得到以下输出:

Output
1 a b c 2 a b c 3 a b c

输出显示程序通过打印1完成了外层循环的第一次迭代,这随后触发了内层循环的完成,连续打印abc。一旦内层循环完成,程序返回到外层循环的顶部,打印2,然后再次打印整个内层循环(abc),等等。

嵌套的for循环在遍历由切片组成的切片中非常有用。在一个由切片组成的切片中,如果我们只使用一个for循环,程序将输出每个内部列表作为一个项目:

main.go
package main

import "fmt"

func main() {
	ints := [][]int{
		[]int{0, 1, 2},
		[]int{-1, -2, -3},
		[]int{9, 8, 7},
	}

	for _, i := range ints {
		fmt.Println(i)
	}
}
Output
[0 1 2] [-1 -2 -3] [9 8 7]

为了访问内部切片的每个单独的项目,我们将实现一个嵌套的for循环:

main.go
package main

import "fmt"

func main() {
	ints := [][]int{
		[]int{0, 1, 2},
		[]int{-1, -2, -3},
		[]int{9, 8, 7},
	}

	for _, i := range ints {
		for _, j := range i {
			fmt.Println(j)
		}
	}
}
Output
0 1 2 -1 -2 -3 9 8 7

当我们在这里使用嵌套的for循环时,我们能够遍历包含在切片中的各个项目。

结论

在本教程中,我们学习了如何声明和使用for循环来解决Go中的重复任务。我们还学习了for循环的三种不同变体以及何时使用它们。为了了解更多关于for循环以及如何控制它们的流程,请阅读在Go中使用break和continue语句时循环的工作

Source:
https://www.digitalocean.com/community/tutorials/how-to-construct-for-loops-in-go