成人午夜视频全免费观看高清-秋霞福利视频一区二区三区-国产精品久久久久电影小说-亚洲不卡区三一区三区一区

CanSet,CanAddr是什么-創(chuàng)新互聯(lián)

這篇文章主要介紹了CanSet, CanAddr是什么,具有一定借鑒價(jià)值,需要的朋友可以參考下。希望大家閱讀完這篇文章后大有收獲。下面讓小編帶著大家一起了解一下。

專注于為中小企業(yè)提供成都網(wǎng)站制作、網(wǎng)站設(shè)計(jì)服務(wù),電腦端+手機(jī)端+微信端的三站合一,更高效的管理,為中小企業(yè)武宣免費(fèi)做網(wǎng)站提供優(yōu)質(zhì)的服務(wù)。我們立足成都,凝聚了一批互聯(lián)網(wǎng)行業(yè)人才,有力地推動(dòng)了1000+企業(yè)的穩(wěn)健成長,幫助中小企業(yè)通過網(wǎng)站建設(shè)實(shí)現(xiàn)規(guī)模擴(kuò)充和轉(zhuǎn)變。

一篇理解什么是CanSet, CanAddr?

什么是可設(shè)置( CanSet )

首先需要先明確下,可設(shè)置是針對 reflect.Value 的。普通的變量要轉(zhuǎn)變成為 reflect.Value 需要先使用 reflect.ValueOf() 來進(jìn)行轉(zhuǎn)化。

那么為什么要有這么一個(gè)“可設(shè)置”的方法呢?比如下面這個(gè)例子:

var x float64 = 3.4v := reflect.ValueOf(x)fmt.Println(v.CanSet()) // false

golang 里面的所有函數(shù)調(diào)用都是值復(fù)制,所以這里在調(diào)用 reflect.ValueOf 的時(shí)候,已經(jīng)復(fù)制了一個(gè) x 傳遞進(jìn)去了,這里獲取到的 v 是一個(gè) x 復(fù)制體的 value。那么這個(gè)時(shí)候,我們就希望知道我能不能通過 v 來設(shè)置這里的 x 變量。就需要有個(gè)方法來輔助我們做這個(gè)事情: CanSet()

但是, 非常明顯,由于我們傳遞的是 x 的一個(gè)復(fù)制,所以這里根本無法改變 x 的值。這里顯示的就是 false。

那么如果我們把 x 的地址傳遞給里面呢?下面這個(gè)例子:

var x float64 = 3.4v := reflect.ValueOf(&x)fmt.Println(v.CanSet()) // false

我們將 x 變量的地址傳遞給 reflect.ValueOf 了。應(yīng)該是 CanSet 了吧。但是這里卻要注意一點(diǎn),這里的 v 指向的是 x 的指針。所以 CanSet 方法判斷的是 x 的指針是否可以設(shè)置。指針是肯定不能設(shè)置的,所以這里還是返回 false。

那么我們下面需要可以通過這個(gè)指針的 value 值來判斷的是,這個(gè)指針指向的元素是否可以設(shè)置,所幸 reflect 提供了 Elem() 方法來獲取這個(gè)“指針指向的元素”。

var x float64 = 3.4v := reflect.ValueOf(&x)fmt.Println(v.Elem().CanSet()) // true

終于返回 true 了。但是這個(gè) Elem() 使用的時(shí)候有個(gè)前提,這里的 value 必須是指針對象轉(zhuǎn)換的 reflect.Value。(或者是接口對象轉(zhuǎn)換的 reflect.Value)。這個(gè)前提不難理解吧,如果是一個(gè) int 類型,它怎么可能有指向的元素呢?所以,使用 Elem 的時(shí)候要十分注意這點(diǎn),因?yàn)槿绻粷M足這個(gè)前提,Elem 是直接觸發(fā) panic 的。

在判斷完是否可以設(shè)置之后,我們就可以通過 SetXX 系列方法進(jìn)行對應(yīng)的設(shè)置了。

var x float64 = 3.4v := reflect.ValueOf(&x)if v.Elem().CanSet() {
    v.Elem().SetFloat(7.1)}fmt.Println(x)

更復(fù)雜的類型

對于復(fù)雜的 slice, map, struct, pointer 等方法,我寫了一個(gè)例子:

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 的方法了。

特別可以關(guān)注下,map,pointer 在修改的時(shí)候并不需要傳遞指針到 reflect.ValueOf 中。因?yàn)樗麄儽旧砭褪侵羔槨?/p>

