go reflect要不要传指针原理详解

目录
  • 正文
  • 什么时候传递指针?
  • 1. 通过传递指针修改变量的值
    • 传值无法修改变量本身
    • 传指针可以修改变量
  • 2. 通过传递指针修改结构体的字段
  • 3. 结构体:获取指针接收值方法
  • 4. 变量本身包含指向数据的指针
    • 通过值反射对象修改 chan、map 和 slice
    • slice 反射对象扩容的影响
    • slice 容量够的话是不是就可以正常追加元素了?
    • map 也不能通过值反射对象来修改其元素。
    • chan 没有追加
    • 结构体字段包含指针的情况
  • 5. interface 类型处理
    • interface 底层类型是值
    • interface 底层类型是指针
    • 不要再对接口类型取地址
  • 6. 指针类型反射对象不可修改其指向地址
  • 7. 反射也不能修改字符串中的字符
    • 相同的字符串只有一个实例
    • 字符串本身可以替换
  • 总结

正文

在我们看一些使用反射的代码的时候,会发现,reflect.ValueOfreflect.TypeOf 的参数有些地方使用的是指针参数,有些地方又不是指针参数, 但是好像这两者在使用上没什么区别,比如下面这样:

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

它们的区别貌似只是需不需要使用 Elem() 方法,但这个跟我们是否传递指针给 reflect.ValueOf 其实关系不大, 相信没有人为了使用一下 Elem() 方法,就去传递指针给 reflect.ValueOf 吧。

那我们什么时候应该传递指针参数呢?

什么时候传递指针?

要回答这个问题,我们可以思考一下以下列出的几点内容:

  • 是否要修改变量的值,要修改就要用指针
  • 结构体类型:是否要修改结构体里的字段,要修改就要用指针
  • 结构体类型:是否要调用指针接收值方法,要调用就要用指针
  • 对于 chanmapslice 类型,我们传递值和传递指针都可以修改其内容
  • 对于非 interface 类型,传递给 TypeOfValueOf 的时候都会转换为 interface 类型,如果本身就是 interface 类型,则不需转换。
  • 指针类型不可修改,但是可以修改指针指向的值。(v := reflect.ValueOf(&a)v.CanSet()falsev.Elem().CanSet()true
  • 字符串:我们可以对字符串进行替换,但不能修改字符串的某一个字符

大概总结下来,就是:如果我们想修改变量的内容,就传递指针,否则就传递值。对于某些复合类型如果其内部包含了底层数据的指针, 也是可以通过传值来修改其底层数据的,这些类型有 chanmapslice。 又或者如果我们想修改结构体类型里面的指针类型字段,传递结构体的拷贝也能实现。

1. 通过传递指针修改变量的值

对于一些基础类型的变量,如果我们想修改其内容,就要传递指针。这是因为在 go 里面参数传递都是值传递,如果我们不传指针, 那么在函数内部拿到的只是参数的拷贝,对其进行修改,不会影响到外部的变量(事实上在对这种反射值进行修改的时候会直接 panic)。

传值无法修改变量本身

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

在这个例子中,v 中保存的是 x 的拷贝,对这份拷贝在反射的层面上做修改其实是没有实际意义的,因为对拷贝进行修改并不会影响到 x 本身。 我们在通过反射来修改变量的时候,我们的预期行为往往是修改变量本身。鉴于实际的使用场景,go 的反射系统已经帮我们做了限制了, 在我们对拷贝类型的反射对象进行修改的时候,会直接 panic

传指针可以修改变量

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

在这个例子中,我们传递了 x 的指针到 reflect.ValueOf 中,这样一来,v 指向的就是 x 本身了。 在这种情况下,我们对 v 的修改就会影响到 x 本身。

2. 通过传递指针修改结构体的字段

对于结构体类型,如果我们想修改其字段的值,也是要传递指针的。这是因为结构体类型的字段是值类型,如果我们不传递指针, reflect.ValueOf 拿到的也是一份拷贝,对其进行修改并不会影响到结构体本身。当然,这种情况下,我们修改它的时候也会 panic

type person struct {
   Name string
   Age  int
}
p := person{
    Name: "foo",
    Age:  30,
}
// v 本质上是指向 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. 结构体:获取指针接收值方法

对于结构体而言,如果我们想通过反射来调用指针接收者方法,那么我们需要传递指针。

在开始讲解这一点之前,需要就以下内容达成共识:

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 没有 M2 方法
   // v2 有 M2 方法
   assert.False(t, v1.MethodByName("M2").IsValid())
   assert.True(t, v2.MethodByName("M2").IsValid())
}

