话说胖虎上次没有问到实习生,觉得实习生底子不错,最近闲来无事,决定在考考实习生。
胖虎:以下代码输出什么
- package main
-
- import "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 main
-
- import "fmt"
-
- func main() {
- s := []int{0, 1}
- for _, value := range s {
- s = append(s, value)
- }
- fmt.Printf("s的值是:%+v\n", s)
- }
-
胖虎:那你知道为什么吗?
实习生:范围循环在迭代过程中,难道是迭代值的拷贝?我再试下吧。
- package main
-
- import "fmt"
-
- func main() {
- s := []int{0, 1}
- for _, value := range s {
- value += 10
- }
- fmt.Printf("s的值是:%+v\n", s)
- }
-
实习生:跟我猜想的一样。
胖虎点点头:范围遍历在开始遍历数据之前,会先拷贝一份被遍历的数据,所以在遍历过程中去修改被遍历的数据,只是修改拷贝的数据,不会影响到原数据。
而普通for循环,会一直不断追加数据到切片,对原数据产生影响。
胖虎:顺便复习一下这两个的使用语法吧
- s := "test"
- // 常见的 for 循环,支持初始化语句。
- for i, n := 0, len(s); i < n; i++ {
- println(string(s[i]))
- }
-
- initNum := 0
- intSlice := []int{0, 1}
- sliceLen := len(intSlice)
- for initNum < sliceLen {
- initNum++
- fmt.Println(intSlice[initNum])
- }
-
- for { // 替代 while (true) {}
- println(s) // 替代 for (;;) {}
- }
-
-
由此可见for常见的使用场景是字符串,数组,切片和无限循环。但需要注意的是 for 循环字符串的时候,结果想要为字符串的时候,需要string转换一下,而有的编程语言不需要,比如说世界上最好的语言。
当然使用 range 遍历字符串就没有这个问题。
- words := []string{"Go", "Java", "C++"}
- for i, value := range words {
- words = append(words, "test")
- fmt.Println(i, value)
- }
-
- test := "abc"
- // 忽略 value
- for i := range s {
- fmt.Println(s[i])
- }
-
Go 语言中,range 使用场景除了数组(array)、切片(slice),还可以很方便字典(map)和信道(chan)
- 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:2
- key:three, value:3
- key:one, value:1
-
需要注意的是:
- ch := make(chan string)
- go func() {
- ch <- "听我说"
- ch <- "谢谢你"
- ch <- "因为有你"
- ch <- "温暖了四季"
- close(ch)
- }()
- for n := range ch {
- fmt.Println(n)
- }
-
结果如下:
- 听我说
- 谢谢你
- 因为有你
- 温暖了四季
-
- package main
-
- import (
- "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: darwin
- goarch: amd64
- cpu: Intel(R) Core(TM) i5-1038NG7 CPU @ 2.00GHz
- BenchmarkForIntSlice-8 3552 334038 ns/op
- BenchmarkRangeIntSlice-8 3544 321965 ns/op
-
名词解释:
BenchmarkForIntSlice-8,即 GOMAXPROCS,默认等于 CPU 核数。
3552 代表运行了多少次
334038 ns/op 每次执行平均时间,
由此可见 ,遍历 []int 类型的切片,for 与 range 性能两者几乎没有区别。
如果是稍微复杂一点的[]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: darwin
- goarch: amd64
- cpu: Intel(R) Core(TM) i5-1038NG7 CPU @ 2.00GHz
- BenchmarkForStruct-8 3373856 359.7 ns/op
- BenchmarkRangeIndexStruct-8 3587757 329.0 ns/op
- BenchmarkRangeStruct-8 3478 288161 ns/op
-
- 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: darwin
- goarch: amd64
- cpu: Intel(R) Core(TM) i5-1038NG7 CPU @ 2.00GHz
- BenchmarkForPointer-8 734905 1545 ns/op
- BenchmarkRangePointer-8 709748 1558 ns/op
-
切片元素从结构体Item替换为指针*Item后,for 和 range 的性能几乎是一样的。而且使用指针还有另一个好处,可以直接修改指针对应的结构体的值。
所以说for 不一定比range快。
实习生:那我总结一下吧。
胖虎:总结的不错,下次不要总结了。
实习生:那我走?
胖虎:哈哈哈哈,开玩笑的