Go 中的循环语句:for,break,continue,goto,range

145 阅读5分钟

循环语句

Go语言中只有for循环,Go中的for 循环可以实现其他语言中的 for ,while 和遍历迭代

for循环

for 循环是一个循环控制结构,可以执行指定次数的循环。

语法

Go 语言中的 For 循环有四种形式,只有其中的一种使用分号

第一种和普通的for 循环一样
for init; condition; post { }
//init 一般为赋值表达式,给控制变量赋初值
//condition 关系表达式或者或者逻辑表达式,循环控制条件
// post 一般为赋值表达式,给给控制变量增量或者减量

第二种和 while 类似的for循环
for condition { }

第三中无限循环
//无限循环  相当于 for true {}
for { }

for 循环中的 `range`格式可以对 slice、map 、数组、字符串 等进行迭代循环。格式如下:
for key, value := range oldMap {
    newMap[key] = value
}

for 语句执行的过程

  1. 先对表达式init 赋初值;
  2. 判别赋值表达式 init 是否满足给定条件condition 若其值为真,满足循环条件,则执行循环体内语句,然后执行post, 进入第二次循环,判别condition 的值为假,不满足条件就终止for循环。

循环嵌套

Go 语言中允许循环嵌套:

for [condition |  ( init; condition; increment ) | Range]
{
   for [condition |  ( init; condition; increment ) | Range]
   {
      statement(s);
   }
   statement(s);
}

循环控制

循环控制语句可以控制循环体内语句的执行过程。

控制语句描述
break经常用于中断当前for 循环或者跳出 switch 语句
continue跳过当前循环的剩余语句,然后继续进行下一轮循环
goto将控制转移到被标记的语句
  1. 这三个语句都可以配合标签label 使用
  2. 标签区分大小写,定义以后若不使用会造成编译错误
  3. continue 、break 配合标签 label 可以用于多层循环跳出
  4. goto 是调整执行位置,于 continue、break 配合标签 label 的结果并不相同

break

  1. 用于循环语句中跳出循环,并开始执行循环之后的语句
  2. break 在 switch (开关语句) 中在执行一条case 后跳出语句的作用
  3. 在多重循环中可以用标号 label 标出像break 的循环。

例子:

package main

import "fmt"

func main() {

	// 不使用标记
	fmt.Println("---- break ----")
	for i := 1; i <= 3; i++ {
		fmt.Printf("i: %d\n", i)
		for i2 := 11; i2 <= 13; i2++ {
			fmt.Printf("i2: %d\n", i2)
			break
		}
	}

	// 使用标记
	fmt.Println("---- break label ----")
re:   //re是一个标记
	for i := 1; i <= 3; i++ {
		fmt.Printf("i: %d\n", i)
		for i2 := 11; i2 <= 13; i2++ {
			fmt.Printf("i2: %d\n", i2)
			break re
		}
	}
}

continue

  1. continue 语句有点像 break 语句。但是continue 不是跳出循环,而是跳过当前循环执行的下一个循环语句。
  2. for 循环中,执行 continue 语句会触发 for 增量语句increment 的执行。
  3. 在多重循环中,可以用标号 label 标出想 continue 的循环。

goto

  1. go 语言中的 goto 语句 可以无条件地转移到过程中指定的行。
  2. goro 语句通常与条件语句配合使用。可以用来实现条件转移,构成循环,跳出循环等功能

但是在结构化程序设计中一般不主张使用go 语句,以免造成流程的混乱,使理解和调试程序都产生困难。
goto 的语法格式:

goto label;
..
.
label: statement;

循环语句range

语法

Golang range 类似迭代器操作,返回(索引,值)(键,值) for 循环的 range 格式可以对slice、map、数组、字符串 等进行迭代循环:

for key, value := range oldMap {
    newMap[key] = value
}

选择接收值

可只设置一个接收器忽略 range 返回的第二个值,或使用 “_” 这个特殊变量忽略第一个返回值。没有接收器就是忽略全部返回值。

package main

func main() {
	s := "abc"
	// 忽略 2nd value,支持 string/array/slice/map。
	for i := range s {
		println(s[i])
	}
	// 忽略 index。
	for _, c := range s {
		println(c)
	}
	// 忽略全部返回值,仅迭代。
	for range s {

	}

	m := map[string]int{"a": 1, "b": 2}
	// 返回 (key, value)。
	for k, v := range m {
		println(k, v)
	}
}

range 会复制值对象

package main

import "fmt"

func main() {
	a := [3]int{0, 1, 2}

	for i, v := range a { // range复制数组a。index、value 都是从复制品中取出。

		if i == 0 { // 我们先修改原数组。
			a[1], a[2] = 999, 999
			fmt.Println(a) // 确认修改有效,输出 [0, 999, 999]。
		}

		a[i] = v + 100 // range 使用复制品中取出的 value 修改原数组。

	}

	fmt.Println(a) 
}

输出结果:

[0 999 999]
[100 101 102]  //而不是[100 1099 1099]

建议改成引用数据类型,其底层数组不会被复制

package main

func main() {
	s := []int{1, 2, 3, 4, 5}

	for i, v := range s { // s是切片,一种引用类型。range复制slice的struct:{ pointer, len, cap }。
		if i == 0 {
			s = s[:3]  // 对 slice 的修改,不会影响 range。
			s[2] = 100 // 对底层数据的修改。
		}
		println(i, v)
	}
}

输出结果:

0 1
1 2
2 100
3 4
4 5

另外两种引用类型 map、channel 是指针包装,而不像 slice 是 struct。

for 和 for range 的区别

它们的区别主要是使用场景不同。

for可以:
  1. 遍历array和slice
  2. 遍历key为整型递增的map
  3. 遍历string
for range可以完成所有for可以做的事情,却能做到for不能做的,包括
  1. 遍历key为string类型的map并同时获取key和value
  2. 遍历channel