在上面的代码中,p 只有一个方法 M1,而 &p 有两个方法 M1M2但是在实际使用中,我们使用 p 来调用 M2 也是可以的p 之所以能调用 M2 是因为编译器帮我们做了一些处理,将 p 转换成了 &p,然后调用 M2

但是在反射的时候,我们是无法做到这一点的,这个需要特别注意。如果我们想通过反射来调用指针接收者的方法,就需要传递指针。

4. 变量本身包含指向数据的指针

最好不要通过值的反射对象来修改值的数据,就算有些类型可以实现这种功能。

对于 chanmapslice 这三种类型,我们可以通过 reflect.ValueOf 来获取它们的值, 但是这个值本身包含了指向数据的指针,因此我们依然可以通过反射系统修改其数据。但是,我们最好不这么用,从规范的角度,这是一种错误的操作。

通过值反射对象修改 chan、map 和 slice

在 go 中,chanmapslice 这几种数据结构中,存储数据都是通过一个 unsafe.Pointer 类型的变量来指向实际存储数据的内存。 这是因为,这几种类型能够存储的元素个数都是不确定的,都需要根据我们指定的大小和存储的元素类型来进行内存分配。

正因如此,我们复制 chanmapslice 的时候,虽然值被复制了一遍,但是存储数据的指针也被复制了, 这样我们依然可以通过拷贝的数据指针来修改其数据,如下面的例子:

func TestPointer1(t *testing.T) {
   // 数组需要传递引用才能修改其元素
   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 反射对象扩容的影响

但是,我们需要注意的是,对于 mapslice 类型,在其分配的内存容纳不下新的元素的时候,会进行扩容扩容之后,保存数据字段的指针就指向了一片新的内存了。 这意味着什么呢?这意味着,我们通过 mapslice 的值创建的反射值对象中拿到的那份数据指针已经跟旧的 mapslice 指向的内存不一样了。

说明:在上图中,我们在反射对象中往 slice 追加元素后,导致反射对象 slicearray 指针指向了一片新的内存区域了, 这个时候我们再对反射对象进行修改的时候,不会影响到原 slice。这也就是我们不能通过 slicemap 的拷贝的反射对象来修改 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])
   // 这里发生了扩容
   // v5 的 array 跟 s 的 array 指向的是不同的内存区域了。
   v5 := reflect.Append(v4, reflect.ValueOf(4))
   fmt.Println(s) // [1 20 3]
   fmt.Println(v5.Interface().([]int)) // [1 20 3 4]
   // 这里修改 v5 的时候影响不到 s 了
   v5.Index(1).SetInt(30)
   fmt.Println(s) // [1 20 3]
   fmt.Println(v5.Interface().([]int)) // [1 30 3 4]
}

说明:在上面的代码中,v5 实际上是 v4 扩容后的切片,底层的 array 指针指向的是跟 s 不一样的 array 了, 因此在我们修改 v5 的时候,会发现原来的 s 并没有发生改变。

虽然通过值反射对象可以修改 slice 的数据,但是如果通过反射对象 append 元素到 slice 的反射对象的时候, 可能会触发 slice 扩容,这个时候再修改反射对象的时候,就影响不了原来的 slice 了。

slice 容量够的话是不是就可以正常追加元素了?

只能说,能,也不能。我们看看下面这个例子:

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 底层数组还是同一个
   // array1 是 s1 底层数组的内存地址
   array1 := (*(*reflect.SliceHeader)(unsafe.Pointer(&s1))).Data
   s2 := v7.Interface().([]int)
    // array2 是 s2 底层数组的内存地址
   array2 := (*(*reflect.SliceHeader)(unsafe.Pointer(&s2))).Data
   assert.Equal(t, array1, array2)
   // 这是因为 s1 的长度并没有发生改变,
   // 所以 s1 看不到追加的那个元素
   fmt.Println(len(s1), cap(s1)) // 3 6
   fmt.Println(len(s2), cap(s2)) // 4 6
}

在这个例子中,我们给 slice 分配了足够大的容量,但是我们通过反射对象来追加元素的时候, 虽然数据被正常追加到了 s1 底层数组,但是由于在反射对象以外的 s1len 并没有发生改变, 因此 s1 还是看不到反射对象追加的元素。所以上面说可以正常追加元素

