欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

go reflect要不要傳指針原理詳解

 更新時(shí)間:2023年01月03日 10:18:24   作者:eleven26  
這篇文章主要為大家介紹了go reflect要不要傳指針原理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

正文

在我們看一些使用反射的代碼的時(shí)候,會(huì)發(fā)現(xiàn),reflect.ValueOfreflect.TypeOf 的參數(shù)有些地方使用的是指針參數(shù),有些地方又不是指針參數(shù), 但是好像這兩者在使用上沒(méi)什么區(qū)別,比如下面這樣:

var a = 1
v1 := reflect.ValueOf(a)
v2 := reflect.ValueOf(&a)
fmt.Println(v1.Int())        // 1
fmt.Println(v2.Elem().Int()) // 1

它們的區(qū)別貌似只是需不需要使用 Elem() 方法,但這個(gè)跟我們是否傳遞指針給 reflect.ValueOf 其實(shí)關(guān)系不大, 相信沒(méi)有人為了使用一下 Elem() 方法,就去傳遞指針給 reflect.ValueOf 吧。

那我們什么時(shí)候應(yīng)該傳遞指針參數(shù)呢?

什么時(shí)候傳遞指針?

要回答這個(gè)問(wèn)題,我們可以思考一下以下列出的幾點(diǎn)內(nèi)容:

  • 是否要修改變量的值,要修改就要用指針
  • 結(jié)構(gòu)體類(lèi)型:是否要修改結(jié)構(gòu)體里的字段,要修改就要用指針
  • 結(jié)構(gòu)體類(lèi)型:是否要調(diào)用指針接收值方法,要調(diào)用就要用指針
  • 對(duì)于 chan、map、slice 類(lèi)型,我們傳遞值和傳遞指針都可以修改其內(nèi)容
  • 對(duì)于非 interface 類(lèi)型,傳遞給 TypeOfValueOf 的時(shí)候都會(huì)轉(zhuǎn)換為 interface 類(lèi)型,如果本身就是 interface 類(lèi)型,則不需轉(zhuǎn)換。
  • 指針類(lèi)型不可修改,但是可以修改指針指向的值。(v := reflect.ValueOf(&a),v.CanSet()false,v.Elem().CanSet()true
  • 字符串:我們可以對(duì)字符串進(jìn)行替換,但不能修改字符串的某一個(gè)字符

大概總結(jié)下來(lái),就是:如果我們想修改變量的內(nèi)容,就傳遞指針,否則就傳遞值。對(duì)于某些復(fù)合類(lèi)型如果其內(nèi)部包含了底層數(shù)據(jù)的指針, 也是可以通過(guò)傳值來(lái)修改其底層數(shù)據(jù)的,這些類(lèi)型有 chanmap、slice。 又或者如果我們想修改結(jié)構(gòu)體類(lèi)型里面的指針類(lèi)型字段,傳遞結(jié)構(gòu)體的拷貝也能實(shí)現(xiàn)。

1. 通過(guò)傳遞指針修改變量的值

對(duì)于一些基礎(chǔ)類(lèi)型的變量,如果我們想修改其內(nèi)容,就要傳遞指針。這是因?yàn)樵?go 里面參數(shù)傳遞都是值傳遞,如果我們不傳指針, 那么在函數(shù)內(nèi)部拿到的只是參數(shù)的拷貝,對(duì)其進(jìn)行修改,不會(huì)影響到外部的變量(事實(shí)上在對(duì)這種反射值進(jìn)行修改的時(shí)候會(huì)直接 panic)。

傳值無(wú)法修改變量本身

x := 1
v := reflect.ValueOf(x)

在這個(gè)例子中,v 中保存的是 x 的拷貝,對(duì)這份拷貝在反射的層面上做修改其實(shí)是沒(méi)有實(shí)際意義的,因?yàn)閷?duì)拷貝進(jìn)行修改并不會(huì)影響到 x 本身。 我們?cè)谕ㄟ^(guò)反射來(lái)修改變量的時(shí)候,我們的預(yù)期行為往往是修改變量本身。鑒于實(shí)際的使用場(chǎng)景,go 的反射系統(tǒng)已經(jīng)幫我們做了限制了, 在我們對(duì)拷貝類(lèi)型的反射對(duì)象進(jìn)行修改的時(shí)候,會(huì)直接 panic。

傳指針可以修改變量

x := 1
v := reflect.ValueOf(&x).Elem()

在這個(gè)例子中,我們傳遞了 x 的指針到 reflect.ValueOf 中,這樣一來(lái),v 指向的就是 x 本身了。 在這種情況下,我們對(duì) v 的修改就會(huì)影響到 x 本身。

