首页后端开发其他后端知识详解什么是CanSet?什么是CanAddr?

详解什么是CanSet?什么是CanAddr?

时间2024-03-27 06:18:03发布访客分类其他后端知识浏览1556
导读:关于“详解什么是CanSet?什么是CanAddr?”的知识点有一些人不是很理解,对此小编给大家总结了相关内容,文中的内容简单清晰,易于学习与理解,具有一定的参考学习价值,希望能对大家有所帮助,接下来就跟随小编一起学习一下“详解什么是Can...
关于“详解什么是CanSet?什么是CanAddr?”的知识点有一些人不是很理解,对此小编给大家总结了相关内容,文中的内容简单清晰,易于学习与理解,具有一定的参考学习价值,希望能对大家有所帮助,接下来就跟随小编一起学习一下“详解什么是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?的介绍啦,需要的朋友可以参考上述内容,希望对大家有帮助,欢迎关注网络,小编将为大家输出更多高质量的实用文章!

声明:本文内容由网友自发贡献,本站不承担相应法律责任。对本内容有异议或投诉,请联系2913721942#qq.com核实处理,我们将尽快回复您,谢谢合作!

gogolang

若转载请注明出处: 详解什么是CanSet?什么是CanAddr?
本文地址: https://pptw.com/jishu/654034.html
node​项目的常见package.json配置属性怎么用 Angular组件模板的使用怎么学习,有哪些知识要掌握

游客 回复需填写必要信息