文章目录
- 导言
- 指针
- 指针是什么?
- 声明指针
- 指针的零值
- 使用 `new`函数 创建指针
- 指针解引用
- 将指针传递给函数
- 从函数中返回指针
- 数组指针 vs 切片
- 指针运算
- 原作者留言
- 最后
导言
- If translation is not allowed, please leave me in the comment area and I will delete it as soon as possible.
指针
在这一部分,我们来说说 Go
语言 的指针。Go
语言 的指针与其他的编程语言,如 C
、C++
,略有不同。
下面就开始讲解吧~
指针是什么?
指针是一个变量,它的值是 另外一个变量的内存地址。
在上面的插图中,b
的值是 156
,它的内存地址是 0x1040a124
。指针a
持有 b
的内存地址,即 指针a
指向 b
。
声明指针
指向类型为 T
的指针,它的类型是 *T
。(可能有些拗口,慢慢你就明白了)
接下来,我们来声明一个指针:
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
(由于 b
的类型是 int
,所以 a
的类型必须为 *int
)。此时 a
的值就是 b
的地址,所以我们可以说:a
指向了 b
。
运行程序,输出如下:
Type of a is *int
address of b is 0x1040a124
在你本机运行时,b
的地址可能不太一样,这是因为:在内存中,存储 b
的位置不止 1
个 。
指针的零值
指针的零值是 nil
。
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
的地址。
程序输出如下:
b is <nil>
b after initialisation is 0x1040a124
使用 new
函数 创建指针
Go
语言 提供了一个很方便的函数 — new
函数。new
函数 将类型作为参数,返回对应类型的指针,且该指针指向的内存区域会被清零。
看看程序理解下:
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
函数,创建了 1
个类型为 *int
的指针 (该指针指向的内存已被清零)。因为 int
类型 的零值是 0
,所以 size
的类型为 *int
,*size
等于 0
。
*size
会在下面讲解。
程序输出如下:
Size value is 0, type is *int, address is 0x414020
New size value is 85
指针解引用
指针解引用的意思是:获取该指针指向的变量值。*a
表示解除 a
指针 的引用 。
看看下面的程序:
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
,我们解除了 a
的引用,获取到它指向的值 — 它的值就是 b
。
程序输出如下:
address of b is 0x1040a124
value of b is 255
接下来,我们使用指针修改 b
的值。
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
。因为 a
指向了 b
,所以 b
的值也会自增 1
。
程序输出为:
address of b is 0x1040a124
value of b is 255
new value of b is 256
将指针传递给函数
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
行,我们将 指针变量b
— 它持有 a
的地址,传递给 函数change
。在 change
函数 内部,val
指针 指向的值被改为 55
。
程序输出如下:
value of a before function call is 58
value of a after function call is 55
从函数中返回指针
在 Go
中,我们可以返回 函数内局部变量的地址。因为 Go
的编译器很智能,它能经过逃逸分析,将局部变量分配到堆。
package main
import (
"fmt"
)
func hello() *int {
i := 5
return &i
}
func main() {
d := hello()
fmt.Println("Value of d", *d)
}
在上面程序的第 9
行,我们返回了 局部变量i
的地址。在 C
、C++
中,执行这样的操作将会导致错误发生,因为函数返回时,栈空间会被回收。而在 Go
中,编译器会经过逃逸分析,将 i
分配到堆上。
因此,程序正常工作,并输出:
Value of d 5
数组指针 vs 切片
假如现在我们想对数组做一些修改,这些修改需要在函数内执行,且必须在函数外可见。
其中的一个实现方法就是:将 指向数组的指针 传递给函数。
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
行,我们解除 arr
的引用,并将被指向数组的第 1
个元素修改为 90
。
程序输出如下:
[90 90 91]
a[x]
是 (*a)[x]
的便捷写法。因此上面程序的 (*arr)[0]
,可以写为 arr[0]
。
使用便捷句式,我们重写下上面的函数。
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
来说,这并不常用。对于这个案例,我们最好使用切片。
我们使用切片重写程序,重写后代码如下:
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
函数 传递了 1
个切片。modify
函数 会将切片的第1
个元素,修改为 90
。
程序同样会输出:
[90 90 91]
所以,请不要再使用数组指针了,使用切片代替吧!这会使代码更简洁。
指针运算
虽然 C
、C++
支持指针运算,但是 Go
并不支持。
package main
func main() {
b := [...]int{109, 110, 111}
p := &b
p++
}
上面的程序会抛出编译错误:
main.go:6: invalid operation: p++ (non-numeric type *[3]int)