2. 通過(guò)傳遞指針修改結(jié)構(gòu)體的字段

對(duì)于結(jié)構(gòu)體類(lèi)型,如果我們想修改其字段的值,也是要傳遞指針的。這是因?yàn)榻Y(jié)構(gòu)體類(lèi)型的字段是值類(lèi)型,如果我們不傳遞指針, reflect.ValueOf 拿到的也是一份拷貝,對(duì)其進(jìn)行修改并不會(huì)影響到結(jié)構(gòu)體本身。當(dāng)然,這種情況下,我們修改它的時(shí)候也會(huì) panic。

type person struct {
   Name string
   Age  int
}
p := person{
    Name: "foo",
    Age:  30,
}
// v 本質(zhì)上是指向 p 的指針
v := reflect.ValueOf(&p)
// v.CanSet() 為 false,v 是指針,指針本身是不能修改的
// v.Elem() 是 p 本身,是可以修改的
fmt.Println(v.Elem().FieldByName("Name").CanSet()) // true
fmt.Println(v.Elem().FieldByName("Age").CanSet())  // true

3. 結(jié)構(gòu)體:獲取指針接收值方法

對(duì)于結(jié)構(gòu)體而言,如果我們想通過(guò)反射來(lái)調(diào)用指針接收者方法,那么我們需要傳遞指針。

在開(kāi)始講解這一點(diǎn)之前,需要就以下內(nèi)容達(dá)成共識(shí):

type person struct {
}
func (p person) M1() {
}
func (p *person) M2() {
}
func TestPerson(t *testing.T) {
   p := person{}
   v1 := reflect.ValueOf(p)
   v2 := reflect.ValueOf(&p)
   assert.Equal(t, 1, v1.NumMethod())
   assert.Equal(t, 2, v2.NumMethod())
   // v1 和 v2 都有 M1 方法
   assert.True(t, v1.MethodByName("M1").IsValid())
   assert.True(t, v2.MethodByName("M1").IsValid())
   // v1 沒(méi)有 M2 方法
   // v2 有 M2 方法
   assert.False(t, v1.MethodByName("M2").IsValid())
   assert.True(t, v2.MethodByName("M2").IsValid())
}

在上面的代碼中,p 只有一個(gè)方法 M1,而 &p 有兩個(gè)方法 M1M2。 但是在實(shí)際使用中,我們使用 p 來(lái)調(diào)用 M2 也是可以的, p 之所以能調(diào)用 M2 是因?yàn)榫幾g器幫我們做了一些處理,將 p 轉(zhuǎn)換成了 &p,然后調(diào)用 M2。

但是在反射的時(shí)候,我們是無(wú)法做到這一點(diǎn)的,這個(gè)需要特別注意。如果我們想通過(guò)反射來(lái)調(diào)用指針接收者的方法,就需要傳遞指針。

4. 變量本身包含指向數(shù)據(jù)的指針

最好不要通過(guò)值的反射對(duì)象來(lái)修改值的數(shù)據(jù),就算有些類(lèi)型可以實(shí)現(xiàn)這種功能。

對(duì)于 chan、map、slice 這三種類(lèi)型,我們可以通過(guò) reflect.ValueOf 來(lái)獲取它們的值, 但是這個(gè)值本身包含了指向數(shù)據(jù)的指針,因此我們依然可以通過(guò)反射系統(tǒng)修改其數(shù)據(jù)。但是,我們最好不這么用,從規(guī)范的角度,這是一種錯(cuò)誤的操作。

通過(guò)值反射對(duì)象修改 chan、map 和 slice

在 go 中,chan、map、slice 這幾種數(shù)據(jù)結(jié)構(gòu)中,存儲(chǔ)數(shù)據(jù)都是通過(guò)一個(gè) unsafe.Pointer 類(lèi)型的變量來(lái)指向?qū)嶋H存儲(chǔ)數(shù)據(jù)的內(nèi)存。 這是因?yàn)?,這幾種類(lèi)型能夠存儲(chǔ)的元素個(gè)數(shù)都是不確定的,都需要根據(jù)我們指定的大小和存儲(chǔ)的元素類(lèi)型來(lái)進(jìn)行內(nèi)存分配。

正因如此,我們復(fù)制 chan、map、slice 的時(shí)候,雖然值被復(fù)制了一遍,但是存儲(chǔ)數(shù)據(jù)的指針也被復(fù)制了, 這樣我們依然可以通過(guò)拷貝的數(shù)據(jù)指針來(lái)修改其數(shù)據(jù),如下面的例子:

