基本用法

下述两个函数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)

另一方面,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

for i, v := range x
package main

import (
    "fmt"
)

func main() {
    x := []int{1, 2, 3, 7, 16, 22, 17, 42}
    fmt.Println("We will start out with", x)
	
    for i, v := range x {
        fmt.Println("The current value is", v)
        x = append(x[:i], x[i+1:]...)
        fmt.Println("And after it is removed, we get", x)
    }
}

结果:

We will start out with [1 2 3 7 16 22 17 42] 
The current value is 1 // i = 0
And after it is removed, we get [2 3 7 16 22 17 42]
The current value is 3 // i = 1
And after it is removed, we get [2 7 16 22 17 42]
The current value is 16 // i = 2
And after it is removed, we get [2 7 22 17 42]
The current value is 17 // i = 3
And after it is removed, we get [2 7 22 42]
The current value is 42 // i = 4
panic: runtime error: slice bounds out of range

goroutine 1 [running]:
main.main()
	/tmp/sandbox217462357/main.go:13 +0x3a0

再看一个例子:

package main

import (
	"fmt"
)

type Guest struct {
    id      int
    name    string
    surname string
    friends []int
}

func (self Guest) removeFriend(id int) {
    for i, other := range self.friends {
        if other == id {
            self.friends = append(self.friends[:i], self.friends[i+1:]...)
            break
        }
    }
}

func main() {
	test := Guest{0, "jason", "jn", []int{1,2, 3, 4, 5}}
	fmt.Println(test)
	test.removeFriend(4)
	fmt.Println(test) 
}

结果:

{0 jason jn [1 2 3 4 5]}
// 为什么结果不是{0 jason jn [1, 2, 3, 5]},后面多了个5
{0 jason jn [1 2 3 5 5]}

注意,注意test调用removeFriend方法是通过值拷贝得到self,而self.friends的四元组和self.test的三元组内容是一样的{paddr, len, cap}(其中paddr指向[1, 2, 3, 4, 5]),但二者是不同的变量。

self.friends = append(self.friends[:i](paddr, 3, 5), self.friends[i+1:]...(5))

字符串

在 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语言中占用四个字节。