所以在調(diào)用 reflect.ValueOf 的時(shí)候,我們必須心里非常明確,我們要傳遞的變量的底層結(jié)構(gòu)。比如 map, 實(shí)際上傳遞的是一個(gè)指針,我們沒有必要再將他指針化了。而 slice, 實(shí)際上傳遞的是一個(gè) SliceHeader 結(jié)構(gòu),我們在修改 Slice 的時(shí)候,必須要傳遞的是 SliceHeader 的指針。這點(diǎn)往往是需要我們注意的。

CanAddr

在 reflect 包里面可以看到,除了 CanSet 之外,還有一個(gè) CanAddr 方法。它們兩個(gè)有什么區(qū)別呢?

CanAddr 方法和 CanSet 方法不一樣的地方在于:對于一些結(jié)構(gòu)體內(nèi)的私有字段,我們可以獲取它的地址,但是不能設(shè)置它。

比如下面的例子:

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 的必要不充分條件。一個(gè) Value 如果 CanAddr, 不一定 CanSet。但是一個(gè)變量如果 CanSet,它一定 CanAddr。

源碼

假設(shè)我們要實(shí)現(xiàn)這個(gè) Value 元素 CanSet 或者 CanAddr,我們大概率會(huì)相到使用標(biāo)記位標(biāo)記。事實(shí)也確實(shí)是這樣。

我們先看下 Value 的結(jié)構(gòu):

type Value struct {
    typ *rtype
    ptr unsafe.Pointer
    flag}

這里要注意的就是,它是一個(gè)嵌套結(jié)構(gòu),嵌套了一個(gè) flag,而這個(gè) flag 本身就是一個(gè) uintptr。

type flag uintptr

這個(gè) flag 非常重要,它既能表達(dá)這個(gè) value  的類型,也能表達(dá)一些元信息(比如是否可尋址等)。flag雖然是uint類型,但是它用位來標(biāo)記表示。

首先它需要表示類型,golang 中的類型有27個(gè):

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位是結(jié)構(gòu)類型。

第六位 flagStickyRO: 標(biāo)記是否是結(jié)構(gòu)體內(nèi)部私有屬性
第七位 flagEmbedR0: 標(biāo)記是否是嵌套結(jié)構(gòu)體內(nèi)部私有屬性
第八位 flagIndir: 標(biāo)記 value 的ptr是否是保存了一個(gè)指針
第九位 flagAddr: 標(biāo)記這個(gè) value 是否可尋址
第十位 flagMethod: 標(biāo)記 value 是個(gè)匿名函數(shù)

CanSet, CanAddr是什么

其中比較不好理解的就是 flagStickyRO,flagEmbedR0

看下面這個(gè)例子:

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())}

這個(gè)例子中的 c 的 flagEmbedR0 標(biāo)記位就是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) 做“與”操作。

而他們的區(qū)別就是是否判斷 flagRO 的兩個(gè)位。所以他們的不同換句話說就是“判斷這個(gè) Value 是否是私有屬性”,私有屬性是只讀的。不能Set。

應(yīng)用

在開發(fā) collection (https://github.com/jianfengye/collection)庫的過程中,我就用到這么一個(gè)方法。我希望設(shè)計(jì)一個(gè)方法 func (arr *ObjPointCollection) ToObjs(objs interface{}) error,這個(gè)方法能將 ObjPointCollection 中的 objs reflect.Value 設(shè)置為參數(shù) 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")
    }}

感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享CanSet, CanAddr是什么內(nèi)容對大家有幫助,同時(shí)也希望大家多多支持創(chuàng)新互聯(lián),關(guān)注創(chuàng)新互聯(lián)-成都網(wǎng)站建設(shè)公司行業(yè)資訊頻道,遇到問題就找創(chuàng)新互聯(lián),詳細(xì)的解決方法等著你來學(xué)習(xí)!

文章名稱:CanSet,CanAddr是什么-創(chuàng)新互聯(lián)
鏈接分享:http://jinyejixie.com/article2/dipsoc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供全網(wǎng)營銷推廣、做網(wǎng)站、小程序開發(fā)、關(guān)鍵詞優(yōu)化、網(wǎng)站營銷、網(wǎng)站排名

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

h5響應(yīng)式網(wǎng)站建設(shè)
木里| 米林县| 苍溪县| 嘉义市| 南溪县| 房产| 无棣县| 共和县| 秭归县| 牟定县| 裕民县| 澎湖县| 陆丰市| 建瓯市| 右玉县| 敦煌市| 绥宁县| 丰宁| 横峰县| 弥勒县| 额尔古纳市| 云林县| 沅江市| 于田县| 五原县| 文登市| 洪江市| 灵宝市| 武鸣县| 凭祥市| 乐山市| 高要市| 灵川县| 阳新县| 马鞍山市| 任丘市| 六枝特区| 盐池县| 页游| 木里| 泽库县|