func TestPointer1(t *testing.T) {
   // 數(shù)組需要傳遞引用才能修改其元素
   arr := [3]int{1, 2, 3}
   v1 := reflect.ValueOf(&arr)
   v1.Elem().Index(1).SetInt(100)
   assert.Equal(t, 100, arr[1])
   // chan 傳值也可以修改其元素
   ch := make(chan int, 1)
   v2 := reflect.ValueOf(ch)
   v2.Send(reflect.ValueOf(10))
   assert.Equal(t, 10, <-ch)
   // map 傳值也可以修改其元素
   m := make(map[int]int)
   v3 := reflect.ValueOf(m)
   v3.SetMapIndex(reflect.ValueOf(1), reflect.ValueOf(10))
   assert.Equal(t, 10, m[1])
   // slice 傳值也可以修改其元素
   s := []int{1, 2, 3}
   v4 := reflect.ValueOf(s)
   v4.Index(1).SetInt(20)
   assert.Equal(t, 20, s[1])
}

slice 反射對(duì)象擴(kuò)容的影響

但是,我們需要注意的是,對(duì)于 mapslice 類(lèi)型,在其分配的內(nèi)存容納不下新的元素的時(shí)候,會(huì)進(jìn)行擴(kuò)容, 擴(kuò)容之后,保存數(shù)據(jù)字段的指針就指向了一片新的內(nèi)存了。 這意味著什么呢?這意味著,我們通過(guò) mapslice 的值創(chuàng)建的反射值對(duì)象中拿到的那份數(shù)據(jù)指針已經(jīng)跟舊的 mapslice 指向的內(nèi)存不一樣了。

說(shuō)明:在上圖中,我們?cè)诜瓷鋵?duì)象中往 slice 追加元素后,導(dǎo)致反射對(duì)象 slicearray 指針指向了一片新的內(nèi)存區(qū)域了, 這個(gè)時(shí)候我們?cè)賹?duì)反射對(duì)象進(jìn)行修改的時(shí)候,不會(huì)影響到原 slice。這也就是我們不能通過(guò) slicemap 的拷貝的反射對(duì)象來(lái)修改 slicemap 的原因。

示例代碼:

func TestPointer1(t *testing.T) {
   s := []int{1, 2, 3}
   v4 := reflect.ValueOf(s)
   v4.Index(1).SetInt(20)
   assert.Equal(t, 20, s[1])
   // 這里發(fā)生了擴(kuò)容
   // v5 的 array 跟 s 的 array 指向的是不同的內(nèi)存區(qū)域了。
   v5 := reflect.Append(v4, reflect.ValueOf(4))
   fmt.Println(s) // [1 20 3]
   fmt.Println(v5.Interface().([]int)) // [1 20 3 4]
   // 這里修改 v5 的時(shí)候影響不到 s 了
   v5.Index(1).SetInt(30)
   fmt.Println(s) // [1 20 3]
   fmt.Println(v5.Interface().([]int)) // [1 30 3 4]
}

說(shuō)明:在上面的代碼中,v5 實(shí)際上是 v4 擴(kuò)容后的切片,底層的 array 指針指向的是跟 s 不一樣的 array 了, 因此在我們修改 v5 的時(shí)候,會(huì)發(fā)現(xiàn)原來(lái)的 s 并沒(méi)有發(fā)生改變。

雖然通過(guò)值反射對(duì)象可以修改 slice 的數(shù)據(jù),但是如果通過(guò)反射對(duì)象 append 元素到 slice 的反射對(duì)象的時(shí)候, 可能會(huì)觸發(fā) slice 擴(kuò)容,這個(gè)時(shí)候再修改反射對(duì)象的時(shí)候,就影響不了原來(lái)的 slice 了。

slice 容量夠的話(huà)是不是就可以正常追加元素了?

只能說(shuō),能,也不能。我們看看下面這個(gè)例子:

