什么是指针

可以简单将指针理解为内存地址。

指针是一种数据类型,用来存储一个内存地址,该地址指向存储在该内存中的对象,可以是整型、字符串、或者是我们自定义的任意结构体类型。

也可以理解为:指针就是一本书上的目录上面的页码,这个页码指向具体的内容。

指针的声明和定义

以字符串指针为例

// 声明一个字符串指针变量
var sp *string
// 直接通过 & 操作符获取一个字符串的地址
s := "Hello world."
sp := &s
// 还可以使用 new 函数,传入一个类型作为参数,用以返回该类型的指针
sp := new(string)

指针的操作

针对指针的操作,有两种:获取或者修改指针指向的值

  1. 获取指针指向的值

使用 *指针变量 获取指针指向的值,如 *sp 获取sp指针指向的内存地址的值。

  1. 修改指针指向的值

修改跟获取值也是类似的,使用 *指针变量 = 值 来修改,如 *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++
}

指针接收者

一点复习

  1. 如果接收者类型是 map、slice、channel 这类引用类型,不使用指针
  2. 如果需要修改接收者,需要使用指针
  3. 如果接收者是比较大的类型,可以考虑使用指针,因为内存拷贝廉价,所以效率更高。

指针的两大好处

  1. 可以修改指向数据的值
  2. 在变量赋值、参数传值的时候可以节省内存

使用建议

  1. 不要多map、slice、channel这类引用类型使用指针
  2. 如果需要修改方法接收者内部的数据或者状态时,需要使用指针
  3. 如果需要修改参数的值或者内部数据时,也需要使用指针类型的参数
  4. 如果是比较大的结构体,每次参数传递或者调用方法都要内存拷贝,内存占用多,这时候可以考虑使用指针
  5. 像int、bool这样的小数据类型没有必要使用指针
  6. 如果需要并发安全,则尽可能地不要使用指针,使用指针一定要保证并发安全,比如加锁之类的
  7. 指针最好不要嵌套,也就是不要使用一个指向指针的指针,这会让代码变得复杂。

指向接口的指针是否实现了该接口?

答案是:没有

看个例子:

// 定义一个结构体 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

所以结论是:虽然指向具体类型的指针可以实现一个接口,但是指向接口的指针永远不可能实现该接口