写在前面:
0x01 — 取址符和取值符
指针定义和获取在前面的文章中已经提及,在这里明确讲解下使用方法,获取一个变量的指针可以通过&符号获取:
package main
import (
"reflect"
"testing"
)
func TestPointer(t *testing.T) {
// 定义一个变量a
a := 10
// 获取的指针
b := &a
c := b
t.Log("a的值",a)
t.Log("b的值",b)
t.Log("c的值",c) // 指针可以被传递
t.Logf("b的类型%T",b) //通过格式化字符串%T可以打印对象的类型
// 通过反射也可以打印对象类型,格式化字符串的原理也是反射,关于反射后面会专门说明
t.Log("b的类型",reflect.TypeOf(b))
}
输出:
=== RUN TestPointer
pointer_test.go:14: a的值 10
pointer_test.go:15: b的值 0xc00001c320
pointer_test.go:16: c的值 0xc00001c320
pointer_test.go:17: b的类型*int
pointer_test.go:20: b的类型 *int
--- PASS: TestPointer (0.00s)
PASS
通过&符号可以获取a的指针,指针的类型是int型,在类型前面标记一个*号,表示此对象是一个指针,指针可以传递,指针指向的地址内容变化,所有指向此内存的指针内容将发生变化,指针无变化。
通过指针我们可以获取指针指向地址的数据,符号是*, 方法如下:
package main
import (
"reflect"
"testing"
)
func TestPointerGet(t *testing.T) {
// 定义一个变量a
a := 10
// 获取的指针
b := &a
t.Log("a的值",a)
t.Log("b的值",b)
c := *b
t.Log("c的值", c)
a = 20
t.Log("c的值", c)
t.Log("b的值", b)
t.Log("*b的值", *b)
}
输出:
=== RUN TestPointerGet
pointer_test.go:15: a的值 10
pointer_test.go:16: b的值 0xc0001281e8
pointer_test.go:18: c的值 10
pointer_test.go:20: c的值 10
pointer_test.go:21: b的值 0xc0001281e8
pointer_test.go:22: *b的值 20
--- PASS: TestPointerGet (0.00s)
PASS将*b赋值给c,这一步其实是复制,会重新分配一个内存空间,可以看到c的指针和b是不同的,我们在修改a的值得时候,改的是指针b指向的内存空间的值,所以*b会被同时修改,而c是不会变化的。
0x02 -- 定义指针
除了上面我们通过取址符获取指针外,我们还可以自己定义指针:
将*b赋值给c,这一步其实是复制,会重新分配一个内存空间,可以看到c的指针和b是不同的,我们在修改a的值的时候,改的是指针b指向的内存空间的值,所以*b会被同时修改,而c是不会变化的。
0x02 — 定义指针
除了上面我们通过取址符获取指针外,我们还可以自己定义指针:
package main
import (
"reflect"
"testing"
)
func TestPointerDefine(t *testing.T) {
var a *int
var b * float64
var ta int = 10
a = &ta
// 下面两行代码会报错:Cannot use '&tb' (type *float32) as the type *float64
// 定义的指针类型和赋值的指针类型必须相同,不可隐身转换
//var tb float32 = 20.1
//b = &tb
var tb float64 = 20.1
b = &tb
t.Log("a的值:", a)
t.Log("b的值:", b)
// 定义空指针
var c * int
t.Log("c的值:", c)
t.Log("是否为空指针:", c == nil)
}
输出:
=== RUN TestPointerDefine
pointer_test.go:19: a的值: 0xc0000a2210
pointer_test.go:20: b的值: 0xc0000a2218
pointer_test.go:24: c的值: <nil>
pointer_test.go:25: 是否为空指针: true
--- PASS: TestPointerDefine (0.00s)
PASS
空指针默认值是0,地址指向为nil,可以与nil比较确认是否为空指针。
0x02 — 总结
指针很简单,只需要明确指针本身和指针指向两个概念就可以,比如指向指针的指针也是可以的。变量存储的内容是指针或者是内容数据不影响变量的本质。在写go代码时,经常会考虑,我应该使用值类型还是指针类型?如以下几种情况,该如何决定:
- 一个局部变量赋值
- 结构体字段
- 函数返回值
- 传递给函数的参数
- 方法的接收者
如果你不确定使用那个,那么就使用指针。
知道自己写的代码想要干什么是很重要的,传递值类型是一种确保数据不可变的好方法,可有效防止数据在处理中被改变,尤其是有并发的情况,有些时候,这很必要,但是更多时候,可能真不需要,值传递会造成很大的内存空间占用,尤其是针对体积很大的结构体,如果本身数据量特别小,那么随便哪个都可以,不要再这样的问题上花费太多时间,优选指针。