次のコラムでは、golang チュートリアル コラムの 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 是个匿名函数
其中比较不好理解的就是 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 サイトの他の関連記事を参照してください。