本文是学习 A Tour of Go (中文参考 Go 之旅中文 ) 整理的笔记,介绍Go 语言的指针,结构体,数组,切片,映射和闭包的基本概念和使用。
1. 指针
$GOPATH/src/go_note/gotour/advancetype/pointer/pointer.go
源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
/** * go 语言指针 */ package main import ( "fmt" ) func main() { i, j := 42, 36 p := &i fmt.Println(*p) // 42 *p = 21 fmt.Println(i) // 21 p = &j *p = *p / 4 fmt.Println(j) // 9 } |
Go 具有指针。 指针保存了变量的内存地址。类型 *T
是指向 T
类型值的指针,其零值为 nil 。
1 2 |
var p *int |
&
操作符会生成一个指向其操作数的指针。
1 2 3 |
i := 42 p = &i |
*
操作符表示指针指向的底层值。
1 2 3 |
fmt.Println(*p) // 通过指针 p 读取 i *p = 21 // 通过指针 p 设置 i |
2. 结构体
$GOPATH/src/go_note/gotour/advancetype/struct/struct.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 main import ( "fmt" ) type Point struct { X int Y int } var ( p1 = Point{1, 2} p2 = Point{X: 1} p3 = Point{} pt1 = &Point{1, 2} ) func main() { fmt.Println(Point{1, 2}) p := Point{3, 4} p.X = 4 // 结构体字段使用点号来访问 fmt.Println(p, p.Y) pt := &p pt.X = 5 // 使用隐式间接引用,直接写 `pt.X` (*pt).Y = 6 // 通过 `(*pt).Y` 来访问其字段 `Y` fmt.Println(p) } |
一个结构体( struct )就是一个字段的集合,结构体字段使用点号来访问。
2.1 结构体指针
结构体字段可以通过结构体指针来访问。
1 2 3 4 |
pt := &p pt.X = 5 (*pt).Y = 6 |
如果我们有一个指向结构体的指针 pt
,那么可以通过 (*pt).X
来访问其字段 X
,也可以使用隐式间接引用,直接写 pt.X
。
2.2 结构体语法
结构体文法通过直接列出字段的值来新分配一个结构体。
使用 Name:
语法可以仅列出部分字段。(字段名的顺序无关。)
特殊的前缀 &
返回一个指向结构体的指针。
1 2 3 4 5 6 7 |
var ( p1 = Point{1, 2} p2 = Point{X: 1} p3 = Point{} pt1 = &Point{1, 2} ) |
3. 数组
$GOPATH/src/go_note/gotour/advancetype/array/array.go
源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
/** * go 语言数组 */ package main import ( "fmt" ) func main() { var a [2]string a[0] = "hello" a[1] = "world" fmt.Println(a, a[0]) list := [10]int{1, 2, 3, 4} fmt.Println(list) } |
类型 [n]T
表示拥有 n
个 T
类型的值的数组。
表达式
1 2 |
var a [10]int // 变量 a 声明为拥有 10 个整数的数组 |
数组的长度是其类型的一部分,因此数组不能改变大小
4. 切片
$GOPATH/src/go_note/gotour/advancetype/slice/slice.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 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 61 62 63 64 65 |
/** * go 语言 slice */ package main import ( "fmt" "strings" ) func main() { primes := [6]int{1, 2, 3, 4, 5, 6} var s []int = primes[1:4] fmt.Println(s) // [2 3 4] names := [4]string{"john", "paul", "george", "ringo"} fmt.Println(names) // [john paul george ringo] a := names[0:2] b := names[1:3] fmt.Println(a, b) // [john paul] [paul george] b[0] = "xxx" // 更改切片的元素会修改其底层数组中对应的元素,并且与它共享底层数组的切片都会观测到这些修改 fmt.Println(a, b, names) // [john xxx] [xxx george] [john xxx george ringo] // 默认分片上下界 i := []int{1, 2, 3, 4} fmt.Println(i[0:4]) // [1 2 3 4] fmt.Println(i[:4]) // [1 2 3 4] fmt.Println(i[0:]) // [1 2 3 4] fmt.Println(i[:]) // [1 2 3 4] // 长度与容量 fmt.Printf("len=%d, cap=%d, %v\n", len(i[:4]), cap(i[:4]), i[:4]) // len=4, cap=4, [1 2 3 4] fmt.Printf("len=%d, cap=%d, %v\n", len(i[2:4]), cap(i[2:4]), i[2:4]) // len=2, cap=2, [3 4] // nil 切片 var j []int fmt.Printf("len=%d, cap=%d, %v\n", len(j), cap(j), j) // len=0, cap=0, [] // make 创建切片 a_m := make([]int, 5) b_m := make([]int, 0, 5) fmt.Printf("len=%d, cap=%d, %v\n", len(a_m), cap(a_m), a_m) // len=5, cap=5, [0 0 0 0 0] fmt.Printf("len=%d, cap=%d, %v\n", len(b_m), cap(b_m), b_m) // len=0, cap=5, [] // 包含切片的切片 board := [][]string{ []string{"_", "_", "_"}, []string{"_", "_", "_"}, []string{"_", "_", "_"}, } for i := 0; i < len(board); i++ { fmt.Printf("%s\n", strings.Join(board[i], " ")) } // 向切片追加元素 var s_p []int fmt.Printf("len=%d, cap=%d, %v\n", len(s_p), cap(s_p), s_p) // len=0, cap=5, [] s_p = append(s_p, 1) s_p = append(s_p, 2) fmt.Printf("len=%d, cap=%d, %v\n", len(s_p), cap(s_p), s_p) // len=0, cap=5, [] } |
每个数组的大小都是固定的,而切片则提供动态数组,类型 []T
表示一个元素类型为 T
的切片。切片类似不限定长度的数组。
4.1 切片是数组的引用
切片并不存储任何数据,它只是描述了底层数组中的一段。更改切片的元素会修改其底层数组中对应的元素,并且与它共享底层数组的切片都会观测到这些修改。
如下创建一个切片时,会先创建数组,然后构建一个引用了它的切片
1 2 |
[]bool{true, true, false} |
4.2 切片的默认行为
在进行切片时,切片有默认上下界。切片下界的默认值为 0 ,上界则是该切片的长度。
对于数组
1 2 |
var a [10]int |
来说,以下切片是等价的:
1 2 3 4 5 |
a[0:10] a[:10] a[0:] a[:] |
4.3 切片的长度与容量
切片拥有 长度
和 容量
- 长度就是它所包含的元素个数。
- 容量是从它的第一个元素开始数,到其底层数组元素末尾的个数。
切片 s
的长度和容量可通过表达式 len(s)
和 cap(s)
来获取。
切片的零值是 nil
, nil
切片的长度和容量为 0 且没有底层数组。
4.4 make 创建切片
切片可以用内建函数 make
来创建,这也是你创建动态数组的方式。make
函数会分配一个元素为零值的数组并返回一个引用了它的切片:
1 2 |
a := make([]int, 5) // len(a)=5 |
要指定它的容量,需向 make 传入第三个参数:
1 2 |
b := make([]int, 0, 5) // len(b)=0, cap(b)=5 |
4.5 切片的切片
切片可包含任何类型,甚至包括其它的切片
1 2 3 4 5 6 |
board := [][]string{ []string{"_", "_", "_"}, []string{"_", "_", "_"}, []string{"_", "_", "_"}, } |
4.6 append 向切片追加元素
为切片追加新的元素是种常用的操作,为此 Go 提供了内建的 append
函数
1 2 |
func append(s []T, vs ...T) []T |
append
的第一个参数 s
是一个元素类型为 T
的切片, 其余类型为 T
的值将会追加到该切片的末尾。append
的结果是一个包含原切片所有元素加上新添加元素的切片。当 s
的底层数组太小,不足以容纳所有给定的值时,它就会分配一个更大的数组。 返回的切片会指向这个新分配的数组。
5. range
$GOPATH/src/go_note/gotour/advancetype/range/range.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 |
/** * go 语言 range用法 */ package main import "fmt" var pow = []int{1, 2, 4, 8, 16, 32} func main() { for i, v := range pow { fmt.Printf("2**%d = %d\n", i, v) } // 2**0 = 1 // 2**1 = 2 // 2**2 = 4 // 2**3 = 8 // 2**4 = 16 // 2**5 = 32 s := make([]int, 5) for i := range s { s[i] = 1 << uint(i) // == 2**i } for _, v := range s { fmt.Printf("%d\n", v) } } |
for
循环的 range
形式可遍历切片或映射。
当使用 for
循环遍历切片时,每次迭代都会返回两个值。 第一个值为当前元素的下标,第二个值为该下标所对应元素的一份副本。
可以将下标或值赋予 _
来忽略它, 若你只需要索引,去掉 , value
的部分即可。
6. 映射
$GOPATH/src/go_note/gotour/advancetype/map/map.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 32 33 34 35 36 37 38 39 40 41 42 43 44 |
/** * go 语言映射 */ package main import ( "fmt" ) type Vertex struct { Lit, Log float64 } var m map[string]Vertex var m1 = map[string]Vertex{ // 定义并初始化一个映射 "Bell Labs": Vertex{ 40.123, -74.123, }, "Google": Vertex{ 37.123, -122.123, }, } var m2 = map[string]Vertex{ // 当顶级域名只是一个类型名时,可以在定义语句中省略它 "Bell Labs": {40.123, -74.123}, "Google": {37.123, -74.123}, } func main() { m = make(map[string]Vertex) // make 返回给定类型的映射,并将其初始化备用 m["Bell Labs"] = Vertex{40.123, -74.123} fmt.Println(m["Bell Labs"], m2) user := make(map[string]int) user["Age"] = 42 fmt.Println(user["Age"]) delete(user, "Age") fmt.Println(user["Age"]) v, ok := user["Age"] fmt.Println("The Age:", v, "Presen?", ok) } |
映射将键映射到值, 映射的零值为 nil
, nil
映射既没有键,也不能添加键。make
函数会返回给定类型的映射,并将其初始化备用。
6.1 修改映射
在映射 m
中插入或修改元素:
1 2 |
m[key] = elem |
获取元素:
1 2 |
elem = m[key] |
删除元素:
1 2 |
delete(m, key) |
通过双赋值检测某个键是否存在:
1 2 |
elem, ok = m[key] |
若 key
在 m
中, ok
为 true
;否则, ok
为 false
若 key
不在映射中,那么 elem
是该映射元素类型的零值。同样的,当从映射中读取某个不存在的键时,结果是映射的元素类型的零值。
7. 闭包
$GOPATH/src/go_note/gotour/advancetype/closure/closure.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 32 33 34 35 36 37 38 39 40 |
/** * go 语言闭包 */ package main import ( "fmt" "math" ) func compute(fn func(float64, float64) float64) float64{ return fn(3, 4) } // adder 返回一个闭包, 每个闭包都被绑定在其各自的 sum 变量上。 func adder() func (int) int { sum := 0 return func (x int) int { sum += x return sum } } func main() { hypot := func (x, y float64) float64 { return math.Sqrt(x*x + y*y) } fmt.Println(hypot(5, 12)) // 13 fmt.Println(compute(hypot)) // 5 fmt.Println(compute(math.Pow)) // 81 pos, neg := adder(), adder() fmt.Printf("%v\n", pos) for i := 0; i < 10; i++ { fmt.Println(i, pos(i), -2*i, neg(-2*i)) } } |
函数也是值,它们可以像其它值一样传递,函数值可以用作函数的参数或返回值。
Go 函数可以是一个闭包。闭包是一个函数值,它引用了其函数体之外的变量。该函数可以访问并赋值其引用的变量的值,也即该函数被“绑定”在了这些变量上。
参考
有疑问加站长微信联系(非本文作者)