本篇文章基于 Golang 1.17.2

话说胖虎上次没有问到实习生,感觉实习生底子不错,最近闲来无事,决定在考考实习生。

for 循环的奇怪景象

胖虎:以下代码输入什么

package mainimport "fmt"func main() {    s := []int{0, 1}    for num := 0; num < len(s); num++ {        s = append(s, s[num])    }  fmt.Printf("s的值是:%+v\n", s)}

实习生吐口而出:

[0 1 0 1]

胖虎笑着说:不要焦急答复,思考三秒后在说出你的答案。

实习生: 难道不对?

胖虎看着实习生纳闷的表情,说:咱们来执行下吧

居然是死循环!!!实习生差点喊出来,同时发现胖虎奋斗多年的笔记本,风扇吭吭唧唧不愿意的开始干活了。

胖虎:如果想要 0 1 0 1,应该怎么改呢?

实习生:难道用range?边说边敲下以下代码。

package mainimport "fmt"func main() {    s := []int{0, 1}    for _, value := range s {        s = append(s, value)    }    fmt.Printf("s的值是:%+v\n", s)}

胖虎:那你晓得为什么吗?
实习生:范畴循环在迭代过程中,难道是迭代值的拷贝?我再试下吧。

package mainimport "fmt"func main() {    s := []int{0, 1}    for _, value := range s {        value += 10    }    fmt.Printf("s的值是:%+v\n", s)}

实习生:跟我猜测的一样。

胖虎点点头:范畴遍历在开始遍历数据之前,会先拷贝一份被遍历的数据,所以在遍历过程中去批改被遍历的数据,只是批改拷贝的数据,不会影响到原数据。

而一般for循环,会始终一直追加数据到切片,对原数据产生影响。

胖虎:顺便温习一下这两个的应用语法吧

for 应用语法

s := "test"// 常见的 for 循环,反对初始化语句。for i, n := 0, len(s); i < n; i++ {     println(string(s[i]))}initNum := 0intSlice := []int{0, 1}sliceLen := len(intSlice)for initNum < sliceLen {    initNum++    fmt.Println(intSlice[initNum])}for {            // 代替 while (true) {}    println(s)   // 代替 for (;;) {}}

for 常见应用场景

由此可见for常见的应用场景是字符串,数组,切片和有限循环。但须要留神的是 for 循环字符串的时候,后果想要为字符串的时候,须要string转换一下,而有的编程语言不须要,比如说世界上最好的语言。

当然应用 range 遍历字符串就没有这个问题。

range 应用语法

words := []string{"Go", "Java", "C++"}for i, value := range words {    words = append(words, "test")    fmt.Println(i, value)}test := "abc"// 疏忽 valuefor i := range s {    fmt.Println(s[i])}

range 常见应用场景

Go 语言中,range 应用场景除了数组(array)、切片(slice),还能够很不便字典(map)和信道(chan)

遍历map

m := map[string]int{        "one":   1,        "two":   2,        "three": 3,    }    for k, v := range m {        delete(m, "two")        m["four"] = 4    fmt.Printf("key:%v, value:%v\n", k, v)    }

输入后果:

key:two, value:2key:three, value:3key:one, value:1

须要留神的是:

  • 和切片不同的是,迭代过程中,删除还未迭代到的键值对,则该键值对不会被迭代。
  • 在迭代过程中,如果创立新的键值对,那么新增键值对,可能被迭代,也可能不会被迭代。
  • 针对 nil 字典,迭代次数为 0

遍历channel

ch := make(chan string)go func() {    ch <- "听我说"    ch <- "谢谢你"    ch <- "因为有你"    ch <- "和煦了四季"    close(ch)}()for n := range ch {    fmt.Println(n)}

后果如下:

听我说谢谢你因为有你和煦了四季

range 循环性能肯定比for差吗?

[]int