但是,外部由于 len 没有发生改变,因此外部看不到反射对象追加的元素,所以上面也说不能正常追加元素

因此,虽然理论上修改的是同一片内存,我们依然不能通过传值的方式来通过反射对象往 slice 中追加元素。 但是修改 [0, len(s)) 范围内的元素在反射对象外部是可以看到的。

map 也不能通过值反射对象来修改其元素。

slice 类似,通过 map 的值反射对象来追加元素的时候,同样可能导致扩容, 扩容之后,保存数据的内存区域会发生改变。

但是,从另一个角度看,如果我们只是修改其元素的话,是可以正常修改的。

chan 没有追加

chanslicemap 有个不一样的地方,它的长度是我们创建 chan 的时候就已经固定的了, 因此,不存在扩容导致指向内存区域发生改变的问题。

因此,对于 chan 类型的元素,我们传 ch 或者 &chreflect.ValueOf 都可以实现修改 ch

结构体字段包含指针的情况

如果结构体里面包含了指针字段,我们也只是想通过反射对象来修改这个指针字段的话, 那么我们也还是可以通过传值给 reflect.ValueOf 来创建反射对象来修改这个指针字段:

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 字段已经被成功修改
   fmt.Println(*p.Name)
}

在这个例子中,我们虽然使用了 p 而不是 &p 来创建反射对象, 但是我们依然可以修改 Name 字段,因为反射对象拿到了 Name 的指针的拷贝, 通过这个拷贝是可以定位到 pName 字段本身指向的内存的。

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

5. interface 类型处理

对于 interface 类型的元素,我们可以将以下两种操作看作是等价的:

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

我们可以通过下面的断言来证明:

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

当然,对于指针类型也是一样的:

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

同样的,我们可以通过下面的断言来证明:

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 底层类型是值

interface 类型的底层类型是值的时候,我们将其传给 reflect.ValueOf 跟直接传值是一样的。 是没有办法修改 interface 底层数据的值的(除了指针类型字段,因为反射对象也拿到了指针字段的地址):

type person struct {
    Name *string
}
func TestInterface1(t *testing.T) {
   name := "foo"
   p := person{Name: &name}
   // v 拿到的是 p 的拷贝
    // 下面两行等价于 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())
}

在上面这个例子中 v := reflect.ValueOf(i) 其实等价于 v := reflect.ValueOf(p), 因为在我们调用 reflect.ValueOf(p) 的时候,go 语言本身会帮我们将 p 转换为 interface{} 类型。 在我们赋值给 i 的时候,go 语言也会帮我们将 p 转换为 interface{} 类型。 这样再调用 reflect.ValueOf 的时候就不需要再做转换了。

interface 底层类型是指针

传递底层数据是指针类型的 interfacereflect.ValueOf 的时候,我们可以修改 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 本质上都接收了一个 interface 参数,
   // 这个 interface 参数的数据部分都是 &a
   v1.Elem().SetInt(10)
   assert.Equal(t, 10, a)
   // 通过 v1 修改 a 的值,v2 也能看到
   assert.Equal(t, 10, v2.Elem().Interface())
   // 同样的,通过 v2 修改 a 的值,v1 也能看到
   v2.Elem().SetInt(20)
   assert.Equal(t, 20, a)
   assert.Equal(t, 20, v1.Elem().Interface())
}

不要再对接口类型取地址

能不能通过反射 Value 对象来修改变量只取决于,能不能根据反射对象拿到最初变量的内存地址。 如果拿到的只是原始值的拷贝,不管我们怎么做都无法修改原始值。

对于初学者另外一个令人困惑的地方可能是下面这样的代码:

