您好,登錄后才能下訂單哦!
這篇文章主要介紹了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") }}
感謝你能夠認真閱讀完這篇文章,希望小編分享CanSet, CanAddr是什么內容對大家有幫助,同時也希望大家多多支持億速云,關注億速云行業資訊頻道,遇到問題就找億速云,詳細的解決方法等著你來學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。