You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
golang-tutorial/docs/golang_tutorial_15.md

301 lines
6.9 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

15 - 指针
========================
上一节:[第十四篇 字符串](/docs/golang_tutorial_14.md)
下一节:[第十六篇 结构体](/docs/golang_tutorial_16.md)
这是本Golang系列教程的第15篇。我们将学习 Go 语言中指针的作用,并且将 Go 指针与其他语言(如 C/C++)的指针进行区分。
## 什么是指针?
指针是存储一个变量的内存地址的变量。
![pointer-explained](../images/pointer-explained.png)
在上图中,变量 `b` 的值是 `156`,存储在地址为 `0x1040a124` 的内存中。变量 `a` 存储了变量 `b` 的地址,也可以说 `a` 指向 `b`
## 指针的声明
指向类型 `T` 的指针用 `*T` 表示。
让我们写一些代码。
```golang
package main
import (
"fmt"
)
func main() {
b := 255
var a *int = &b
fmt.Printf("Type of a is %T\n", a)
fmt.Println("address of b is", a)
}
```
`&` 操作符用来获取一个变量的地址。在上面的程序中,第 9 行我们将 `b` 的地址赋给 `a``a` 的类型为 `*int`)。现在我们说 `a` 指向了 `b`。当我们打印 `a` 的值时,`b` 的地址将会被打印出来。程序的输出为:
```
Type of a is *int
address of b is 0x1040a124
```
你可能得到的是一个不同的 `b` 的地址,因为 `b` 可能被存储在内存的任意一个地方。
## 指针的空值
指针的空值为 `nil`
```golang
package main
import (
"fmt"
)
func main() {
a := 25
var b *int
if b == nil {
fmt.Println("b is", b)
b = &a
fmt.Println("b after initialization is", b)
}
}
```
'b' 首先被初始化为 `nil`,之后被赋值为 `a` 的地址,程序输出为:
```golang
b is <nil>
b after initialization is 0x1040a124
```
## 利用 `new` 函数创建指针
Go 还提供一个实用的函数 `new` 来创建指针。`new` 函数接收一个类型,并返回一个该类型的空值指针。
我们来看一个实例:
```golang
package main
import (
"fmt"
)
func main() {
size := new(int)
fmt.Printf("Size value is %d, type is %T, address is %v\n", *size, size, size)
*size = 85
fmt.Println("New size value is", *size)
}
```
上面的程序中,我们在第 8 行利用 `new` 函数创建了一个指向 `int` 类型的指针。由于 `int` 类型的空值为 `0`,变量 `size` 是一个指向 `0` 的整数指针(`*int` 类型)。
程序输出:
```golang
Size value is 0, type is *int, address is 0x414020
New size value is 85
```
## 指针的间接引用
间接引用dereference指针就是获取指针所指向的变量的值。间接引用 `a` 的语句是 `*a`。例如:
```golang
package main
import (
"fmt"
)
func main() {
b := 255
a := &b
fmt.Println("address of b is", a)
fmt.Println("value of b is", *a)
}
```
在第 10 行,我们间接引用了 `a` 并打印了它的值,也就是 `b` 的值。程序输出为:
```golang
address of b is 0x1040a124
value of b is 255
```
我们再编写一个利用指针改变变量 `b` 的值的程序:
```golang
package main
import (
"fmt"
)
func main() {
b := 255
a := &b
fmt.Println("address of b is", a)
fmt.Println("value of b is", *a)
*a++
fmt.Println("new value of b is", b)
}
```
在第 12 行,我们将 `a` 所指向的值加上了 1也就是改变了 `b` 的值(因为 `a` 指向 `b`)。因此 `b` 变成了256程序输出
```golang
address of b is 0x1040a124
value of b is 255
new value of b is 256
```
## 将指针传入函数
```golang
package main
import (
"fmt"
)
func change(val *int) {
*val = 55
}
func main() {
a := 58
fmt.Println("value of a before function call is",a)
b := &a
change(b)
fmt.Println("value of a after function call is", a)
}
```
在第 14 行,我们将存有 `a` 的地址的指针 `b` 传入了 `change` 函数。`change` 函数通过间接引用的方法改变了 `a` 的值,程序输出:
```golang
value of a before function call is 58
value of a after function call is 55
```
## 从函数中返回指针
从函数中返回一个局部变量的指针是完全可以的Go 语言的编译器会将这个变量分配到堆中。
```golang
package main
import (
"fmt"
)
func hello() *int {
i := 5
return &i
}
func main() {
d := hello()
fmt.Println("Value of d", *d)
}
```
在第 9 行中,我们从 `hello` 函数中返回了局部变量 `i` 的地址。 **该操作在 C/C++ 等语言中是未定义的,因为一旦 `hello` 函数执行“返回”之后,变量 `i` 就不再存在于当前域scope内了。但是在 Go 语言中编译器会进行“逃逸分析”escape analysis并且在地址离开当前域时将 `i` 分配到堆中。** 因此这一程序可以运行,输出为:
```golang
Value of d 5
```
## 不要将指向数组的指针传入函数中!要用切片
假设我们想要在一个函数内部对一个数组进行更改,一种实现方法是将一个指向数组的指针传入函数。
```golang
package main
import (
"fmt"
)
func modify(arr *[3]int) {
(*arr)[0] = 90
}
func main() {
a := [3]int{89, 90, 91}
modify(&a)
fmt.Println(a)
}
```
在第 13 行,我们将数组 `a` 的地址传入 `modify` 函数。在第 8 行,`modify` 函数内,我们间接引用了 `arr` 并且将数组的第一个元素改为 `90`。这个程序会输出:`[90 90 91]`。
**a[x] 是 (\*a)[x] 的简写形式,所以上面程序中的 (\*arr)[0] 可以被 arr[0] 代替。** 我们用简写方法重写这个程序:
```golang
package main
import (
"fmt"
)
func modify(arr *[3]int) {
arr[0] = 90
}
func main() {
a := [3]int{89, 90, 91}
modify(&a)
fmt.Println(a)
}
```
这个程序也会输出:`[90 90 91]`。
**尽管我们可以通过将指向数组的指针传入函数中来对数组进行修改,但是这在 Go 语言中并不是地道的方式,我们应该用切片来解决这类问题。**
我们用切片来重写之前的程序:
```golang
package main
import (
"fmt"
)
func modify(sls []int) {
sls[0] = 90
}
func main() {
a := [3]int{89, 90, 91}
modify(a[:])
fmt.Println(a)
}
```
在第 13 行,我们将一个切片传入了 `modify` 函数,这个函数将切片的第一个元素改为 `90`,程序输出仍然是:`[90 90 91]`。 **因此,不要再把指向数组的指针传入函数了,应该使用的是切片 :)**
## Go 不支持指针的算术运算
不像 C/C++ 中的指针可以进行算术运算Go 语言并不支持指针算术运算。
```golang
package main
func main() {
b := [...]int{109, 110, 111}
p := &b
p++
}
```
上面的程序会出现编译错误:`main.go:6: invalid operation: p++ (non-numeric type *[3]int)`。
我(原文作者)在 [github](https://github.com/golangbot/pointers) 上创建了一个简单的小程序,包含了我们这次所学的全部内容。