1. 指针 Go 拥有指针。指针保存了值的内存地址。 *类型 T 是指向 T 类型值的指针。其零值为 nil。
& 操作符会生成一个指向其操作数的指针。
* 操作符表示指针指向的底层值。
1 2 fmt.Println(*p) *p = 21
这也就是通常所说的“间接引用”或“重定向”。 与 C 不同,Go 没有指针运算。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package mainimport ( "fmt" ) func main () { var a *int fmt.Println(a) i,j := 10 ,20 p := &i fmt.Println(*p) *p = 11 fmt.Println(i) p = &j *p = *p/5 ; fmt.Println(j) } 输出结果: <nil > 10 11 4
2. 结构体 一个结构体(struct)就是一组字段(field)。
2.1. 结构体字段 结构体字段使用点号来访问。
2.2. 结构体指针 结构体字段可以通过结构体指针来访问。 如果我们有一个指向结构体的指针 p,那么可以通过 (*p).X 来访问其字段 X。不过这么写太啰嗦了,所以语言也允许我们使用隐式间接引用,直接写 p.X 就可以。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package mainimport "fmt" type Vertex struct { X int Y int } func main () { v := Vertex{3 ,4 } fmt.Println(v) fmt.Printf("X is %v; Y is %v\n" ,v.X,v.Y) v = Vertex{10 ,100 } p := &v; fmt.Printf("X is %v; Y is %v\n" ,(*p).X,(*p).Y) fmt.Printf("X is %v; Y is %v\n" ,p.X,p.Y) } 输出结果: {3 4 } X is 3 ; Y is 4 X is 10 ; Y is 100 X is 10 ; Y is 100
2.3. 结构体文法 结构体文法通过直接列出字段的值来新分配一个结构体。 使用 Name: 语法可以仅列出部分字段。(字段名的顺序无关。) 特殊的前缀 & 返回一个指向结构体的指针。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package mainimport "fmt" type Vertex struct { X int Y int } var ( v1 = Vertex{1 ,2 } v2 = Vertex{} v3 = Vertex{Y:4 ,X:3 } v4 = & Vertex{5 ,6 } v5 = & Vertex{Y:7 } ) func main () { fmt.Println(v1,v2,v3) fmt.Printf("V4 is X: %v , Y: %v\n" , (*v4).X,v4.Y) fmt.Printf("V5 is X: %v\n" , *v5) } 输出结果: {1 2 } {0 0 } {3 4 } V4 is X: 5 , Y: 6 V5 is X: {0 7 }
3. 数组(Java 数组) 3.1. 声明 类型 [n]T 表示拥有 n 个 T 类型的值的数组。
会将变量 a 声明为拥有 10 个整数的数组。 数组的长度是其类型的一部分,因此数组不能改变大小。这看起来是个限制,不过没关系,Go 提供了更加便利的方式来使用数组。
4. 切片(Java List) 4.1. 声明 每个数组的大小都是固定的。而切片则为数组元素提供动态大小的、灵活的视角。在实践中,切片比数组更常用。 类型 []T 表示一个元素类型为 T 的切片。
切片通过两个下标来界定,即一个上界和一个下界,二者以冒号分隔:
它会选择一个半开区间,包括第一个元素,但排除最后一个元素。 以下表达式创建了一个切片,它包含 a 中下标从 1 到 3 的元素:
4.2. 切片就像数组的引用 切片并不存储任何数据,它只是描述了底层数组中的一段。更改切片的元素会修改其底层数组中对应的元素。 与它共享底层数组的切片都会观测到这些修改。
4.3. 切片文法 切片文法类似于没有长度的数组文法。 这是一个数组文法:
1 [3 ]bool {true , true , false }
下面这样则会创建一个和上面相同的数组,然后构建一个引用了它的切片:
1 []bool {true , true , false }
4.4. 切片的默认行为 在进行切片时,你可以利用它的默认行为来忽略上下界。 切片下界的默认值为 0,上界则是该切片的长度。 对于数组
来说,以下切片是等价的:
1 2 3 4 a[0 :10 ] a[:10 ] a[0 :] a[:]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 package mainimport ( "fmt" ) func main () { var strArray [2 ]string ; strArray[0 ] = "World" strArray[1 ] = "Hello" fmt.Println(strArray) intArray := []int {1 , 2 , 3 , 4 , 5 , 6 }; fmt.Println(intArray) sliceArray := intArray[1 :4 ] fmt.Println(sliceArray) sliceArray[0 ] = 100 fmt.Println(sliceArray) fmt.Println(intArray) intArray[3 ] = 200 fmt.Println(sliceArray) fmt.Println(intArray) q := []int {1000 , 2000 , 3000 } r := []bool {true , false , false } s := []struct { name string age int }{ {"yuanmomo" , 10 }, {"yjj" , 9 }, } fmt.Println(q) fmt.Println(r) fmt.Println(s) fmt.Println(intArray[0 :len (intArray)]) fmt.Println(intArray[0 :]) fmt.Println(intArray[:len (intArray)]) fmt.Println(intArray[:]) } 输出结果: [World Hello] [1 2 3 4 5 6 ] [2 3 4 ] [100 3 4 ] [1 100 3 4 5 6 ] [100 3 200 ] [1 100 3 200 5 6 ] [1000 2000 3000 ] [true false false ] [{yuanmomo 10 } {yjj 9 }] [1 100 3 200 5 6 ] [1 100 3 200 5 6 ] [1 100 3 200 5 6 ] [1 100 3 200 5 6 ]
4.5. 切片的长度与容量 切片拥有 长度 和 容量 。
切片的长度就是它所包含的元素个数。 切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数。 切片 s 的长度和容量可通过表达式 len(s) 和 cap(s) 来获取。 你可以通过重新切片来扩展一个切片,给它提供足够的容量。试着修改示例程序中的切片操作,向外扩展它的容量,看看会发生什么。
注意: 这个地方很难理解: 首先,切片的应该理解为 a[low : high] low 可以看作为 左指针, high 可以看作右指针。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 package mainimport "fmt" func main () { intArray = []int {0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 } printSlices(intArray); intArray = intArray[:0 ] printSlices(intArray) intArray = intArray[:4 ] printSlices(intArray) intArray = intArray[3 :] printSlices(intArray) intArray = intArray[0 :6 ] printSlices(intArray) } func printSlices (s []int ) { fmt.Printf("len=%d cap=%d %v \n" , len (s), cap (s), s) } 输出结果: len =10 cap =10 [0 1 2 3 4 5 6 7 8 9 ] len =0 cap =10 [] len =4 cap =10 [0 1 2 3 ] len =1 cap =7 [3 ] len =6 cap =7 [3 4 5 6 7 8 ]
4.6. nil 切片 切片的零值是 nil。 nil 切片的长度和容量为 0 且没有底层数组。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport "fmt" func main () { var s []int fmt.Println(s, len (s), cap (s)) if s == nil { fmt.Println("nil!" ) } } 输出结果: [] 0 0 nil !
4.7. 用 make 创建切片 切片可以用内建函数 make 来创建,这也是你创建动态数组的方式。 make 函数会分配一个元素为零值的数组并返回一个引用了它的切片:
要指定它的容量,需向 make 传入第三个参数:
1 2 3 4 b := make ([]int , 0 , 5 ) b = b[:cap (b)] b = b[1 :]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package mainimport "fmt" func main () { a := fill(make ([]int , 5 )) printSlice("a" , a) b := fill(make ([]int , 6 , 7 )) printSlice("b" , b) c := b[:2 ] printSlice("c" , c) d := c[2 :7 ] printSlice("d" , d) } func fill (x []int ) []int { for i := 0 ; i < len (x); i++ { x[i] = i } return x } func printSlice (s string , x []int ) { fmt.Printf("%s len=%d cap=%d %v\n" , s, len (x), cap (x), x) } 输出结果: a len =5 cap =5 [0 1 2 3 4 ] b len =6 cap =7 [0 1 2 3 4 5 ] c len =2 cap =7 [0 1 ] d len =5 cap =5 [2 3 4 5 0 ]
4.8. 切片的切片 切片可包含任何类型,甚至包括其它的切片。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package mainimport ( "fmt" "strings" ) func main () { board := [][]string { []string {"_" , "_" , "_" }, []string {"_" , "_" , "_" }, []string {"_" , "_" , "_" }, } board[0 ][0 ] = "X" board[2 ][2 ] = "O" board[1 ][2 ] = "X" board[1 ][0 ] = "O" board[0 ][2 ] = "X" for i := 0 ; i < len (board); i++ { fmt.Printf("%s\n" ,strings.Join(board[i]," " )) } } 输出结果: X _ X O _ X _ _ O
4.9. 向切片追加元素 为切片追加新的元素是种常用的操作,为此 Go 提供了内建的 append 函数。内建函数的文档对此函数有详细的介绍。
1 func append (s []T, vs ...T) []T
append 的第一个参数 s 是一个元素类型为 T 的切片,其余类型为 T 的值将会追加到该切片的末尾。 append 的结果是一个包含原切片所有元素加上新添加元素的切片。 当 s 的底层数组太小,不足以容纳所有给定的值时,它就会分配一个更大的数组。返回的切片会指向这个新分配的数组。
(要了解关于切片的更多内容,请阅读文章 Go 切片:用法和本质 )。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package mainimport ( "fmt" ) func main () { var s []int printSlice(s) s = append (s, 0 ) printSlice(s) s = append (s, 1 ) printSlice(s) s = append (s, 2 , 3 , 4 ) printSlice(s) } func printSlice (x []int ) { fmt.Printf("len=%d, cap=%d, %v \n" , len (x), cap (x), x) } 输出结果: len =0 , cap =0 , [] len =1 , cap =1 , [0 ] len =2 , cap =2 , [0 1 ] len =5 , cap =6 , [0 1 2 3 4 ]
4.10. Range for 循环的 range 形式可遍历切片或映射。 当使用 for 循环遍历切片时,每次迭代都会返回两个值。第一个值为当前元素的下标,第二个值为该下标所对应元素的一份副本。
可以将下标或值赋予 _ 来忽略它。
1 2 for i, _ := range powfor _, value := range pow
若你只需要索引,忽略第二个变量即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package mainimport "fmt" var pow = []int {1 , 2 , 4 , 8 , 16 , 32 , 64 , 128 }func main () { for i, v := range pow { fmt.Printf("index=%d, value=%d \n" , i, v) } } 输出结果: index=0 , value=1 index=1 , value=2 index=2 , value=4 index=3 , value=8 index=4 , value=16 index=5 , value=32 index=6 , value=64 index=7 , value=128
4.11. 切片练习 实现 Pic。它应当返回一个长度为 dy 的切片,其中每个元素是一个长度为 dx,元素类型为 uint8 的切片。当你运行此程序时,它会将每个整数解释为灰度值(好吧,其实是蓝度值)并显示它所对应的图像。
图像的选择由你来定。几个有趣的函数包括 (x+y)/2, xy, x^y, x log(y) 和 x%(y+1)。
(提示:需要使用循环来分配 [][]uint8 中的每个 []uint8;请使用 uint8(intValue) 在类型之间转换;你可能会用到 math 包中的函数。)
分析:就是一个二维数组 uint8[x][y]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package mainimport "golang.org/x/tour/pic" func Pic (dx, dy int ) [][]uint8 { dySlice := make ([][]uint8 , dy) for y := range dySlice { dySlice[y] = make ([]uint8 , dx) for x := range dySlice[y] { dySlice[y][x] = uint8 ((x^y)); } } return dySlice; } func main () { pic.Show(Pic) }
输出结果:
5. 映射(Java Map) 5.1. make 初始化映射 映射将键映射到值。 映射的零值为 nil 。nil 映射既没有键,也不能添加键。 make 函数会返回给定类型的映射,并将其初始化备用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package mainimport "fmt" type Person struct { name string age int } var personMap map [string ]Personfunc main () { personMap = make (map [string ]Person) personMap["yuanmomo" ] = Person{ name: "momo" , age: 30 , } fmt.Println(personMap["yuanmomo" ]) fmt.Println(personMap["yuanmomo2" ]) } 输出结果: {momo 30 } { 0 }
5.2. 自定义初始化映射 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package mainimport "fmt" type Person struct { name string age int } var personMap = map [string ]Person{ "ywj" :{ age:18 , name:"yewenjuan" , }, "zs" :{ age:20 , name:"zhangsan" , }, "ww" : Person { age:21 , name:"wangwu" , }, } func main () { personMap["lisi" ] = Person{ name: "lisi" , age: 25 , } fmt.Println(personMap["ywj" ]) fmt.Println(personMap["lisi" ]) fmt.Println(personMap["zw" ]) } 输出结果: {yewenjuan 18 } {lis 25 } { 0 }
5.3. 修改映射 在映射 m 中插入或修改元素:
1 2 3 4 5 6 7 8 9 10 11 m[key] = elem elem = m[key] delete (m, key)elem, ok = m[key]
若 key 不在映射中,那么 elem 是该映射元素类型的零值。 同样的,当从映射中读取某个不存在的键时,结果是映射的元素类型的零值。注 :若 elem 或 ok 还未声明,你可以使用短变量声明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package mainimport "fmt" var mapTest = map [string ]int { "1" : 100 , "2" : 200 , } func main () { mapTest["3" ] = 300 fmt.Println(mapTest) fmt.Println(mapTest["2" ]) delete (mapTest,"2" ) fmt.Println(mapTest) value, exists := mapTest["1" ] fmt.Println("The value:" , value, "exists?" , exists) value, exists = mapTest["4" ] fmt.Println("The value:" , value, "exists?" , exists) } 输出结果: map [1 :100 2 :200 3 :300 ]200 map [1 :100 3 :300 ]The value: 100 exists? true The value: 0 exists? false
5.4. 映射练习 实现 WordCount。它应当返回一个映射,其中包含字符串 s 中每个“单词”的个数。函数 wc.Test 会对此函数执行一系列测试用例,并输出成功还是失败。
你会发现 strings.Fields 很有帮助。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 package mainimport ( "golang.org/x/tour/wc" "strings" ) func WordCount (s string ) map [string ]int { strArray := strings.Fields(s) count :=make (map [string ]int ); for _,str := range strArray { _,exists := count[str] if exists { count[str] ++ }else { count[str] = 1 ; } } return count } func main () { wc.Test(WordCount) } 输出结果: PASS f("I am learning Go!" ) = map [string ]int {"Go!" :1 , "I" :1 , "am" :1 , "learning" :1 } PASS f("The quick brown fox jumped over the lazy dog." ) = map [string ]int {"The" :1 , "brown" :1 , "dog." :1 , "fox" :1 , "jumped" :1 , "lazy" :1 , "over" :1 , "quick" :1 , "the" :1 } PASS f("I ate a donut. Then I ate another donut." ) = map [string ]int {"I" :2 , "Then" :1 , "a" :1 , "another" :1 , "ate" :2 , "donut." :2 } PASS f("A man a plan a canal panama." ) = map [string ]int {"A" :1 , "a" :2 , "canal" :1 , "man" :1 , "panama." :1 , "plan" :1 }
6. 函数值 函数也是值。它们可以像其它值一样传递。 函数值可以用作函数的参数或返回值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package mainimport ( "fmt" "math" ) func compute (fn func (float64 , float64 ) float64 ) float64 { return fn(2 , 3 ) } func main () { hypot := func (a, b float64 ) float64 { return math.Sqrt(a*a + b*b); } fmt.Println(hypot(3 , 4 )) fmt.Println(compute(hypot)) fmt.Println(compute(math.Pow)) } 输出结果: 5 3.605551275463989 8
7. 函数的闭包 Go 函数可以是一个闭包。闭包是一个函数值,它引用了其函数体之外的变量。该函数可以访问并赋予其引用的变量的值,换句话说,该函数被这些变量“绑定”在一起。 例如,函数 adder 返回一个闭包。每个闭包都被绑定在其各自的 sum 变量上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package mainimport "fmt" func addr () func (int ) int { sum := 0 return func (x int ) int { sum += x; return sum } } func main () { pos, neg := addr(), addr() for i := 0 ; i < 10 ; i++ { fmt.Println(pos(i), neg(-2 *i)) } } 输出结果: 0 0 1 -2 3 -6 6 -12 10 -20 15 -30 21 -42 28 -56 36 -72 45 -90
8. 练习:斐波纳契闭包 让我们用函数做些好玩的事情。
实现一个 fibonacci 函数,它返回一个函数(闭包),该闭包返回一个斐波纳契数列 (0, 1, 1, 2, 3, 5, ...)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 package mainimport ( "fmt" ) func fibonacci () func () int { last, current := 0 , 0 ; return func () int { if current == 0 { current = 1 return 0 } newCurrent := last + current last = current current = newCurrent return last } } func main () { f,b:= fibonacci(),fibonacci() for i := 0 ; i < 10 ; i++ { if i%2 == 0 { fmt.Printf("b -- %d\n" ,b()) } fmt.Printf("fffffff -- %d\n" ,f()) } } 输出结果: b -- 0 fffffff -- 0 fffffff -- 1 b -- 1 fffffff -- 1 fffffff -- 2 b -- 1 fffffff -- 3 fffffff -- 5 b -- 2 fffffff -- 8 fffffff -- 13 b -- 3 fffffff -- 21 fffffff -- 34