什么是指针
- 指针是指向另一个变量地址的变量。
- Go 语言的指针同时也强调安全性,不会出现迷途指针(dangling pointers)

& 和 * 符号
- 变量会将它们的值存储在计算机的 RAM 里,存储位置就是该变量的内存地址。
- & 表示地址操作符,通过 & 可以获得变量的内存地址。
- & 操作符无法获得字符串/数值/布尔字面值的地址。
- 操作符与 & 的作用相反,它用来解引用,提供内存地址指向的值。

- C 语言中的内存地址可以通过例如 address++ 这样的指针运算进行操作,但是在 Go 里面不允许这种不安全操作。
package mainimport "fmt"func main() { answer := 42 fmt.Println(&answer) address := &answer fmt.Println(*address)}
指针类型
package mainimport "fmt"func main() { answer := 42 address := &answer fmt.Printf("address is a %T\n", address)}
- 指针类型和其它普通类型一样,出现在所有需要用到类型的地方,如变量声明、函数形参、返回值类型、结构体字段等。
package mainimport "fmt"func main() { canada := "Canada" var home *string fmt.Printf("home is a %T\n", home) home = &canada fmt.Println(*home)}
- 将 * 放在类型前面表示声明指针类型
- 将 * 放在变量前面表示解引用操作
指针就是用来指向的
package mainimport "fmt"func main() { var administrator *string scolese := "Christopher J. Scolese" administrator = &scolese fmt.Println(*administrator) bolden := "Charles F. Bolden" administrator = &bolden fmt.Println(*administrator) bolden = "Charles Frank Bolden Jr." fmt.Println(*administrator) *administrator = "Maj. Gen. Charles Frank Bolden Jr." fmt.Println(bolden) major := administrator *major = "Major General Charles Frank Bolden Jr." fmt.Println(bolden) fmt.Println(administrator == major) lightfoot := "Robert M. Lightfoot Jr." administrator = &lightfoot fmt.Println(administrator == major) charles := *major *major = "Charles Bolden" fmt.Println(charles) fmt.Println(bolden) charles = "Charles Bolden" fmt.Println(charles == bolden) fmt.Println(&charles == &bolden)}
- 两个指针变量持有相同的内存地址,那么它们就是相等。