func TestInterface(t *testing.T) {
   var a = 1
   var i interface{} = a
   v1 := reflect.ValueOf(&a)
   v2 := reflect.ValueOf(&i)
   // v1 和 v2 的类型都是 reflect.Ptr
   assert.Equal(t, reflect.Ptr, v1.Kind())
   assert.Equal(t, reflect.Ptr, v2.Kind())
   // 但是两者的 Elem() 类型不同,
   // 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() 这个函数的参数是 interface{} 类型的, 这意味着我们可以传递任意类型的值给它,包括指针类型的值。

正因如此,如果我们不懂得 reflect 包的工作原理的话, 就会传错变量到 reflect.ValueOf() 函数中,导致程序出错。

对于上面例子的 v2,它是一个指向 interface{} 类型的指针的反射对象,它也能找到最初的变量 a

但是能不能修改 a,还是取决于 a 是否是可寻址的。也就是最初传递给 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 是不可寻址的,也就是说 v2 是不可寻址的。

上图说明:

  • i 是接口类型,它的数据部分是 a 的拷贝,它的类型部分是 int 类型。
  • &i 是指向接口的指针,它指向了上图的 eface
  • v2 是指向 eface 的指针的反射对象。
  • 最终,我们通过 v2 找到 i 这个接口,然后通过 i 找到 a 这个变量的拷贝

所以,绕了一大圈,我们最终还是修改不了 a 的值。到最后我们只拿到了 a 的拷贝。

6. 指针类型反射对象不可修改其指向地址

其实这一点上面有些地方也有涉及到,但是这里再强调一下。一个例子如下:

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

说明:

  • v 是指向 &a 的指针的反射对象。
  • 通过这个反射对象的 Elem() 方法,我们可以找到原始的变量 a
  • 反射对象本身不能修改,但是它的 Elem() 方法返回的反射对象可以修改。

对于指针类型的反射对象,其本身不能修改,但是它的 Elem() 方法返回的反射对象可以修改。

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

这是因为,go 中的字符串本身是不可变的,我们无法像在 C 语言中那样修改其中某一个字符。 其实不止是 go,其实很多编程语言的字符串都是不可变的,比如 Java 中的 String 类型。

在 go 中,字符串是用一个结构体来表示的,大概长下面这个样子:

type StringHeader struct {
   Data uintptr
   Len  int
}
  • Data 是指向字符串的指针。
  • Len 是字符串的长度(单位为字节)。

在 go 中 str[1] = 'a' 这样的操作是不允许的,因为字符串是不可变的。

相同的字符串只有一个实例

假设我们定义了两个相同的字符串,如下:

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

这两个字符串的值是相同的,但是它们的地址是不同的。那既然如此,为什么我们还是不能修改它的其中某一个字符呢? 这是因为,虽然 s1s2 的地址不一样,但是它们实际保存 hello 这个字符串的地址是一样的:

v1 := (*reflect.StringHeader)(unsafe.Pointer(&s1))
v2 := (*reflect.StringHeader)(unsafe.Pointer(&s2))
// 两个字符串实例保存字符串的内存地址是一样的
assert.Equal(t, v1.Data, v2.Data)

两个字符串内存表示如下:

所以,我们可以看到,s1s2 实际上是指向同一个字符串的指针,所以我们无法修改其中某一个字符。 因为如果允许这种行为存在的话,我们对其中一个字符串实例修改,也会影响到另外一个字符串实例。

字符串本身可以替换

虽然我们不能修改字符串中的某一个字符,但是我们可以通过反射对象把整个字符串替换掉:

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
}

这里实际上是把 s 中保存字符串的地址替换成了指向 world 这个字符串的地址,而不是将 hello 指向的内存修改成 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
   // 修改之后,实际保存字符串的内存地址发生了改变
   assert.NotEqual(t, oldAddr, newAddr)
}

这可以用下图表示:

总结

  • 如果我们需要通过反射对象来修改变量的值,那么我们必须得有办法拿到变量实际存储的内存地址。这种情况下,很多时候都是通过传递指针给 reflect.ValueOf() 方法来实现的。
  • 但是对于 chanmapslice 或者其他类似的数据结构,它们通过指针来引用实际存储数据的内存,这种数据结构是通过通过传值给 reflect.ValueOf() 方法来实现修改其中的元素的。因为这些数据结构的数据部分可以通过指针的拷贝来修改。
  • 但是 mapslice 有可能会扩容,如果通过反射对象来追加元素,可能导致追加失败。这是因为,通过反射对象追加元素的时候,如果扩容了,那么原来的内存地址就会失效,这样我们其实就修改不了原来的 mapslice 了。
  • 同样的,结构体传值来创建反射对象的时候,如果其中有指针类型的字段,那么我们也可以通过指针来修改其中的元素。但是其他字段也还是修改不了的。
  • 如果我们创建反射对象的参数是 interface 类型,那么能不能修改元素的变量还是取决于我们这个 interface 类型变量的数据部分是值还是指针。如果 interface 变量中存储的是值,那么我们就不能修改其中的元素了。如果 interface 变量中存储的是指针,就可以修改。
  • 我们无法修改字符串的某一个字符,通过反射也不能,因为字符串本身是不可变的。不同的 stirng 类型的变量,如果它们的值是一样的,那么它们会共享实际存储字符串的内存。
  • 但是我们可以直接用一个新的字符串替代旧的字符串。