package mainimport (    "math/rand"    "testing"    "time")func generateWithCap(n int) []int {    //生成不同系列的随机数    rand.Seed(time.Now().UnixNano())    nums := make([]int, 0, n)    for i := 0; i < n; i++ {        nums = append(nums, rand.Int())    }    return nums}func BenchmarkForIntSlice(b *testing.B) {    nums := generateWithCap(1024 * 1024)    for i := 0; i < b.N; i++ {        len := len(nums)        var tmp int        for k := 0; k < len; k++ {            tmp = nums[k]        }        _ = tmp    }}func BenchmarkRangeIntSlice(b *testing.B) {    nums := generateWithCap(1024 * 1024)    for i := 0; i < b.N; i++ {        var tmp int        for _, num := range nums {            tmp = num        }        _ = tmp    }}

执行后果如下:

goos: darwingoarch: amd64cpu: Intel(R) Core(TM) i5-1038NG7 CPU @ 2.00GHzBenchmarkForIntSlice-8              3552            334038 ns/opBenchmarkRangeIntSlice-8            3544            321965 ns/op

名词解释:

BenchmarkForIntSlice-8,即 GOMAXPROCS,默认等于 CPU 核数。

3552 代表运行了多少次

334038 ns/op 每次执行均匀工夫,

由此可见 ,遍历 []int 类型的切片,for 与 range 性能两者简直没有区别。

[]struct

如果是略微简单一点的 []struct 类型呢?

type Item struct {    id  int    val [4096]byte}//for 循环func BenchmarkForStruct(b *testing.B) {    var items [1024]Item    for i := 0; i < b.N; i++ {        length := len(items)        var tmp int        for k := 0; k < length; k++ {            tmp = items[k].id        }        _ = tmp    }}//range 循环只取下标func BenchmarkRangeIndexStruct(b *testing.B) {    var items [1024]Item    for i := 0; i < b.N; i++ {        var tmp int        for k := range items {            tmp = items[k].id        }        _ = tmp    }}//range 循环取值func BenchmarkRangeStruct(b *testing.B) {    var items [1024]Item    for i := 0; i < b.N; i++ {        var tmp int        for _, item := range items {            tmp = item.id        }        _ = tmp    }}

执行后果如下:

goos: darwingoarch: amd64cpu: Intel(R) Core(TM) i5-1038NG7 CPU @ 2.00GHzBenchmarkForStruct-8             3373856               359.7 ns/opBenchmarkRangeIndexStruct-8      3587757               329.0 ns/opBenchmarkRangeStruct-8              3478            288161 ns/op
  • 仅遍历下标的状况下,for 和 range 的性能简直是一样的。
  • items 的每一个元素的类型是一个构造体类型 ItemItem 由两个字段形成,一个类型是 int,一个是类型是 [4096]byte,也就是说每个 Item 实例须要申请约 4KB 的内存。
  • 在这个例子中,for 的性能大概是 range (同时遍历下标和值) 的 2000 倍。

[]*struct{}

func generateItems(n int) []*Item {    items := make([]*Item, 0, n)    for i := 0; i < n; i++ {        items = append(items, &Item{id: i})    }    return items}//for 循环指针func BenchmarkForPointer(b *testing.B) {    items := generateItems(1024)    for i := 0; i < b.N; i++ {        length := len(items)        var tmp int        for k := 0; k < length; k++ {            tmp = items[k].id        }        _ = tmp    }}//range 循环指针func BenchmarkRangePointer(b *testing.B) {    items := generateItems(1024)    for i := 0; i < b.N; i++ {        var tmp int        for _, item := range items {            tmp = item.id        }        _ = tmp    }}

执行后果:

goos: darwingoarch: amd64cpu: Intel(R) Core(TM) i5-1038NG7 CPU @ 2.00GHzBenchmarkForPointer-8             734905              1545 ns/opBenchmarkRangePointer-8           709748              1558 ns/op

切片元素从构造体 Item 替换为指针 *Item 后,for 和 range 的性能简直是一样的。而且应用指针还有另一个益处,能够间接批改指针对应的构造体的值。

所以说for 不肯定比range快。

实习生:那我总结一下吧。

胖虎:总结的不错,下次不要总结了。

实习生:那我走?

胖虎:哈哈哈哈,开玩笑的

参考文章:https://geektutu.com/post/hpg...