CanSet、CanAddrとは何ですか?

藏色散人
リリース: 2020-10-28 15:32:26
転載
2838 人が閲覧しました

次のコラムでは、golang チュートリアル コラムの CanSet と CanAddr について紹介します。困っている友達の役に立つでしょう!

CanSet、CanAddrとは何ですか?

CanSet と CanAddr とは何かを理解するための記事?

setable (CanSet) とは

まず、settable が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 になります。コピーの価値。したがって、現時点では、v を通じて x 変数をここに設定できるかどうかを知りたいと考えています。これを行うには、CanSet()

というメソッドが必要です。ただし、x のコピーを渡しているため、ここでは x の値をまったく変更できないことは明らかです。ここに示されているものは虚偽です。

それでは、x のアドレスを渡すとどうなるでしょうか?以下に例を示します。

var x float64 = 3.4v := reflect.ValueOf(&x)fmt.Println(v.CanSet()) // false
ログイン後にコピー

x 変数のアドレスをreflect.ValueOfに渡します。 CanSet である必要があります。ただし、ここで注意すべき点は、ここでの v は x のポインタを指しているということです。したがって、CanSet メソッドは、x のポインターを設定できるかどうかを判断します。ポインターは絶対に設定できないため、ここでも false が返されます。

次に、このポインタの値から判断する必要があるのは、このポインタが指す要素を設定できるかどうかです。幸いなことに、reflect には「ポインタが指す要素」を取得するための Elem() メソッドが用意されています。 」。

var x float64 = 3.4v := reflect.ValueOf(&x)fmt.Println(v.Elem().CanSet()) // true
ログイン後にコピー

最終的に true を返します。ただし、Elem()を使用する場合は前提条件があり、ここでの値はポインタオブジェクトから変換されたreflect.Valueである必要があります。 (または、インターフェイス オブジェクト変換の場合は、reflect.Value)。この前提を理解するのは難しくありませんが、それが int 型の場合、どのようにしてそれが指す要素を持つことができるのでしょうか?したがって、この前提条件が満たされていない場合、Elem は直接パニックを引き起こすため、Elem を使用するときは十分に注意してください。

設定可能かどうかを判断した後、SetXX 一連のメソッドを通じて対応する設定を行うことができます。

var x float64 = 3.4v := reflect.ValueOf(&x)if v.Elem().CanSet() {
    v.Elem().SetFloat(7.1)}fmt.Println(x)
ログイン後にコピー

より複雑な型

複雑なスライス、マップ、構造体、ポインター、その他のメソッドについては、次の例を書きました。

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 メソッドを基本的に理解できます。

マップとポインターが変更されるときに、reflect.ValueOf にポインターを渡す必要がないという事実に特に注意してください。なぜなら、それらはそれ自体がポインタだからです。

したがって、reflect.ValueOf を呼び出すときは、渡したい変数の基礎となる構造を明確にしておく必要があります。たとえば、map は実際にはポインターを転送するため、それを指す必要はもうありません。スライスに関しては、実際に渡されるのは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 の必要条件であり、不十分条件です。 CanAddr の場合は値。必ずしも CanSet である必要はありません。ただし、変数 canSet の場合は、CanAddr でなければなりません。

ソースコード

この値要素 CanSet または CanAddr を実装すると仮定すると、マーク ビット マークを使用する可能性が高くなります。まさにその通りです。

最初に Value の構造を見てみましょう:

type Value struct {
    typ *rtype
    ptr unsafe.Pointer
    flag}
ログイン後にコピー

ここで注意すべき点は、これは内部にフラグがネストされたネスト構造であり、フラグ自体は uintptr であるということです。 。

type flag uintptr
ログイン後にコピー

このフラグは非常に重要で、値の型だけでなく、いくつかのメタ情報 (アドレス指定可能かどうかなど) も表現できます。 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 是个匿名函数

CanSet、CanAddrとは何ですか?

其中比较不好理解的就是 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とは何ですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:learnku.com
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート
私たちについて 免責事項 Sitemap
PHP中国語ウェブサイト:福祉オンライン PHP トレーニング,PHP 学習者の迅速な成長を支援します!