指针
Go 拥有指针。指针保存了值的内存地址。
类型 *T 是指向 T 类型值的指针,其零值为 nil。
&
操作符会生成一个指向其操作数的指针。
*
操作符表示指针指向的底层值。
1 2
| fmt.Println(*p) *p = 21
|
这也就是通常所说的「解引用」或「间接引用」。
与 C 不同,Go 没有指针运算。
pointers.go1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package main
import "fmt"
func main() { var p *int i := 42
p = &i
fmt.Println(p) fmt.Println(i) fmt.Println(*p)
}
|
结构体
一个 结构体(struct)就是一组 字段(field)。
structs.go1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package main
import "fmt"
type Vertex struct { X int Y int }
func main() { v := Vertex{1, 2} v.X = 4 fmt.Println(v) }
|
结构体字段
结构体字段可通过点号 .
来访问。
struct-fields.go1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package main
import "fmt"
type Vertex struct { X int Y int }
func main() { v := Vertex{1, 2} v.X = 4 fmt.Println(v.X) }
|
结构体指针
结构体字段可通过结构体指针来访问。
如果我们有一个指向结构体的指针 p 那么可以通过 (*p).X
来访问其字段 X。 不过这么写太啰嗦了,所以语言也允许我们使用隐式解引用,直接写 p.X 就可以。
struct-pointers.go1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| package main
import "fmt"
type Vertex struct { X int Y int }
func main() { v := Vertex{1, 2} p := &v p.X = 1e9 fmt.Println(v) }
|
结构体字面量
使用 Name:
语法可以仅列出部分字段(字段名的顺序无关)。
特殊的前缀 &
返回一个指向结构体的指针。
struct-literals.go1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package main
import "fmt"
type Vertex struct { X, Y int }
var ( v1 = Vertex{1, 2} v2 = Vertex{X: 1} v3 = Vertex{} p = &Vertex{1, 2} )
func main() { fmt.Println(v1, p, v2, v3) }
|
数组
类型 [n]T
表示一个数组,它拥有 n 个类型为 T
的值。
表达式
会将变量 a 声明为拥有 10 个整数的数组。
数组的长度是其类型的一部分,因此数组不能改变大小。 这看起来是个限制,不过没关系,Go 拥有更加方便的使用数组的方式。
切片
每个数组的大小都是固定的。而切片则为数组元素提供了动态大小的、灵活的视角。 在实践中,切片比数组更常用。
类型 []T
表示一个元素类型为 T
的切片。.
切片通过两个下标来界定,一个下界和一个上界,二者以冒号分隔:
它会选出一个半闭半开区间,包括第一个元素,但排除最后一个元素。
以下表达式创建了一个切片,它包含 a 中下标从 1 到 3 的元素:
slices.go1 2 3 4 5 6 7 8 9 10
| package main
import "fmt"
func main() { primes := [6]int{2, 3, 5, 7, 11, 13} var s []int = primes[1:4] fmt.Println(s) }
|
切片就像数组的引用
切片并不存储任何数据,它只是描述了底层数组中的一段。 更改切片的元素会修改其底层数组中对应的元素。 和它共享底层数组的切片都会观测到这些修改。
slices-pointers.go1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package main
import "fmt"
func main() { names := [4]string{ "John", "Paul", "George", "Ringo", } fmt.Println(names)
a := names[0:2] b := names[1:3] fmt.Println(a, b)
b[0] = "XXX" fmt.Println(a, b) fmt.Println(names) }
|
输出结果1 2 3 4
| [John Paul George Ringo] [John Paul] [Paul George] [John XXX] [XXX George] [John XXX George Ringo]
|
切片字面量
切片字面量类似于没有长度的数组字面量。
这是一个数组字面量:
1
| [3]bool{true, true, false}
|
下面这样则会创建一个和上面相同的数组,然后再构建一个引用了它的切片:
1
| []bool{true, true, false}
|
运行以下示例查看结果。
slices-literals.go1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package main
import "fmt"
func main() { q := []int{2, 3, 5, 7, 11, 13} fmt.Println(q)
r := []bool{true, false, true, true, false, true} fmt.Println(r)
s := []struct { i int b bool }{ {2, true}, {3, false}, {5, true}, {7, true}, {11, false}, {13, true}, } fmt.Println(s) }
|
切片的默认行为
在进行切片时,你可以利用它的默认行为来忽略上下界。
切片下界的默认值为 0,上界则是该切片的长度。
对于数组
来说,以下切片表达式和它是等价的:
1 2 3 4
| a[0:10] a[:10] a[0:] a[:]
|
运行以下示例查看输出结果。
slices-bounds.go1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package main
import "fmt"
func main() { s := []int{2, 3, 5, 7, 11, 13}
s = s[1:4] fmt.Println(s)
s = s[:2] fmt.Println(s)
s = s[1:] fmt.Println(s) }
|
切片的长度和容量
切片拥有 长度 和 容量。
切片的长度就是它所包含的元素个数。
切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数。
切片 s 的长度和容量可通过表达式 len(s)
和 cap(s)
来获取。
你可以通过重新切片来扩展一个切片,给它提供足够的容量。 试着修改示例程序中的切片操作,向外扩展它的长度,看看会发生什么。
slices-len-cap.go1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package main
import "fmt"
func main() { s := []int{2, 3, 5, 7, 11, 13} printSlice(s)
s = s[:0] printSlice(s)
s = s[:4] printSlice(s)
s = s[2:] printSlice(s) }
func printSlice(s []int) { fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s) }
|
nil 切片
切片的零值是 nil
。
nil
切片的长度和容量为 0 且没有底层数组。
nil-slices.go1 2 3 4 5 6 7 8 9 10 11
| package main
import "fmt"
func main() { var s []int fmt.Println(s, len(s), cap(s)) if s == nil { fmt.Println("nil!") } }
|
用make创建切片
切片可以用内置函数 make 来创建,这也是你创建动态数组的方式。
make 函数会分配一个元素为零值的数组并返回一个引用了它的切片:
要指定它的容量,需向 make 传入第三个参数:
1 2 3 4
| b := make([]int, 0, 5)
b = b[:cap(b)] b = b[1:]
|
运行以下示例查看输出结果。
making-slices.go1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package main
import "fmt"
func main() { a := make([]int, 5) printSlice("a", a)
b := make([]int, 0, 5) printSlice("b", b)
c := b[:2] printSlice("c", c)
d := c[2:5] printSlice("d", d) }
func printSlice(s string, x []int) { fmt.Printf("%s len=%d cap=%d %v\n", s, len(x), cap(x), x) }
|
切片的切片
切片可以包含任何类型,当然也包括其他切片。
slices-of-slices.go1 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 main
import ( "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], " ")) } }
|
向切片追加元素
为切片追加新的元素是种常见的操作,为此 Go 提供了内置的 append
函数。内置函数的文档对该函数有详细的介绍。
1
| func append(s []T, vs ...T) []T
|
append
的第一个参数 s 是一个元素类型为 T 的切片,其余类型为 T 的值将会追加到该切片的末尾。
append
的结果是一个包含原切片所有元素加上新添加元素的切片。
当 s 的底层数组太小,不足以容纳所有给定的值时,它就会分配一个更大的数组。 返回的切片会指向这个新分配的数组。
append.go1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package main
import "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(s []int) { fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s) }
|
range
for 循环的 range 形式可遍历切片或映射。
当使用 for 循环遍历切片时,每次迭代都会返回两个值。 第一个值为当前元素的下标,第二个值为该下标所对应元素的一份副本。
可以将下标或值赋予 _ 来忽略它。
1 2
| for i, _ := range pow for _, value := range pow
|
若你只需要索引,忽略第二个变量即可。
range.go1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package main
import "fmt"
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
func main() { for i, v := range pow { fmt.Printf("2**%d = %d\n", i, v) } for i := range pow { fmt.Printf("%d\n", i) }
for _, v := range pow { fmt.Printf("%d\n", v) } }
|
映射
map 映射将键映射到值。
映射的零值为 nil
。nil
映射既没有键,也不能添加键。
make 函数会返回给定类型的映射,并将其初始化备用。
maps.go1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package main
import "fmt"
type Vertex struct { Lat, Long float64 }
var m map[string]Vertex
func main() { m = make(map[string]Vertex) m["Bell Labs"] = Vertex{ 40.68433, -74.39967, } fmt.Println(m["Bell Labs"]) }
|
映射字面量
映射的字面量和结构体类似,只不过必须有键名。
map-literals.go1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| package main
import "fmt"
type Vertex struct { Lat, Long float64 }
var m = map[string]Vertex{ "Bell Labs": Vertex{ 40.68433, -74.39967, }, "Google": Vertex{ 37.42202, -122.08408, }, }
func main() { fmt.Println(m) }
|
若顶层类型只是一个类型名,那么你可以在字面量的元素中省略它。
map-literals-continue.go1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package main
import "fmt"
type Vertex struct { Lat, Long float64 }
var m = map[string]Vertex{ "Bell Labs": {40.68433, -74.39967}, "Google": {37.42202, -122.08408}, }
func main() { fmt.Println(m) }
|
修改映射
在映射 m 中插入或修改元素:
获取元素:
删除元素:
通过双赋值检测某个键是否存在:
若 key 在 m 中,ok 为 true ;否则,ok 为 false。
若 key 不在映射中,则 elem 是该映射元素类型的零值。
注:若 elem 或 ok 还未声明,你可以使用短变量声明:
mutating-maps.go1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package main
import "fmt"
func main() { m := make(map[string]int)
m["答案"] = 42 fmt.Println("值:", m["答案"])
m["答案"] = 48 fmt.Println("值:", m["答案"])
delete(m, "答案") fmt.Println("值:", m["答案"])
v, ok := m["答案"] fmt.Println("值:", v, "是否存在?", ok) }
|
函数值
函数也是值。它们可以像其他值一样传递。
函数值可以用作函数的参数或返回值。
function-values.go1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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)) }
|
函数闭包
Go 函数可以是一个闭包。闭包是一个函数值,它引用了其函数体之外的变量。 该函数可以访问并赋予其引用的变量值,换句话说,该函数被“绑定”到了这些变量。
例如,函数 adder 返回一个闭包。每个闭包都被绑定在其各自的 sum 变量上。
function-closures.go1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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), ) } }
|