golang的for range原理以及引致的一些奇怪问题

基本用法

下述两个函数test1与test2运行结果有何区别?

func test1() {
	intArray := []int{1, 2, 3, 4}
	for _, val := range intArray {
		val++
	}
	// 仍然为[1, 2, 3, 4]
	fmt.Println(intArray)
}

func test2() {
	intArray := []int{1, 2, 3, 4}
	for i := 0; i < len(intArray); i++ {
		intArray[i]++
	}
	// 改变为[2, 3, 4, 5]
	fmt.Println(intArray)
}

原理是这样的,对于for i, val := range(intArray)来说,val是intArray[i]的副本,对val的改变不会导致intArray内元素的改变。

另一方面,i和val在循环内都是同一个变量,只在循环头声明一次,即两者的地址不变。这是另一个易错的地方。下述两个函数test3与test4运行结果有何区别?

package main

import "fmt"

func test3() {
    slice := []int{0, 1, 2, 3}
    myMap := make(map[int]*int)

    for index , value := range slice {
        myMap[index] = &value
    }
    // map[0]=3, map[1]=3, map[2]=3, map[3]=3
    prtMap(myMap)
}

func test4() {
    slice := []int{0, 1, 2, 3}
    myMap := make(map[int]*int)

    for index , value := range slice {
        // 每次进入循环体,声明一个新变量valueCopy,并把value赋值给它
        valueCopy := value
        myMap[index] = &valueCopy
    }
    // map[0]=0, map[1]=1, map[2]=2, map[3]=3
    prtMap(myMap)
}


func prtMap(myMap map[int]*int) {
    for key, value := range myMap {
        fmt.Printf("map[%v]=%v\n", key, *value)
    }
}

再举一个闭包的例子

package main
import (
    "fmt"
    "time"
)
func main()  {
    str := []string{"I","am","Jason"}
    for _,v := range str{
    	// 每个goroutine的v的地址相同,同时为外部v的地址
        go func() {
        	// 这里的v是引用外部变量v的地址
            fmt.Println(v)
        }()
    }
    time.Sleep(3 * time.Second)
}
/*
v的地址存储的值为Jason
输出结果:
Jason
Jason
Jason
*/

修改方法,利用函数的值传递性质:

package main
import (
    "fmt"
    "time"
)
func main()  {
    str := []string{"I","am","Jason"}
    for _,v := range str{
        // 把外部的v值拷贝给函数内部的v
        // 每个goroutine的v的地址不同,同时不同于外部v的地址
        go func(v string) {
            fmt.Println(v)
        }(v)
    }
    time.Sleep(3 * time.Second)
}
/*
输出结果: {"I", "am", "Jason"}的排列组合
*/

Slice

字符串

在 Go 中,字符串是以 UTF-8 为格式进行存储的,在字符串上调用 len 函数,取得的是字符串包含的 byte 的个数。

func test5() {
	str := "beijing,北京"
	// 14
	fmt.Println(len(str))
	// 10
	fmt.Println(utf8.RuneCountInString(str))
	// 10
	fmt.Println(len([]rune(str)))
	// 10
	fmt.Println(utf8.RuneCount([]byte(str)))
}

再看下面例子,在go语言中支持两种方式遍历字符串。
1.以字节数组的方式遍历。

str := "beijing,北京"
for i := 0; i < len(str); i++{
	fmt.Printf("%d: %v : %T\n", i, str[i], str[i])
}

输出结果为:

0: 98 : uint8
1: 101 : uint8
2: 105 : uint8
3: 106 : uint8
...
11: 228 : uint8
12: 186 : uint8
13: 172 : uint8

可以看出,这个字符串长度为14,尽管从直观上来说,这个字符串应该只有10个字符,这是因为每个中文字符在UTF-8中占3个字节,而不是1个字节。

2.以Unicode字符遍历

str := "beijing,北京"
for i, val := range str {
	fmt.Printf("%d: %v : %T\n", i, val, val)
}

输出结果为:

0: 98 : int32
1: 101 : int32
2: 105 : int32
3: 106 : int32
4: 105 : int32
5: 110 : int32
6: 103 : int32
7: 44 : int32
8: 21271 : int32
9: 20140 : int32

以Unicode字符方式遍历时,每个字符的类型是rune,而不是byte。rune类型在go语言中占用四个字节。

猜你喜欢

转载自blog.csdn.net/jason_cuijiahui/article/details/84671702