V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
newmiao
V2EX  ›  Go 编程语言

Dig101-Go 之如何在函数内修改指针

  •  
  •   newmiao · 2020-05-23 11:25:59 +08:00 · 1522 次点击
    这是一个创建于 1405 天前的主题,其中的信息可能已经有所发展或是发生改变。

    Dig101: dig more, simplified more and know more

    今天来看一个小问题:如何在函数内部修改一个指针(参数或接收者),使其值的改变能反映在函数外部

    直接上代码,这样可以么?

    type ArgType struct {
      A string
      b int
    }
    
    func modifyPointerArg1(arg *ArgType) {
      arg = &ArgType{"arg1", 1}
      fmt.Println("inside modifyPointerArg1:", arg)
    }
    

    答案是 [不可以]

    等会分析,再看一个,这个呢?

    func modifyPointerArg2(arg *ArgType) {
      *arg = ArgType{"arg2", 2}
      fmt.Println("inside modifyPointerArg2:", arg)
    }
    

    答案是 [可以]

    仔细看下,你应该就明白了。

    第一个替换的是指针变量本身,

    也就是在函数modifyPointerArg1的作用域内,其修改是有效

    函数返回后,并不影响指针arg所指向的值(别忘了,Go 参数传递是值传递嘛!)

    至于modifyPointerArg2则是对指针解引用,修改了其指向的值


    这样的方式其实还有很多,比如这个:

    func modifyPointerArg3(arg *ArgType) {
      val := reflect.ValueOf(arg)
      val.Elem().FieldByName("A").SetString("arg3")
      fmt.Println("inside modifyPointerArg3:", arg)
      // val.Elem().FieldByName("b").SetInt(3)
      // panic: reflect: reflect.flag.mustBeAssignable using value obtained using unexported field
    }
    

    实际是利用反射的Elem()获取val的值

    • 如果其为空接口(empty interface),则获取其内部值(空接口值字段的类型是指针哦)
    • 如果其为指针(pointer),则获取其指向的值

    获取到的结构如下

    type Value struct {
        // 类型
        typ *rtype
        // 值指针
        ptr unsafe.Pointer
        // 标志位
      flag
    }
    

    然后对应类型修改时, 实际就是对指针解引用修改其指向的值

    func (v Value) SetString(x string) {
      v.mustBeAssignable()
      v.mustBe(String)
      // 这里
      *(*string)(v.ptr) = x
    }
    

    Tips: 这里注意一点,不导出的字段(结构体内小写的字段)不能用此类方法修改,会 panic !

    再如:

    func modifyPointerArg4(arg *ArgType) {
      jsonStr := `{"A":"arg4","b":4}`
      json.Unmarshal([]byte(jsonStr), arg)
    }
    

    内部实际也是用了反射修改指针指向的值

    另外,把上边几个测试函数由指针参数换为指针接受者,也是一样的

    比如:

    func (arg *ArgType) modifyPointerReceiver4() {
      jsonStr := `{"A":"arg4","b":4}`
      json.Unmarshal([]byte(jsonStr), arg)
    }
    

    有兴趣可以去自行尝试下其他几个函数。

    说了这么多,这样修改有啥用么?

    常见的一个场景便是:

    测试时,可以通过对接口实现对应的 mock 函数,改变参数或接收者,以达到排除依赖,进行单元测试的目的。


    文章首发公众号:newbmiao

    推荐阅读:Dig101-Go 系列

    欢迎关注,获取及时更新内容

    目前尚无回复
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3452 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 32ms · UTC 11:12 · PVG 19:12 · LAX 04:12 · JFK 07:12
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.