什么是指针
可以简单将指针理解为内存地址。
指针是一种数据类型,用来存储一个内存地址,该地址指向存储在该内存中的对象,可以是整型、字符串、或者是我们自定义的任意结构体类型。
也可以理解为:指针就是一本书上的目录上面的页码,这个页码指向具体的内容。
指针的声明和定义
以字符串指针为例
// 声明一个字符串指针变量
var sp *string
// 直接通过 & 操作符获取一个字符串的地址
s := "Hello world."
sp := &s
// 还可以使用 new 函数,传入一个类型作为参数,用以返回该类型的指针
sp := new(string)
指针的操作
针对指针的操作,有两种:获取或者修改指针指向的值
- 获取指针指向的值
使用 *指针变量
获取指针指向的值,如 *sp
获取sp指针指向的内存地址的值。
- 修改指针指向的值
修改跟获取值也是类似的,使用 *指针变量 = 值
来修改,如 *sp = "new value"
,这样,就把 sp指针指向的内存地址值修改为了 new value
注意
另外,通过 var sp *string
定义的sp指针,初始值是nil,表示它没有指向任何一块内存地址,我们不能够对它进行取值和赋值操作,否则,会提示:
invalid memory address or nil pointer dereference
要解决这个问题,只需要使用 new 函数给该地址分配一块内存即可:
var sp *string = new(string)
还有就是,不能获取常量的指针。
指针参数
当我们需要在函数中通过形参改变实参的值是,需要使用指针类型的参数
举个栗子,我们有个 increase
函数,每次调用的时候,我们就让传入的参数加一
无效写法:
func increase(num int){
num++
}
上述修改的只是形参传入的值,外部的实参没有变化
有效写法:
func increase(num *int){
*num++
}
指针接收者
一点复习
- 如果接收者类型是 map、slice、channel 这类引用类型,不使用指针
- 如果需要修改接收者,需要使用指针
- 如果接收者是比较大的类型,可以考虑使用指针,因为内存拷贝廉价,所以效率更高。
指针的两大好处
- 可以修改指向数据的值
- 在变量赋值、参数传值的时候可以节省内存
使用建议
- 不要多map、slice、channel这类引用类型使用指针
- 如果需要修改方法接收者内部的数据或者状态时,需要使用指针
- 如果需要修改参数的值或者内部数据时,也需要使用指针类型的参数
- 如果是比较大的结构体,每次参数传递或者调用方法都要内存拷贝,内存占用多,这时候可以考虑使用指针
- 像int、bool这样的小数据类型没有必要使用指针
- 如果需要并发安全,则尽可能地不要使用指针,使用指针一定要保证并发安全,比如加锁之类的
- 指针最好不要嵌套,也就是不要使用一个指向指针的指针,这会让代码变得复杂。
指向接口的指针是否实现了该接口?
答案是:没有
看个例子:
// 定义一个结构体 address,其实现了 fmt.Stringer 接口
type address stuct {
city string
province string
}
func (addr address) String() string {
return fmt.Sprintf("Address is %s/%s", addr.province, addr.city)
}
// 然后,我们定义一个调用 Stringer 的打印函数
func printer(s fmt.Stringer) {
fmt.Println("string value is : ", s.String())
}
一般情况下,我们知道,值接收者实现了Stringer接口,那么它的值类型和指针类型也实现了该接口。
因此,我们可以这样调用:
func main(){
addr := address{city: "Shenzhen", province: "GuangDong"}
// 验证值类型接收者
printer(addr)
// 验证指针类型接收者
printer(&addr)
}
这些都是没有问题的,我们接下来验证下指向接口的指针是否实现了该接口
func main(){
// 声明一个 Stringer 接口 si,因为 address实现了 Stringer接口,因此可以直接赋值给si
var si fmt.Stringer := address{city: "Shenzhen", province: "GuangDong"}
// 调用 printer 打印 si,没有问题
printer(si)
// 来一个指向si的指针sip(指向接口的指针)
sip := &si
// 看看sip是否实现了Stringer接口
printer(sip)
}
最后,printer(sip)
这句会编译不通过,提示 Cannot use '&si' (type *fmt.Stringer) as type fmt.Stringer
所以结论是:虽然指向具体类型的指针可以实现一个接口,但是指向接口的指针永远不可能实现该接口