func TestPointer000(t *testing.T) {
   s1 := make([]int, 3, 6)
   s1[0] = 1
   s1[1] = 2
   s1[2] = 3
   fmt.Println(s1) // [1 2 3]
   v6 := reflect.ValueOf(s1)
   v7 := reflect.Append(v6, reflect.ValueOf(4))
   // 雖然 s1 的容量足夠大,但是 s1 還是看不到追加的元素
   fmt.Println(s1)                     // [1 2 3]
   fmt.Println(v7.Interface().([]int)) // [1 2 3 4]
   // s1 和 s2 底層數(shù)組還是同一個(gè)
   // array1 是 s1 底層數(shù)組的內(nèi)存地址
   array1 := (*(*reflect.SliceHeader)(unsafe.Pointer(&s1))).Data
   s2 := v7.Interface().([]int)
    // array2 是 s2 底層數(shù)組的內(nèi)存地址
   array2 := (*(*reflect.SliceHeader)(unsafe.Pointer(&s2))).Data
   assert.Equal(t, array1, array2)
   // 這是因?yàn)?s1 的長(zhǎng)度并沒(méi)有發(fā)生改變,
   // 所以 s1 看不到追加的那個(gè)元素
   fmt.Println(len(s1), cap(s1)) // 3 6
   fmt.Println(len(s2), cap(s2)) // 4 6
}

在這個(gè)例子中,我們給 slice 分配了足夠大的容量,但是我們通過(guò)反射對(duì)象來(lái)追加元素的時(shí)候, 雖然數(shù)據(jù)被正常追加到了 s1 底層數(shù)組,但是由于在反射對(duì)象以外的 s1len 并沒(méi)有發(fā)生改變, 因此 s1 還是看不到反射對(duì)象追加的元素。所以上面說(shuō)可以正常追加元素。

但是,外部由于 len 沒(méi)有發(fā)生改變,因此外部看不到反射對(duì)象追加的元素,所以上面也說(shuō)不能正常追加元素。

因此,雖然理論上修改的是同一片內(nèi)存,我們依然不能通過(guò)傳值的方式來(lái)通過(guò)反射對(duì)象往 slice 中追加元素。 但是修改 [0, len(s)) 范圍內(nèi)的元素在反射對(duì)象外部是可以看到的。

map 也不能通過(guò)值反射對(duì)象來(lái)修改其元素。

slice 類(lèi)似,通過(guò) map 的值反射對(duì)象來(lái)追加元素的時(shí)候,同樣可能導(dǎo)致擴(kuò)容, 擴(kuò)容之后,保存數(shù)據(jù)的內(nèi)存區(qū)域會(huì)發(fā)生改變。

但是,從另一個(gè)角度看,如果我們只是修改其元素的話(huà),是可以正常修改的。

chan 沒(méi)有追加

chanslice、map 有個(gè)不一樣的地方,它的長(zhǎng)度是我們創(chuàng)建 chan 的時(shí)候就已經(jīng)固定的了, 因此,不存在擴(kuò)容導(dǎo)致指向內(nèi)存區(qū)域發(fā)生改變的問(wèn)題。

因此,對(duì)于 chan 類(lèi)型的元素,我們傳 ch 或者 &chreflect.ValueOf 都可以實(shí)現(xiàn)修改 ch。

結(jié)構(gòu)體字段包含指針的情況

如果結(jié)構(gòu)體里面包含了指針字段,我們也只是想通過(guò)反射對(duì)象來(lái)修改這個(gè)指針字段的話(huà), 那么我們也還是可以通過(guò)傳值給 reflect.ValueOf 來(lái)創(chuàng)建反射對(duì)象來(lái)修改這個(gè)指針字段:

type person struct {
   Name *string
}
func TestPointerPerson(t *testing.T) {
   name := "foo"
   p := person{Name: &name}
   v := reflect.ValueOf(p)
   fmt.Println(v.Field(0).Elem().CanAddr())
   fmt.Println(v.Field(0).Elem().CanSet())
   name1 := "bar"
   v.Field(0).Elem().Set(reflect.ValueOf(name1))
   // p 的 Name 字段已經(jīng)被成功修改
   fmt.Println(*p.Name)
}

在這個(gè)例子中,我們雖然使用了 p 而不是 &p 來(lái)創(chuàng)建反射對(duì)象, 但是我們依然可以修改 Name 字段,因?yàn)榉瓷鋵?duì)象拿到了 Name 的指針的拷貝, 通過(guò)這個(gè)拷貝是可以定位到 pName 字段本身指向的內(nèi)存的。

但是我們依然是不能修改 p 中的其他字段。

5. interface 類(lèi)型處理

對(duì)于 interface 類(lèi)型的元素,我們可以將以下兩種操作看作是等價(jià)的:

// v1 跟 v2 都拿到了 a 的拷貝
var a = 1
v1 := reflect.ValueOf(a)
var b interface{} = a
v2 := reflect.ValueOf(b)

我們可以通過(guò)下面的斷言來(lái)證明:

assert.Equal(t, v1.Kind(), v2.Kind())
assert.Equal(t, v1.CanAddr(), v2.CanAddr())
assert.Equal(t, v1.CanSet(), v2.CanSet())
assert.Equal(t, v1.Interface(), v2.Interface())

