jinhailang / blog

技术博客:知其然,知其所以然
https://github.com/jinhailang/blog/issues
60 stars 6 forks source link

golang 指针引用使用场景 #16

Open jinhailang opened 6 years ago

jinhailang commented 6 years ago

指针和引用的使用场景关键在于两个字 延后,当我们需要延后修改 A 变量,可以先让变量 B 指向 A ,然后操作 B 就是操作 B 了。

以下为伪代码:

B:=&A
B=xxx

但是,golang 在赋值时,需要注意是引用还是拷贝,对于数组,切片,channel,map 默认是引用,即:

mp := make(map[string]string)
mp["a"] = "aaa"

mm := mp

mm["a"] = "sbsb"
mm["b"] = "hahahha"

fmt.Printf("mp: %v", mp)   // mp: map[a:sbsb b:hahahha]

但是,当 slice 使用 append 添加元素时需要额外注意,可能踩入一个著名的坑:

mp := make([]string, 1, 1) 
mp[0] = "aaa"

mm := &mp

mm = append(*mm, "sbsb")
mm = append(*mm, "hahahha")

fmt.Printf("mp: %v\r\n", mp) // mp: [aaa]

思考上面的代码,为什么 slice 变量 mp 里面值没有被修改呢?

这是因为当使用 append,超过 slice 容量时,会自动重新分配内存大小(扩容),可能是 2 倍或 1/4 的扩容,具体自行研究。这里扩容后 mm 地址会改变,因此 mpmm 指向的地址块也就不一样了。

这种场景下,如何仍然保持 mmmp 指向的地址一致呢?可以使用 引用,上面的代码作如下修改即可:

mp := make([]string, 1, 1)
mp[0] = "aaa"

mm := &mp

*mm = append(*mm, "sbsb")
*mm = append(*mm, "hahahha")

fmt.Printf("mp: %v\r\n", mp)

合理的使用指针和引用,才能写出更高效,优雅的代码。

思考如下问题:

flag 包是用来解析程序参数输入的,我们可以自定义解析函数,例如:

package main

import (
    "flag"
    "fmt"
    "strings"
)

type ary []string

func (ar *ary) String() string {

    return fmt.Sprintf("%v", *ar)
}

func (ar *ary) Set(v string) error {
    fmt.Printf("v: %s\r\n", v)

    *ar = ary(strings.Split(v, ","))

    return nil
}

func aryVar(name, value string, usage string) *ary {
    f := ary(strings.Split(value, ","))

    flag.CommandLine.Var(&f, name, usage)

    return &f
}

func main() {
    temp := aryVar("g", "1", "spilt ,")

    flag.Parse()

    fmt.Printf("temp: %v\r\n", *temp)
}

以上代码中为什么要使用引用,返回指针呢?

这块代码还是包括了接口的使用,仔细思考,受益颇多。