指向结构的指针
- 与字符串和数值不一样,复合字面量的前面可以放置 &。
- 访问字段时,对结构体进行解引用并不是必须的。
package mainimport "fmt"func main() { type person struct { name, superpower string age int } timmy := &person{ name: "Timothy", age: 10, } timmy.superpower = "flying" fmt.Printf("%+v\n", timmy)}
指向数组的指针
- 和结构体一样,可以把 & 放在数组的复合字面值前面来创建指向数组的指针。
package mainimport "fmt"func main() { superpowers := &[3]string{"flight", "invisibility", "super strength"} fmt.Println(superpowers[0]) fmt.Println(superpowers[1:2])}
- 数组在执行索引或切片操作时会自动解引用。没有必要写 (*superpower)[0] 这种形式。
- 与 C 语言不一样,Go 里面数组和指针式两种完全独立的类型。
- slice 和 map 的复合字面值前面也可以放置 & 操作符,但是 Go 并没有为它们提供自动解引用的功能。
实现修改
- Go 语言的函数和方法都是按值传递参数的,这意味着函数总是操作于被传递参数的副本。
- 当指针被传递到函数时,函数将接收传入的内存地址的副本。之后函数可以通过解引用内存地址来修改指针指向的值。
package mainimport "fmt"type person struct { name, superpower string age int}func birthday(p *person) { p.age++}func main() { rebecca := person{ name: "Rebecca", superpower: "imagination", age: 14, } birthday(&rebecca) fmt.Printf("%+v\n", rebecca)}
指针接收者
- 方法的接收者和方法的参数在处理指针方面是很相似的。
package mainimport "fmt"type person struct { name string age int}func (p *person) birthday() { p.age++}func main() { terry := &person{ name: "Terry", age: 15, } terry.birthday() fmt.Printf("%+v\n", terry) nathan := person{ name: "Nathan", age: 17, } nathan.birthday() fmt.Printf("%+v\n", nathan)}
- Go 语言在变量通过点标记法进行调用的时候,自动使用 & 取得变量的内存地址。
- 所以不用写 (&nathan).birthday() 这种形式也可以正常运行。
package mainimport ( "fmt" "time")func main() { const layout = "Mon, Jan 2, 2006" day := time.Now() tomorrow := day.Add(24 * time.Hour) fmt.Println(day.Format(layout)) fmt.Println(tomorrow.Format(layout))}
- 使用指针作为接收者的策略应该始终如一:
- 如果一种类型的某些方法需要用到指针作为接收者,就应该为这种类型的所有方法都是用指针作为接收者。
内部指针
- Go 语言提供了 内部指针 这种特性。
- 它用于确定结构体中指定字段的内存地址。
- & 操作符不仅可以获得结构体的内存地址,还可以获得结构体中指定字段的内存地址。
package mainimport "fmt"type stats struct { level int endurance, health int}func levelUp(s *stats) { s.level++ s.endurance = 42 + (14 * s.level) s.health = 5 * s.endurance}func main() { type character struct { name string stats stats } player := character{name: "Matthias"} levelUp(&player.stats) fmt.Printf("%+v\n", player.stats)}
修改数组
package mainimport "fmt"func reset(board *[8][8]rune) { board[0][0] = 'r' // ...}func main() { var board [8][8]rune reset(&board) fmt.Printf("%c", board[0][0])}
隐式的指针
- Go 语言里一些内置的集合类型就在暗中使用指针。
- map 在被赋值或者被作为参数传递的时候不会被复制。
- map 就是一种隐式指针。
- 这种写法就是多此一举:func demolish(planets *map[string]string)
- map 的键和值都可以是指针类型
- 需要将指针指向 map 的情况并不多见
slice 指向数组
- 之前说过 slice 是指向数组的窗口,实际上 slice 在指向数组元素的时候也使用了指针。
- 每个 slice 内部都会被表示为一个包含 3 个元素的结构,它们分别指向:
- 当 slice 被直接传递至函数或方法时,slice 的内部指针就可以对底层数据进行修改。
- 指向 slice 的显式指针的唯一作用就是修改 slice 本身:slice 的长度、容量以及起始偏移量。
package mainimport "fmt"func reclassify(planets *[]string) { *planets = (*planets)[0:8]}func main() { planets := []string{ "Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune", "Pluto", } reclassify(&planets) fmt.Println(planets)}
指针和接口
package mainimport ( "fmt" "strings")type talker interface { talk() string}func shout(t talker) { louder := strings.ToUpper(t.talk()) fmt.Println(louder)}type martian struct{}func (m martian) talk() string { return "nack nack"}func main() { shout(martian{}) shout(&martian{})}
- 本例中,无论 martian 还是指向 martian 的指针,都可以满足 talker 接口。
- 如果方法使用的是指针接收者,那么情况会有所不同。
package mainimport ( "fmt" "strings")type talker interface { talk() string}func shout(t talker) { louder := strings.ToUpper(t.talk()) fmt.Println(louder)}type laser intfunc (l *laser) talk() string { return strings.Repeat("pew ", int(*l))}func main() { pew := laser(2) shout(&pew)}
明智的使用指针
作业题
- 编写一个可以让海龟上下左右移动的程序:
- 程序中的海龟需要存储一个位置(x,y)
- 正数坐标表示向下或向右
- 通过使用方法对相应的变量实施自增和自减来实现移动
- 请使用 main 函数测试这些方法并打印出海龟的最终位置
package mainimport "fmt"type turtle struct { x, y int}func (t *turtle) up() { t.y--}func (t *turtle) down() { t.y++}func (t *turtle) left() { t.x--}func (t *turtle) right() { t.x++}func main() { var t turtle t.up() t.up() t.left() t.left() fmt.Println(t) t.down() t.down() t.right() t.right() fmt.Println(t)}