當(dāng)然,對(duì)于指針類(lèi)型也是一樣的:

// v1 跟 v2 都拿到了 a 的指針
var a = 1
v1 := reflect.ValueOf(&a)
var b interface{} = &a
v2 := reflect.ValueOf(b)

同樣的,我們可以通過(guò)下面的斷言來(lái)證明:

assert.Equal(t, v1.Kind(), v2.Kind())
assert.Equal(t, v1.Elem().Kind(), v2.Elem().Kind())
assert.Equal(t, v1.Elem().CanAddr(), v2.Elem().CanAddr())
assert.Equal(t, v1.Elem().Addr(), v2.Elem().Addr())
assert.Equal(t, v1.Interface(), v2.Interface())
assert.Equal(t, v1.Elem().Interface(), v2.Elem().Interface())

interface 底層類(lèi)型是值

interface 類(lèi)型的底層類(lèi)型是值的時(shí)候,我們將其傳給 reflect.ValueOf 跟直接傳值是一樣的。 是沒(méi)有辦法修改 interface 底層數(shù)據(jù)的值的(除了指針類(lèi)型字段,因?yàn)榉瓷鋵?duì)象也拿到了指針字段的地址):

type person struct {
    Name *string
}
func TestInterface1(t *testing.T) {
   name := "foo"
   p := person{Name: &name}
   // v 拿到的是 p 的拷貝
    // 下面兩行等價(jià)于 v := reflect.ValueOf(p)
   var i interface{} = p
   v := reflect.ValueOf(i)
   assert.False(t, v.CanAddr())
   assert.Equal(t, reflect.Struct, v.Kind())
   assert.True(t, v.Field(0).Elem().CanAddr())
}

在上面這個(gè)例子中 v := reflect.ValueOf(i) 其實(shí)等價(jià)于 v := reflect.ValueOf(p), 因?yàn)樵谖覀冋{(diào)用 reflect.ValueOf(p) 的時(shí)候,go 語(yǔ)言本身會(huì)幫我們將 p 轉(zhuǎn)換為 interface{} 類(lèi)型。 在我們賦值給 i 的時(shí)候,go 語(yǔ)言也會(huì)幫我們將 p 轉(zhuǎn)換為 interface{} 類(lèi)型。 這樣再調(diào)用 reflect.ValueOf 的時(shí)候就不需要再做轉(zhuǎn)換了。

interface 底層類(lèi)型是指針

傳遞底層數(shù)據(jù)是指針類(lèi)型的 interfacereflect.ValueOf 的時(shí)候,我們可以修改 interface 底層指針指向的值, 效果等同于直接傳遞指針給 reflect.ValueOf

func TestInterface(t *testing.T) {
   var a = 1
   v1 := reflect.ValueOf(&a)
   var b interface{} = &a
   v2 := reflect.ValueOf(b)
   // v1 和 v2 本質(zhì)上都接收了一個(gè) interface 參數(shù),
   // 這個(gè) interface 參數(shù)的數(shù)據(jù)部分都是 &a
   v1.Elem().SetInt(10)
   assert.Equal(t, 10, a)
   // 通過(guò) v1 修改 a 的值,v2 也能看到
   assert.Equal(t, 10, v2.Elem().Interface())
   // 同樣的,通過(guò) v2 修改 a 的值,v1 也能看到
   v2.Elem().SetInt(20)
   assert.Equal(t, 20, a)
   assert.Equal(t, 20, v1.Elem().Interface())
}

不要再對(duì)接口類(lèi)型取地址

能不能通過(guò)反射 Value 對(duì)象來(lái)修改變量只取決于,能不能根據(jù)反射對(duì)象拿到最初變量的內(nèi)存地址。 如果拿到的只是原始值的拷貝,不管我們?cè)趺醋龆紵o(wú)法修改原始值。

對(duì)于初學(xué)者另外一個(gè)令人困惑的地方可能是下面這樣的代碼:

func TestInterface(t *testing.T) {
   var a = 1
   var i interface{} = a
   v1 := reflect.ValueOf(&a)
   v2 := reflect.ValueOf(&i)
   // v1 和 v2 的類(lèi)型都是 reflect.Ptr
   assert.Equal(t, reflect.Ptr, v1.Kind())
   assert.Equal(t, reflect.Ptr, v2.Kind())
   // 但是兩者的 Elem() 類(lèi)型不同,
   // v1 的 Elem() 是 reflect.Int,
   // v2 的 Elem() 是 reflect.Interface
   assert.Equal(t, reflect.Int, v1.Elem().Kind())
   assert.Equal(t, reflect.Interface, v2.Elem().Kind())
}

