[Golang早读] 谈谈for 和 for-range

195 阅读3分钟

在程序开发中,我们经常要通过循环遍历数据,golang中没有引入while关键字,但是引入了另一个关键字range,我们可以通过for-range循环遍历数据。

类型forfor-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类似迭代器操作,返回(索引,值)或(键,值)

类型第一个返回值第二个返回值
stringindexs[index]
array/sliceindexs[index]
mapkeym[key]
channelelem

注意:使用范围遍历和使用普通遍历是有区别的,范围循环会在遍历之前先拷贝一份被遍历的数据,然后遍历拷贝的数据

区别

循环永动机

例:当我们想要实现一个“循环永动机”时

//使用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

1672816266440.png

通过地址我们可以很明显的看出,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类型,此时中文也能够正常输出