for循环
在其他编程语言中,循环一般有for和while循环两种方式,但是在Go中,就只有一种,那就是for循环。
1. for 循环流程
|
|
从图中我们看到,经典 for 循环语句有四个组成部分(分别对应图中的①~④)。我们按顺序拆解一下这张图。
图中①对应的组成部分执行于循环体(③ )之前,并且在整个 for 循环语句中仅会被执行一次,它也被称为循环前置语句。我们通常会在这个部分声明一些循环体(③ )或循环控制条件(② )会用到的自用变量,也称循环变量或迭代变量,比如这里声明的整型变量 i。与 if 语句中的自用变量一样,for 循环变量也采用短变量声明的形式,循环变量的作用域仅限于 for 语句隐式代码块范围内。
图中②对应的组成部分,是用来决定循环是否要继续进行下去的条件判断表达式。和 if 语句的一样,这个用于条件判断的表达式必须为布尔表达式,如果有多个判断条件,我们一样可以由逻辑操作符进行连接。当表达式的求值结果为 true 时,代码将进入循环体(③)继续执行,相反则循环直接结束,循环体(③)与组成部分④都不会被执行。
前面也多次提到了,图中③对应的组成部分是 for 循环语句的循环体。如果相关的判断条件表达式求值结构为 true 时,循环体就会被执行一次,这样的一次执行也被称为一次迭代(Iteration)。在上面例子中,循环体执行的动作是将这次迭代中变量 i 的值累加到变量 sum 中。
图中④对应的组成部分会在每次循环体迭代之后执行,也被称为循环后置语句。这个部分通常用于更新 for 循环语句组成部分①中声明的循环变量,比如在这个例子中,我们在这个组成部分对循环变量 i 进行加 1 操作。
Go 语言的 for 循环也在 C 语言的基础上有一些突破和创新。具体一点,Go 语言的 for 循环支持声明多循环变量,并且可以应用在循环体以及判断条件中,比如下面就是一个使用多循环变量的、稍复杂的例子:
|
|
2. 经典格式
基本格式用的最多
|
|
2.1 格式变形1省略了初始值
|
|
省略循环前置语句。比如下面例子中,我们就没有使用前置语句声明循环变量,而是直接使用了已声明的变量 i 充当循环变量的作用
2.2 变形2把自变量放到了循环体内
前置语句和后置语句都省略的写法
|
|
当循环前置与后置语句都省略掉,仅保留循环判断条件表达式时,我们可以省略经典 for 循环形式中的分号
|
|
2. 无限循环
会不停的输出123
|
|
当 for 循环语句的循环判断条件表达式的求值结果始终为 true 时,我们就可以将它省略掉了。它等价于
|
|
或者
|
|
日常使用时,建议用它的最简形式,也就是for {…},更加简单。后面两种知道即可。记住经典写法就好了,别给自己增加负担,那些骚操作。
3. for range
可以对比一下python,python中也有类似 for range这样的用法!
经典写法
|
|
变量 i 作为切片下标,逐一将切片中的元素读取了出来。不过,这样就有点麻烦了。其实,针对像切片这样的复合数据类型,还有 Go 原生的字符串类型(string),Go 语言提供了一个更方便的“语法糖”形式:for range。现在我们就来写一个等价于上面代码的 for range 循环:for i, v := range sl { fmt.Printf(“sl[%d] = %d\n”, i, v)
|
|
我们看到,for range 循环形式与 for 语句经典形式差异较大,除了循环体保留了下来,其余组成部分都“不见”了。其实那几部分已经被融合到 for range 的语义中了。
具体来说,这里的 i 和 v 对应的是经典 for 语句形式中循环前置语句的循环变量,它们的初值分别为切片 sl 的第一个元素的下标值和元素值。并且,隐含在 for range 语义中的循环控制条件判断为:是否已经遍历完 sl 的所有元素,等价于i < len(sl)这个布尔表达式。另外,每次迭代后,for range 会取出切片 sl 的下一个元素的下标和值,分别赋值给循环变量 i 和 v,这与 for 经典形式下的循环后置语句执行的逻辑是相同的。
3.1 变形1
当我们不关心元素的值时,我们可以省略代表元素值的变量 v,只声明代表下标值的变量 i:
|
|
3.2 变形2
如果我们不关心元素下标,只关心元素值,那么我们可以用空标识符替代代表下标值的变量 i。这里一定要注意,这个空标识符不能省略,否则就与上面的“变种一”形式一样了,Go 编译器将无法区分:
|
|
3.3 变形3
到这里,你肯定要问:如果我们既不关心下标值,也不关心元素值,那是否能写成下面这样呢:
|
|
这种形式在语法上没有错误,就是看起来不太优雅。Go 核心团队早在Go 1.4 版本中就提供了一种优雅的等价形式,你后续直接使用这种形式就好了:
|
|
不过一般很少用,我们既然进行了循环,肯定是有用的啊,怎么会一个值都不取,干循环呢?
好了,讲完了 for range 针对切片这种复合类型的各种形式后,我们再来看看 for range 应该如何用于对其他复合类型,或者是对 string 类型进行循环操作。for range 针对不同复合数据类型进行循环操作时,虽然语义是相同的,但它声明的循环变量的含义会有所不同,我们有必要逐一看一下。
4. for range遍历String类型😀
看下面例子
|
|
运行这个例子,输出结果是这样的:
|
|
我们看到:for range 对于 string 类型来说,每次循环得到的 v 值是一个 Unicode 字符码点,也就是 rune 类型值,而不是一个字节,返回的第一个值 i 为该 Unicode 字符码点的内存编码(UTF-8)的第一个字节在字符串内存序列中的位置。
可以看到,这个就和切片类型的不同了,首先下标i的不同,第二个,值v的不同。另外我要在这里再次提醒你,使用 for 经典形式与使用 for range 形式,对 string 类型进行循环操作的语义是不同的。 [[华为外包机考]] 这里面很多都是关于字符串操作的,都需要转一下。
5. for range遍历map类型 😀
map 就是一个键值对(key-value)集合,最常见的对 map 的操作,就是通过 key 获取其对应的 value 值。但有些时候,我们也要对 map 这个集合进行遍历,这就需要 for 语句的支持了.
但在 Go 语言中,我们要对 map 进行循环操作,for range 是唯一的方法,for 经典循环形式是不支持对 map 类型变量的循环控制的。下面是通过 for range,对一个 map 类型变量进行循环操作的示例:
|
|
out
|
|
通过输出结果我们看到:for range 对于 map 类型来说,每次循环,循环变量 k 和 v 分别会被赋值为 map 键值对集合中一个元素的 key 值和 value 值。而且,map 类型中没有下标的概念,通过 key 和 value 来循环操作 map 类型变量也就十分自然了。 [[map基础篇#9. 对map按照key排序]]
6. for range 遍历channel 😀
除了可以针对 string、数组 / 切片,以及 map 类型变量进行循环操作控制之外,for range 还可以与 channel 类型配合工作。channel 是 Go 语言提供的并发设计的原语,它用于多个 Goroutine 之间的通信,我们在后面的课程中还会详细讲解 channel。当 channel 类型变量作为 for range 语句的迭代对象时,for range 会尝试从 channel 中读取数据,使用形式是这样的:
|
|
在这个例子中,for range 每次从 channel 中读取一个元素后,会把它赋值给循环变量 v,并进入循环体。当 channel 中没有数据可读的时候,for range 循环会阻塞在对 channel 的读操作上。直到 channel 关闭时,for range 循环才会结束,这也是 for range 循环与 channel 配合时隐含的循环判断条件。
详细的看后面的channel部分的介绍吧。
|
|
6. break
和python中break用法相通,这里面就用到了for循环和if判断结合使用,经常这么用。
|
|
7. continue
和python中continue用法相通
|
|
8. for循环中使用哑元变量
如果不想定义变量或者用不到,可以使用匿名变量进行接收。这样也不需要捕捉err。
|
|
9. for循环中使用flag
这种写法在实际编码中也会经常用,设置一个标志flag,当符合一定条件时,flag置为true。
|
|
10. goto
第九个例子是flag为true时,跳出
外循环,这一个是当flag为true时,跳转到
指定语句执行
|
|