困惑的源頭在于,reflect.ValueOf() 這個(gè)函數(shù)的參數(shù)是 interface{} 類(lèi)型的, 這意味著我們可以傳遞任意類(lèi)型的值給它,包括指針類(lèi)型的值。

正因如此,如果我們不懂得 reflect 包的工作原理的話(huà), 就會(huì)傳錯(cuò)變量到 reflect.ValueOf() 函數(shù)中,導(dǎo)致程序出錯(cuò)。

對(duì)于上面例子的 v2,它是一個(gè)指向 interface{} 類(lèi)型的指針的反射對(duì)象,它也能找到最初的變量 a

但是能不能修改 a,還是取決于 a 是否是可尋址的。也就是最初傳遞給 i 的值是不是一個(gè)指針類(lèi)型。

assert.Equal(t, "<*interface {} Value>", v2.String())
assert.Equal(t, "<interface {} Value>", v2.Elem().String())
assert.Equal(t, "<int Value>", v2.Elem().Elem().String())

在上面的例子中,我們傳遞給 i 的是 a 的值,而不是 a 的指針,所以 i 是不可尋址的,也就是說(shuō) v2 是不可尋址的。

上圖說(shuō)明:

  • i 是接口類(lèi)型,它的數(shù)據(jù)部分是 a 的拷貝,它的類(lèi)型部分是 int 類(lèi)型。
  • &i 是指向接口的指針,它指向了上圖的 eface
  • v2 是指向 eface 的指針的反射對(duì)象。
  • 最終,我們通過(guò) v2 找到 i 這個(gè)接口,然后通過(guò) i 找到 a 這個(gè)變量的拷貝。

所以,繞了一大圈,我們最終還是修改不了 a 的值。到最后我們只拿到了 a 的拷貝。

6. 指針類(lèi)型反射對(duì)象不可修改其指向地址

其實(shí)這一點(diǎn)上面有些地方也有涉及到,但是這里再?gòu)?qiáng)調(diào)一下。一個(gè)例子如下:

func TestPointer(t *testing.T) {
   var a = 1
   var b = &a
   v := reflect.ValueOf(b)
   assert.False(t, v.CanAddr())
   assert.False(t, v.CanSet())
   assert.True(t, v.Elem().CanAddr())
   assert.True(t, v.Elem().CanSet())
}

說(shuō)明:

  • v 是指向 &a 的指針的反射對(duì)象。
  • 通過(guò)這個(gè)反射對(duì)象的 Elem() 方法,我們可以找到原始的變量 a
  • 反射對(duì)象本身不能修改,但是它的 Elem() 方法返回的反射對(duì)象可以修改。

對(duì)于指針類(lèi)型的反射對(duì)象,其本身不能修改,但是它的 Elem() 方法返回的反射對(duì)象可以修改。

7. 反射也不能修改字符串中的字符

這是因?yàn)?,go 中的字符串本身是不可變的,我們無(wú)法像在 C 語(yǔ)言中那樣修改其中某一個(gè)字符。 其實(shí)不止是 go,其實(shí)很多編程語(yǔ)言的字符串都是不可變的,比如 Java 中的 String 類(lèi)型。

在 go 中,字符串是用一個(gè)結(jié)構(gòu)體來(lái)表示的,大概長(zhǎng)下面這個(gè)樣子:

type StringHeader struct {
   Data uintptr
   Len  int
}
  • Data 是指向字符串的指針。
  • Len 是字符串的長(zhǎng)度(單位為字節(jié))。

在 go 中 str[1] = 'a' 這樣的操作是不允許的,因?yàn)樽址遣豢勺兊摹?/p>

相同的字符串只有一個(gè)實(shí)例

假設(shè)我們定義了兩個(gè)相同的字符串,如下:

s1 := "hello"
s2 := "hello"

這兩個(gè)字符串的值是相同的,但是它們的地址是不同的。那既然如此,為什么我們還是不能修改它的其中某一個(gè)字符呢? 這是因?yàn)?,雖然 s1s2 的地址不一樣,但是它們實(shí)際保存 hello 這個(gè)字符串的地址是一樣的:

v1 := (*reflect.StringHeader)(unsafe.Pointer(&s1))
v2 := (*reflect.StringHeader)(unsafe.Pointer(&s2))
// 兩個(gè)字符串實(shí)例保存字符串的內(nèi)存地址是一樣的
assert.Equal(t, v1.Data, v2.Data)

