一 struct、slice和映射

1 指针

  Go 拥有指针。指针保存了值的内存地址。类型 *T 是指向 T 类型值的指针。其零值为 nil。

var p *int

  & 操作符会生成一个指向其操作数的指针。

i := 42
p = &i

  * 操作符表示指针指向的底层值。

fmt.Println(*p) // 通过指针 p 读取 i
*p = 21         // 通过指针 p 设置 i

  这也就是通常所说的“间接引用”或“重定向”。与 C 不同,Go 没有指针运算。

2 结构体

  一个结构体(struct)就是一组字段(field)。

package main

import "fmt"

type Vertex struct {
	X int
	Y int
}

func main() {
	fmt.Println(Vertex{1, 2})
}

  结构体字段使用点号来访问。也可以通过结构体指针来访问。如果我们有一个指向结构体的指针 p,那么可以通过 (*p).X 来访问其字段 X。不过这么写太啰嗦了,所以语言也允许我们使用隐式间接引用,直接写 p.X 就可以。

package main

import "fmt"

type Vertex struct {
	X int
	Y int
}

func main() {
	v := Vertex{1, 2}
	p := &v
	p.X = 1e9
    v.y=4
	fmt.Println(v)
}

输出结果:

{4 2}

  结构体语法通过直接列出字段的值来新分配一个结构体。使用 Name: 语法可以仅列出部分字段。(字段名的顺序无关。)特殊的前缀 & 返回一个指向结构体的指针。

package main

import "fmt"

type Vertex struct {
	X, Y int
}

var (
	v1 = Vertex{1, 2}  // 创建一个 Vertex 类型的结构体
	v2 = Vertex{X: 1}  // Y:0 被隐式地赋予
	v3 = Vertex{}      // X:0 Y:0
	p  = &Vertex{1, 2} // 创建一个 *Vertex 类型的结构体(指针)
)

func main() {
	fmt.Println(v1, p, v2, v3)
}

3 数组

  类型 [n]T 表示拥有 n 个 T 类型的值的数组。表达式

var a [10]int

  会将变量 a 声明为拥有 10 个整数的数组。数组的长度是其类型的一部分,因此数组不能改变大小。这看起来是个限制,不过没关系,Go 提供了更加便利的方式来使用数组。

4 切片

  每个数组的大小都是固定的。而切片则为数组元素提供动态大小的、灵活的视角。在实践中,切片比数组更常用。类型 []T 表示一个元素类型为 T 的切片。切片通过两个下标来界定,即一个上界和一个下界,二者以冒号分隔:

a[low : high]

  它会选择一个左闭右开区间,包括第一个元素,但排除最后一个元素。切片下界的默认值为 0,上界则是该切片的长度。
  下面这个表达式创建了一个切片,它包含 a 中下标从 1 到 3 的元素

a[1:4]

  切片并不存储任何数据,它只是描述了底层数组中的一段。更改切片的元素会修改其底层数组中对应的元素。与它共享底层数组的切片都会观测到这些修改。切片语法类似于没有长度的数组语法。
这是数组语法

[3]bool{true, true, false}

下面这样则会创建一个和上面相同的数组,然后构建一个引用了它的切片:

[]bool{true, true, false}

5 切片的长度与容量

  切片拥有 长度 和 容量。切片的长度就是它所包含的元素个数。切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数。切片 s 的长度和容量可通过表达式 len(s) 和 cap(s) 来获取。你可以通过重新切片来扩展一个切片,给它提供足够的容量。
  切片的零值是 nil。nil 切片的长度和容量为 0 且没有底层数组。
  Go中的切片原理较为复杂,但是记住一句话,一个切片是一个数组片段的描述。它包含了指向数组的指针,片段的长度, 和容量(片段的最大长度)。了解更多Go 切片:用法和本质
  切片可以用内建函数 make 来创建,这也是创建动态数组的方式,make 函数会分配一个元素为零值的数组并返回一个引用了它的切片:

a := make([]int, 5)  // len(a)=5

  要指定它的容量,需向 make 传入第三个参数:

b := make([]int, 0, 5) // len(b)=0, cap(b)=5

b = b[:cap(b)] // len(b)=5, cap(b)=5
b = b[1:]      // len(b)=4, cap(b)=4

  为切片追加新的元素是种常用的操作,为此 Go 提供了内建的 append 函数。内建函数的文档对此函数有详细的介绍。

func append(s []T, vs ...T) []T

  append 的第一个参数 s 是一个元素类型为 T 的切片,其余类型为 T 的值将会追加到该切片的末尾。append 的结果是一个包含原切片所有元素加上新添加元素的切片。当 s 的底层数组太小,不足以容纳所有给定的值时,它就会分配一个更大的数组。返回的切片会指向这个新分配的数组。

6 range

  for 循环的 range 形式可遍历切片或映射。当使用 for 循环遍历切片时,每次迭代都会返回两个值。第一个值为当前元素的下标,第二个值为该下标所对应元素的一份副本。

for i, v := range pow {
		fmt.Printf("2**%d = %d\n", i, v)
	}

  可以将下标或值赋予 _ 来忽略它。

for i, _ := range pow
for _, value := range pow

  若你只需要索引,忽略第二个变量即可。

for i := range pow

7 映射

  映射将键(key)映射到值(value)。映射的零值为 nil 。nil 映射既没有键,也不能添加键。make 函数会返回给定类型的映射,并将其初始化备用。

var m map[string]int

  map 的使用方法类似C++中的map,常用的语法如下:
在映射 m 中插入或修改元素:

m[key] = elem

获取元素:

elem = m[key]

删除元素:

delete(m, key)

通过双赋值检测某个键是否存在:

elem, ok = m[key]

  若 key 在 m 中,ok 为 true ;否则,ok 为 false。若 key 不在映射中,那么 elem 是该映射元素类型的零值。同样的,当从映射中读取某个不存在的键时,结果是映射的元素类型的零值。(注 :若 elem 或 ok 还未声明,你可以使用短变量声明:)

elem, ok := m[key]

8 函数值

  函数也是值。它们可以像其它值一样传递。(也可以将函数赋值给变量)函数值可以用作函数的参数或返回值。

package main

import (
	"fmt"
	"math"
)

func compute(fn func(float64, float64) float64) float64 {
	return fn(3, 4)
}

func main() {
	hypot := func(x, y float64) float64 {
		return math.Sqrt(x*x + y*y)
	}
	fmt.Println(hypot(5, 12))

	fmt.Println(compute(hypot))
	fmt.Println(compute(math.Pow))
}

输出结果:

13
5
81

9 函数的闭包

  Go 函数可以是一个闭包。闭包是一个函数值,它引用了其函数体之外的变量。该函数可以访问并赋予其引用的变量的值,换句话说,该函数被这些变量“绑定”在一起。例如,函数 adder 返回一个闭包。每个闭包都被绑定在其各自的 sum 变量上。

package main

import "fmt"

func adder() func(int) int {
	sum := 0
	return func(x int) int {
		sum += x
		return sum
	}
}

func main() {
	pos, neg := adder(), adder()
	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