但其实说了那么多,简单来说只有一点,就是我们只能通过反射对象来修改指针类型的变量。如果拿不到实际存储数据的指针,那么我们就无法通过反射对象来修改其中的元素了。

以上就是go reflect要不要传指针原理详解的详细内容,更多关于go reflect 传指针的资料请关注我们其它相关文章!

(0)

相关推荐

  • go语言reflect.Type 和 reflect.Value 应用示例详解

    目录 一.使用 reflect.Type 创建实例 二.使用 reflect.Value 调用函数 一.使用 reflect.Type 创建实例 在通过 reflect.TypeOf 函数获取到变量的反射类型对象之后,可以通过反射类型对象 reflect.Type 的 New 函数来创建一个新的实例,注意这个实例的类型是 reflect.Type 类型的. package main import ( "fmt" "reflect" ) func main() { v

  • Go REFLECT Library反射类型详解

    目录 一.反射概述 二.反射类型对象 基本数类型的 反射类型对象 引用数据类型的 反射类型对象 结构体的 反射类型对象 指针的 反射类型对象 一.反射概述 反射是指程序在运行期间对程序本身进行访问和修改的能力.程序在编译过程中变量会被转换为内存地址,变量名不会被编译器写入到可执行部分.在程序运行时程序无法获取自身的信息. 在静态语言中如 Java 可以在程序编译期将变量的反射信息,如字段名称.类型等信息整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并

  • golang 如何用反射reflect操作结构体

    背景 需要遍历结构体的所有field 对于exported的field, 动态set这个field的value 对于unexported的field, 通过强行取址的方法来获取该值(tricky?) 思路 下面的代码实现了从一个strct ptr对一个包外结构体进行取值的操作,这种场合在笔者需要用到反射的场合中出现比较多 simpleStrtuctField 函数接受一个结构体指针,因为最后希望改变其值,所以传参必须是指针.然后解引用. 接下来遍历结构体的每个field, exported字段是

  • Go语言反射reflect.Value实现方法的调用

    目录 引言 func (Value) Call 通过反射,调用方法. 通过反射,调用函数. 引言 这算是一个高级用法了,前面我们只说到对类型.变量的几种反射的用法,包括如何获取其值.其类型.以及如何重新设置新值.但是在项目应用中,另外一个常用并且属于高级的用法,就是通过reflect来进行方法[函数]的调用.比如我们要做框架工程的时候,需要可以随意扩展方法,或者说用户可以自定义方法,那么我们通过什么手段来扩展让用户能够自定义呢?关键点在于用户的自定义方法是未可知的,因此我们可以通过reflect

  • Go reflect 反射原理示例详解

    目录 开始之前 分析 从何处获取类型信息 如何实现赋值操作? 总结 开始之前 在开始分析原理之前,有必要问一下自己一个问题: 反射是什么?以及其作用是什么? 不论在哪种语言中,我们所提到的反射功能,均指开发者可以在运行时通过调用反射库来获取到来获取到指定对象类型信息,通常类型信息中会包含对象的字段/方法等信息.并且,反射库通常会提供方法的调用, 以及字段赋值等功能. 使用反射可以帮助我们避免写大量重复的代码, 因此反射功能常见用于ORM框架, 以及序列化何反序列化框架,除此之外在Java中反射还

  • go reflect要不要传指针原理详解

    目录 正文 什么时候传递指针? 1. 通过传递指针修改变量的值 传值无法修改变量本身 传指针可以修改变量 2. 通过传递指针修改结构体的字段 3. 结构体:获取指针接收值方法 4. 变量本身包含指向数据的指针 通过值反射对象修改 chan.map 和 slice slice 反射对象扩容的影响 slice 容量够的话是不是就可以正常追加元素了? map 也不能通过值反射对象来修改其元素. chan 没有追加 结构体字段包含指针的情况 5. interface 类型处理 interface 底层类

  • Python参数传递机制传值和传引用原理详解

    首先还是应该科普下函数参数传递机制,传值和传引用是什么意思? 函数参数传递机制问题在本质上是调用函数(过程)和被调用函数(过程)在调用发生时进行通信的方法问题.基本的参数传递机制有两种:值传递和引用传递. 值传递(passl-by-value)过程中,被调函数的形式参数作为被调函数的局部变量处理,即在堆栈中开辟了内存空间以存放由主调函数放进来的实参的值,从而成为了实参的一个副本.值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值. 引用传递(pass-

  • c++11 新特性——智能指针使用详解

    c++11添加了新的智能指针,unique_ptr.shared_ptr和weak_ptr,同时也将auto_ptr置为废弃(deprecated). 但是在实际的使用过程中,很多人都会有这样的问题: 不知道三种智能指针的具体使用场景 无脑只使用shared_ptr 认为应该禁用raw pointer(裸指针,即Widget*这种形式),全部使用智能指针 初始化方法 class A { public: A(int size){ this->size = size; } A(){} void Sh

  • golang字符串本质与原理详解

    目录 一.字符串的本质 1.字符串的定义 2.字符串的长度 3.字符与符文 二.字符串的原理 1.字符串的解析 2.字符串的拼接 3.字符串的转换 总结 一.字符串的本质 1.字符串的定义 golang中的字符(character)串指的是所有8比特位字节字符串的集合,通常(非必须)是UTF-8 编码的文本. 字符串可以为空,但不能是nil. 字符串在编译时即确定了长度,值是不可变的. // go/src/builtin/builtin.go // string is the set of al

  • Java线程池FutureTask实现原理详解

    前言 线程池可以并发执行多个任务,有些时候,我们可能想要跟踪任务的执行结果,甚至在一定时间内,如果任务没有执行完成,我们可能还想要取消任务的执行,为了支持这一特性,ThreadPoolExecutor提供了 FutureTask 用于追踪任务的执行和取消.本篇介绍FutureTask的实现原理. 类视图 为了更好的理解FutureTask的实现原理,这里先提供几个重要接口和类的结构,如下图所示: RunnableAdapter ThreadPoolExecutor提供了submit接口用于提交任

  • Java中synchronized实现原理详解

    记得刚刚开始学习Java的时候,一遇到多线程情况就是synchronized,相对于当时的我们来说synchronized是这么的神奇而又强大,那个时候我们赋予它一个名字"同步",也成为了我们解决多线程情况的百试不爽的良药.但是,随着我们学习的进行我们知道synchronized是一个重量级锁,相对于Lock,它会显得那么笨重,以至于我们认为它不是那么的高效而慢慢摒弃它. 诚然,随着Javs SE 1.6对synchronized进行的各种优化后,synchronized并不会显得那么

  • python函数声明和调用定义及原理详解

    这篇文章主要介绍了python函数声明和调用定义及原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 函数是指代码片段,可以重复调用,比如我们前面文章接触到的type()/len()等等都是函数,这些函数是python的内置函数,python底层封装后用于实现某些功能. 一.函数的定义 在Python中,定义一个函数要使用def语句,依次写出函数名.括号.括号中的参数和冒号:,然后,在缩进块中编写函数体,函数的返回值用return语句返回:

  • Java Iterator接口遍历单列集合迭代器原理详解

    这篇文章主要介绍了Java Iterator接口遍历单列集合迭代器原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 Iterator接口概述 在程序开发中,经常需要遍历集合中的所有元素.针对这种需求,JDK专门提供了一个接口java.util.Iterator . Iterator 接口也是Java集合中的一员,但它与 Collection . Map 接口有所不同,Collection 接口与 Map 接口主要用于存储元素,而 Iter

  • JavaScript原型继承和原型链原理详解

    这篇文章主要介绍了JavaScript原型继承和原型链原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 在讨论原型继承之前,先回顾一下关于创建自定义类型的方式,这里推荐将构造函数和原型模式组合使用,通过构造函数来定义实例自己的属性,再通过原型来定义公共的方法和属性. 这样一来,每个实例都有自己的实例属性副本,又能共享同一个方法,这样的好处就是可以极大的节省内存空间.同时还可以向构造函数传递参数,十分的方便. 这里还要再讲一下两种特色的构造

  • Java JDK动态代理(AOP)用法及实现原理详解

    Java-JDK动态代理(AOP)使用及实现原理分析 第一章:代理的介绍 介绍:我们需要掌握的程度 动态代理(理解) 基于反射机制 掌握的程度: 1.什么是动态代理? 2.动态代理能够做什么? 后面我们在用Spirng和Mybatis的时候,要理解怎么使用的. 1.什么是代理? 代理,在我们日常生活之中就有体现,代购,中介,换ip,商家等等. 比如有一家美国的大学,可以对全世界招生.留学中介(代理 ) 留学中介(代理):帮助这家美国的学校招生,中介是学校的代理中介是代替学校完成招生功能 代理特点

随机推荐