兩個(gè)字符串內(nèi)存表示如下:

所以,我們可以看到,s1s2 實(shí)際上是指向同一個(gè)字符串的指針,所以我們無(wú)法修改其中某一個(gè)字符。 因?yàn)槿绻试S這種行為存在的話(huà),我們對(duì)其中一個(gè)字符串實(shí)例修改,也會(huì)影響到另外一個(gè)字符串實(shí)例。

字符串本身可以替換

雖然我們不能修改字符串中的某一個(gè)字符,但是我們可以通過(guò)反射對(duì)象把整個(gè)字符串替換掉:

func TestStirng(t *testing.T) {
   s := "hello"
   v := reflect.ValueOf(&s)
   fmt.Println(v.Elem().CanAddr())
   fmt.Println(v.Elem().CanSet())
   v.Elem().SetString("world")
   fmt.Println(s) // world
}

這里實(shí)際上是把 s 中保存字符串的地址替換成了指向 world 這個(gè)字符串的地址,而不是將 hello 指向的內(nèi)存修改成 world

func TestStirng(t *testing.T) {
   s := "hello"
   oldAddr := (*reflect.StringHeader)(unsafe.Pointer(&s)).Data
   v := reflect.ValueOf(&s)
   v.Elem().SetString("world")
   newAddr := (*reflect.StringHeader)(unsafe.Pointer(&s)).Data
   // 修改之后,實(shí)際保存字符串的內(nèi)存地址發(fā)生了改變
   assert.NotEqual(t, oldAddr, newAddr)
}

這可以用下圖表示:

總結(jié)

  • 如果我們需要通過(guò)反射對(duì)象來(lái)修改變量的值,那么我們必須得有辦法拿到變量實(shí)際存儲(chǔ)的內(nèi)存地址。這種情況下,很多時(shí)候都是通過(guò)傳遞指針給 reflect.ValueOf() 方法來(lái)實(shí)現(xiàn)的。
  • 但是對(duì)于 chan、mapslice 或者其他類(lèi)似的數(shù)據(jù)結(jié)構(gòu),它們通過(guò)指針來(lái)引用實(shí)際存儲(chǔ)數(shù)據(jù)的內(nèi)存,這種數(shù)據(jù)結(jié)構(gòu)是通過(guò)通過(guò)傳值給 reflect.ValueOf() 方法來(lái)實(shí)現(xiàn)修改其中的元素的。因?yàn)檫@些數(shù)據(jù)結(jié)構(gòu)的數(shù)據(jù)部分可以通過(guò)指針的拷貝來(lái)修改。
  • 但是 mapslice 有可能會(huì)擴(kuò)容,如果通過(guò)反射對(duì)象來(lái)追加元素,可能導(dǎo)致追加失敗。這是因?yàn)椋ㄟ^(guò)反射對(duì)象追加元素的時(shí)候,如果擴(kuò)容了,那么原來(lái)的內(nèi)存地址就會(huì)失效,這樣我們其實(shí)就修改不了原來(lái)的 mapslice 了。
  • 同樣的,結(jié)構(gòu)體傳值來(lái)創(chuàng)建反射對(duì)象的時(shí)候,如果其中有指針類(lèi)型的字段,那么我們也可以通過(guò)指針來(lái)修改其中的元素。但是其他字段也還是修改不了的。
  • 如果我們創(chuàng)建反射對(duì)象的參數(shù)是 interface 類(lèi)型,那么能不能修改元素的變量還是取決于我們這個(gè) interface 類(lèi)型變量的數(shù)據(jù)部分是值還是指針。如果 interface 變量中存儲(chǔ)的是值,那么我們就不能修改其中的元素了。如果 interface 變量中存儲(chǔ)的是指針,就可以修改。
  • 我們無(wú)法修改字符串的某一個(gè)字符,通過(guò)反射也不能,因?yàn)樽址旧硎遣豢勺兊?。不同?stirng 類(lèi)型的變量,如果它們的值是一樣的,那么它們會(huì)共享實(shí)際存儲(chǔ)字符串的內(nèi)存。
  • 但是我們可以直接用一個(gè)新的字符串替代舊的字符串。

但其實(shí)說(shuō)了那么多,簡(jiǎn)單來(lái)說(shuō)只有一點(diǎn),就是我們只能通過(guò)反射對(duì)象來(lái)修改指針類(lèi)型的變量。如果拿不到實(shí)際存儲(chǔ)數(shù)據(jù)的指針,那么我們就無(wú)法通過(guò)反射對(duì)象來(lái)修改其中的元素了。

