在程序开发中,我们经常要通过循环遍历数据,golang中没有引入while关键字,但是引入了另一个关键字range,我们可以通过for-range循环遍历数据。
| 类型 | for | for-range |
|---|---|---|
| string | √ | √ |
| array/slice | √ | √ |
| map | √(只支持key为整型的情况) | √ |
| channel | × | √ |
我们通常称for循环为普通循环或经典循环,而称for-range为范围循环
for循环
go中的for循环有3种形式,只有其中一种使用分号
//init: 一般为赋值表达式,给控制变量赋初值;
//condition: 关系表达式或逻辑表达式,循环控制条件;
//post: 一般为赋值表达式,给控制变量增量或减量。
//第一种,也是最经典的for循环格式
for init; condition; post { }
//第二种,条件判断是否继续循环执行
for condition { }
//第三种,由于没有退出判断,因此内部需要手动break,否则就会死循环
for { }
for-range循环
golang的range类似迭代器操作,返回(索引,值)或(键,值)
| 类型 | 第一个返回值 | 第二个返回值 |
|---|---|---|
| string | index | s[index] |
| array/slice | index | s[index] |
| map | key | m[key] |
| channel | elem |
注意:使用范围遍历和使用普通遍历是有区别的,范围循环会在遍历之前先拷贝一份被遍历的数据,然后遍历拷贝的数据
区别
循环永动机
例:当我们想要实现一个“循环永动机”时
//使用for-range
s := []int{0, 1}
for i := range s {
s = append(s, s[i])
}
fmt.Printf("s=%v\n", s)
//s=[0 1 0 1]
//使用for循环
for i := 0; i < len(s); i++ {
s = append(s, s[i])
}
fmt.Printf("s=%v\n", s)
//无法输出结果
阅读上面这两段代码,输出结果不同的原因就是,范围遍历在开始遍历数据之前,会先拷贝一份被遍历的数据,所以在遍历过程中去修改被遍历的数据,只是修改拷贝的数据,不会影响到原数据。而普通遍历一直在遍历添加,因此无法返回最后的结果
神奇的指针
func main() {
arr := []int{1, 2, 3}
newArr := []*int{}
for _, v := range arr {
// 正确的做法应该是使用 `&arr[i]` 替代 `&v`
newArr = append(newArr, &v)
}
for _, v := range newArr {
fmt.Println(*v)
}
}
//3 3 3
通过地址我们可以很明显的看出,for-range在每次遍历时生成的v都是同一个,只是在每次遍历时将值复制到v,因此最后返回的是3 3 3
奇怪的字符串
tmp := "abc%#中农cd"
for i, v := range tmp {
fmt.Printf("%d, %T, %v\n", i, v, string(v))
}
fmt.Println("========")
for i := range tmp {
fmt.Printf("%d, %T, %v\n", i, tmp[i], string(tmp[i]))
}
fmt.Println("========")
for i := 0; i < len(tmp); i++ {
fmt.Printf("%d, %T, %v\n", i, tmp[i], string(tmp[i]))
}
0, int32, a
1, int32, b
2, int32, c
3, int32, %
4, int32, #
5, int32, 中
8, int32, 农
11, int32, c
12, int32, d
========
0, uint8, a
1, uint8, b
2, uint8, c
3, uint8, %
4, uint8, #
5, uint8, ä
8, uint8, å
11, uint8, c
12, uint8, d
========
0, uint8, a
1, uint8, b
2, uint8, c
3, uint8, %
4, uint8, #
5, uint8, ä
6, uint8, ¸
7, uint8,
8, uint8, å
9, uint8,
10, uint8,
11, uint8, c
12, uint8, d
遍历字符串时,如果我们是根据索引来遍历,那么返回的每个字符都是uint8类型,也就是byte类型,如果碰到中文等特殊字符的话,就会出现乱码,因为一个汉字要占3个byte位。
当我们通过for-range遍历每一个字符时,返回的字符是int32类型,也就是rune类型,此时中文也能够正常输出