1. 程式人生 > >golang的for range原理以及引致的一些奇怪問題

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語言中佔用四個位元組。