以上就是go reflect要不要傳指針原理詳解的詳細(xì)內(nèi)容,更多關(guān)于go reflect 傳指針的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Golang 1.18新特性模糊測(cè)試用法詳解

    Golang 1.18新特性模糊測(cè)試用法詳解

    模糊測(cè)試是一種軟件測(cè)試技術(shù)。其核心思想是將自動(dòng)或半自動(dòng)生成的隨機(jī)數(shù)據(jù)輸入到一個(gè)程序中,并監(jiān)視程序異常,如崩潰,斷言失敗,以發(fā)現(xiàn)可能的程序錯(cuò)誤,比如內(nèi)存泄漏,本文給大家介紹了Golang 1.18 新特性模糊測(cè)試,感興趣的同學(xué)可以參考閱讀下
    2023-05-05
  • Go函數(shù)的使用示例教程

    Go函數(shù)的使用示例教程

    這篇文章主要介紹了Go函數(shù)的使用示例,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧
    2024-07-07
  • 詳解Go語(yǔ)言如何實(shí)現(xiàn)字符串切片反轉(zhuǎn)函數(shù)

    詳解Go語(yǔ)言如何實(shí)現(xiàn)字符串切片反轉(zhuǎn)函數(shù)

    Go?語(yǔ)言不像其他語(yǔ)言如?Python,有著內(nèi)置的?reverse()?函數(shù),本文將先學(xué)習(xí)一下Python中對(duì)于列表的反轉(zhuǎn)方法,然后再學(xué)習(xí)如果在Go語(yǔ)言中實(shí)現(xiàn)相同的功能,感興趣的小伙伴快跟隨小編一起來(lái)學(xué)習(xí)一下
    2022-10-10
  • Go語(yǔ)言測(cè)試庫(kù)testify使用學(xué)習(xí)

    Go語(yǔ)言測(cè)試庫(kù)testify使用學(xué)習(xí)

    這篇文章主要為大家介紹了Go語(yǔ)言測(cè)試庫(kù)testify的使用學(xué)習(xí)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-07-07
  • 關(guān)于go平滑重啟庫(kù)overseer實(shí)現(xiàn)原理詳解

    關(guān)于go平滑重啟庫(kù)overseer實(shí)現(xiàn)原理詳解

    這篇文章主要為大家詳細(xì)介紹了關(guān)于go平滑重啟庫(kù)overseer實(shí)現(xiàn)原理,文中的示例代碼講解詳細(xì),具有一定的參考價(jià)值,有需要的小伙伴可以參考下
    2023-11-11
  • PHP結(jié)構(gòu)型模式之組合模式

    PHP結(jié)構(gòu)型模式之組合模式

    這篇文章主要介紹了PHP組合模式Composite Pattern優(yōu)點(diǎn)與實(shí)現(xiàn),組合模式是一種結(jié)構(gòu)型模式,它允許你將對(duì)象組合成樹(shù)形結(jié)構(gòu)來(lái)表示“部分-整體”的層次關(guān)系。組合能讓客戶(hù)端以一致的方式處理個(gè)別對(duì)象和對(duì)象組合
    2023-04-04
  • golang?chan傳遞數(shù)據(jù)的性能開(kāi)銷(xiāo)詳解

    golang?chan傳遞數(shù)據(jù)的性能開(kāi)銷(xiāo)詳解

    這篇文章主要為大家詳細(xì)介紹了Golang中chan在接收和發(fā)送數(shù)據(jù)時(shí)因?yàn)椤皬?fù)制”而產(chǎn)生的開(kāi)銷(xiāo),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解下
    2024-01-01
  • Go 實(shí)現(xiàn)一次性打包各個(gè)平臺(tái)的可執(zhí)行程序

    Go 實(shí)現(xiàn)一次性打包各個(gè)平臺(tái)的可執(zhí)行程序

    這篇文章主要介紹了Go 實(shí)現(xiàn)一次性打包各個(gè)平臺(tái)的可執(zhí)行程序,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-12-12
  • go?singleflight緩存雪崩源碼分析與應(yīng)用

    go?singleflight緩存雪崩源碼分析與應(yīng)用

    這篇文章主要為大家介紹了go?singleflight緩存雪崩源碼分析與應(yīng)用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-09-09
  • Golang slice切片操作之切片的追加、刪除、插入等

    Golang slice切片操作之切片的追加、刪除、插入等

    這篇文章主要介紹了Golang slice切片操作之切片的追加、刪除、插入等,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-11-11

最新評(píng)論