您好,欢迎访问一九零五行业门户网

什么是CanSet, CanAddr?

下面由golang教程栏目给大家介绍什么是canset, canaddr,希望对需要的朋友有所帮助!
一篇理解什么是canset, canaddr?
什么是可设置( canset )
首先需要先明确下,可设置是针对 reflect.value 的。普通的变量要转变成为 reflect.value 需要先使用 reflect.valueof() 来进行转化。
那么为什么要有这么一个“可设置”的方法呢?比如下面这个例子:
var x float64 = 3.4v := reflect.valueof(x)fmt.println(v.canset()) // false
golang 里面的所有函数调用都是值复制,所以这里在调用 reflect.valueof 的时候,已经复制了一个 x 传递进去了,这里获取到的 v 是一个 x 复制体的 value。那么这个时候,我们就希望知道我能不能通过 v 来设置这里的 x 变量。就需要有个方法来辅助我们做这个事情: canset()
但是, 非常明显,由于我们传递的是 x 的一个复制,所以这里根本无法改变 x 的值。这里显示的就是 false。
那么如果我们把 x 的地址传递给里面呢?下面这个例子:
var x float64 = 3.4v := reflect.valueof(&x)fmt.println(v.canset()) // false
我们将 x 变量的地址传递给 reflect.valueof 了。应该是 canset 了吧。但是这里却要注意一点,这里的 v 指向的是 x 的指针。所以 canset 方法判断的是 x 的指针是否可以设置。指针是肯定不能设置的,所以这里还是返回 false。
那么我们下面需要可以通过这个指针的 value 值来判断的是,这个指针指向的元素是否可以设置,所幸 reflect 提供了 elem() 方法来获取这个“指针指向的元素”。
var x float64 = 3.4v := reflect.valueof(&x)fmt.println(v.elem().canset()) // true
终于返回 true 了。但是这个 elem() 使用的时候有个前提,这里的 value 必须是指针对象转换的 reflect.value。(或者是接口对象转换的 reflect.value)。这个前提不难理解吧,如果是一个 int 类型,它怎么可能有指向的元素呢?所以,使用 elem 的时候要十分注意这点,因为如果不满足这个前提,elem 是直接触发 panic 的。
在判断完是否可以设置之后,我们就可以通过 setxx 系列方法进行对应的设置了。
var x float64 = 3.4v := reflect.valueof(&x)if v.elem().canset() {    v.elem().setfloat(7.1)}fmt.println(x)
更复杂的类型
对于复杂的 slice, map, struct, pointer 等方法,我写了一个例子:
package mainimport (    fmt    reflect)type foo interface {    name() string}type foostruct struct {    a string}func (f foostruct) name() string {    return f.a}type foopointer struct {    a string}func (f *foopointer) name() string {    return f.a}func main() {    {        // slice        a := []int{1, 2, 3}        val := reflect.valueof(&a)        val.elem().setlen(2)        val.elem().index(0).setint(4)        fmt.println(a) // [4,2]    }    {        // map        a := map[int]string{            1: foo1,            2: foo2,        }        val := reflect.valueof(&a)        key3 := reflect.valueof(3)        val3 := reflect.valueof(foo3)        val.elem().setmapindex(key3, val3)        fmt.println(val) // &map[1:foo1 2:foo2 3:foo3]    }    {        // map        a := map[int]string{            1: foo1,            2: foo2,        }        val := reflect.valueof(a)        key3 := reflect.valueof(3)        val3 := reflect.valueof(foo3)        val.setmapindex(key3, val3)        fmt.println(val) // &map[1:foo1 2:foo2 3:foo3]    }    {        // struct        a := foostruct{}        val := reflect.valueof(&a)        val.elem().fieldbyname(a).setstring(foo2)        fmt.println(a) // {foo2}    }    {        // pointer        a := &foopointer{}        val := reflect.valueof(a)        val.elem().fieldbyname(a).setstring(foo2)        fmt.println(a) //&{foo2}    }}
上面的例子如果都能理解,那基本上也就理解了 canset 的方法了。
特别可以关注下,map,pointer 在修改的时候并不需要传递指针到 reflect.valueof 中。因为他们本身就是指针。
所以在调用 reflect.valueof 的时候,我们必须心里非常明确,我们要传递的变量的底层结构。比如 map, 实际上传递的是一个指针,我们没有必要再将他指针化了。而 slice, 实际上传递的是一个 sliceheader 结构,我们在修改 slice 的时候,必须要传递的是 sliceheader 的指针。这点往往是需要我们注意的。
canaddr
在 reflect 包里面可以看到,除了 canset 之外,还有一个 canaddr 方法。它们两个有什么区别呢?
canaddr 方法和 canset 方法不一样的地方在于:对于一些结构体内的私有字段,我们可以获取它的地址,但是不能设置它。
比如下面的例子:
package mainimport (    fmt    reflect)type foostruct struct {    a string    b int}func main() {    {        // struct        a := foostruct{}        val := reflect.valueof(&a)        fmt.println(val.elem().fieldbyname(b).canset())  // false        fmt.println(val.elem().fieldbyname(b).canaddr()) // true    }}
所以,canaddr 是 canset 的必要不充分条件。一个 value 如果 canaddr, 不一定 canset。但是一个变量如果 canset,它一定 canaddr。
源码
假设我们要实现这个 value 元素 canset 或者 canaddr,我们大概率会相到使用标记位标记。事实也确实是这样。
我们先看下 value 的结构:
type value struct {    typ *rtype    ptr unsafe.pointer    flag}
这里要注意的就是,它是一个嵌套结构,嵌套了一个 flag,而这个 flag 本身就是一个 uintptr。
type flag uintptr
这个 flag 非常重要,它既能表达这个 value  的类型,也能表达一些元信息(比如是否可寻址等)。flag虽然是uint类型,但是它用位来标记表示。
首先它需要表示类型,golang 中的类型有27个:
const (    invalid kind = iota    bool    int    int8    int16    int32    int64    uint    uint8    uint16    uint32    uint64    uintptr    float32    float64    complex64    complex128    array    chan    func    interface    map    ptr    slice    string    struct    unsafepointer)
所以使用5位(2^5-1=63)就足够放这么多类型了。所以 flag 的低5位是结构类型。
第六位 flagstickyro: 标记是否是结构体内部私有属性
第七位 flagembedr0: 标记是否是嵌套结构体内部私有属性
第八位 flagindir: 标记 value 的ptr是否是保存了一个指针
第九位 flagaddr: 标记这个 value 是否可寻址
第十位 flagmethod: 标记 value 是个匿名函数
其中比较不好理解的就是 flagstickyro,flagembedr0
看下面这个例子:
type foostruct struct {    a string    b int}type barstruct struct {    foostruct}{        b := barstruct{}        val := reflect.valueof(&b)        c := val.elem().fieldbyname(b)        fmt.println(c.canaddr())}
这个例子中的 c 的 flagembedr0 标记位就是1了。
所以我们再回去看 canset 和 canaddr 方法
func (v value) canaddr() bool {    return v.flag&flagaddr != 0}func (v value) canset() bool {    return v.flag&(flagaddr|flagro) == flagaddr}
他们的方法就是把 value 的 flag 和 flagaddr 或者 flagro (flagstickyro,flagembedr0) 做“与”操作。
而他们的区别就是是否判断 flagro 的两个位。所以他们的不同换句话说就是“判断这个 value 是否是私有属性”,私有属性是只读的。不能set。
应用
在开发 collection (https://github.com/jianfengye/collection)库的过程中,我就用到这么一个方法。我希望设计一个方法 func (arr *objpointcollection) toobjs(objs interface{}) error,这个方法能将 objpointcollection 中的 objs reflect.value 设置为参数 objs 中。
func (arr *objpointcollection) toobjs(objs interface{}) error {    arr.mustnotbebasetype()    objval := reflect.valueof(objs)    if objval.elem().canset() {        objval.elem().set(arr.objs)        return nil    }    return errors.new(element should be can set)}
使用方法:
func testobjpointcollection_toobjs(t *testing.t) {    a1 := &foo{a: a1, b: 1}    a2 := &foo{a: a2, b: 2}    a3 := &foo{a: a3, b: 3}    barr := []*foo{}    objcoll := newobjpointcollection([]*foo{a1, a2, a3})    err := objcoll.toobjs(&barr)    if err != nil {        t.fatal(err)    }    if len(barr) != 3 {        t.fatal(toobjs error len)    }    if barr[1].a != a2 {        t.fatal(toobjs error copy)    }}
总结
canaddr 和 canset 刚接触的时候是会有一些懵逼,还是需要稍微理解下 reflect.value 的 flag 就能完全理解了。
以上就是什么是canset, canaddr?的详细内容。
其它类似信息

推荐信息