Maison  >  Article  >  développement back-end  >  Un article expliquant en détail la réflexion dans Golang

Un article expliquant en détail la réflexion dans Golang

青灯夜游
青灯夜游avant
2022-12-14 20:26:346029parcourir

Cet article parlera principalement de GolangMedium Reflection, dans l'espoir d'acquérir une nouvelle compréhension de vous.

Un article expliquant en détail la réflexion dans Golang

Bien que de nombreuses personnes utilisent le langage Go depuis un certain temps, et certains l'utilisent même depuis 1 ou 2 ans, ils sont encore ambigus quant à la réflexion dans le langage Go, et ils ne sont pas très confiants lors de son utilisation. [Recommandations associées : Tutoriel vidéo Go, Enseignement de la programmation]

De plus, presque aucune réflexion n'est utilisée. Bien sûr, il n'y a rien de mal à cela. C'est le plus simple et le plus efficace à utiliser au travail, et c'est le cas. est évolutif et a de bonnes performances. C'est naturellement la façon la plus agréable de le gérer de la bonne manière. Il n'est pas nécessaire de copier mécaniquement certaines utilisations avancées. Après tout, le travail n'est pas notre terrain d'essai. par nous-mêmes. Cette fois, regardons de plus près comment jouer avec la réflexion

Article Parlons-en

  • Qu'est-ce que la réflexion sous les cinq aspects suivants ?
  • Les règles de la réflexion
  • Utilisation cas et application flexible
  • Principe de réflexion
  • Résumé

En termes simples, la réflexion est quoi

En termes simples, La réflexion est la possibilité d'accéder et de modifier le programme lui-même pendant que le programme est en cours d'exécution. Par exemple, lorsque le programme est en cours d'exécution, vous pouvez modifier les noms de champs et les valeurs des champs du programme, et vous pouvez également fournir un accès par interface aux informations du programme, etc.

Il s'agit d'un mécanisme fourni dans le langage Go. .Nous pouvons voir beaucoup de choses sur l'utilisation de Reflect dans la bibliothèque publique du langage Go

Par exemple, le package fmt couramment utilisé, séquence json couramment utilisée. Bien sûr, la bibliothèque gorm que nous avons mentionnée plus tôt utilise également la réflexion.

Mais pourquoi utilisons-nous généralement la réflexion ?

Basé sur la capacité de réflexion, naturellement parce que l'interface que nous fournissons ne sait pas quel sera le type de données entrantes, le type de données spécifique n'est connu que lorsque le programme est en cours d'exécution

Mais nous l'attendons lors du codage Pour vérifier quoi le type transmis lorsque le programme est en cours d'exécution (comme la sérialisation json) et opérer sur ces données spécifiques, à ce moment, nous devons utiliser la capacité de réflexion

Donc, pour utiliser N'est-il pas étrange que vous puissiez voir

interface {}

où que cela se reflète ? C'est précisément parce que nous ne sommes pas sûrs du type de données entrantes, nous l'avons donc conçu comme une interface{}. Si vous n'êtes pas sûr des caractéristiques et de l'utilisation de l'interface, vous pouvez consulter les articles historiques :

.
    À quoi dois-je faire attention à propos de l'interface{} ?
    À quoi dois-je faire attention à propos de l'interface{} ? Ensuite
faites d'abord attention aux règles de réflexion

faites d'abord attention aux trois lois importantes de la réflexion Après avoir connu les règles,

il n'y aura aucun problème si nous jouons selon les règles

Seulement quand. nous ne connaissons pas les règles, toujours des problèmes étranges se produiront lorsque la clause est déclenchée

    La réflexion peut convertir des variables de type d'interface en objets de type réflexion
  • La réflexion peut convertir des objets de type réflexion en variables de type d'interface
  • Nous si vous Si vous souhaitez modifier un objet de type réflexion au moment de l'exécution, alors la valeur correspondant à cet objet doit être accessible en écriture. Les trois règles ci-dessus sont également relativement faciles à comprendre. Vous vous souvenez des pointeurs du package non sécurisé que nous avons mentionné précédemment ?
  • Nous convertissons nos types de données couramment utilisés en types de données spécifiés dans les packages (tels que les packages non sécurisés ou les packages réfléchissants), puis modifions les données selon les règles des packages
Cela équivaut à changer de gilet et vous pouvez faites-le Différentes opérations

Faites attention aux cas d'utilisation et utilisez-les de manière flexible

Généralement, nous comprendrons d'abord l'application de base, puis étudierons ses principes et pourquoi elle peut être utilisée de cette manière, et lentement nous pourrons la comprendre plus profondément

Pour la loi 1, convertir les variables de type interface en objets de type réflexion

En fait, pour les variables de type interface mentionnées ici, nous pouvons transmettre des variables de n'importe quel type de données, telles que

etc.

Les objets de type Reflection peuvent être compris ici comme les objets reflect.Type et reflex.Value dans le package Reflection, accessibles via les reflect.Type reflect.Value 对象,可以通过 reflect 包中提供的 TypeOfValueOf 函数得到

其中 reflect.Type 实际上是一个 interface ,他里面包含了各种接口需要进行实现,它里面提供了关于类型相关的信息

其中如下图可以查看到 reflect.Type 的所有方法,其中

  • 绿色的 是所有数据类型都是可以调用的
  • 红色的是 函数类型数据可以调用的
  • 黑色的是 Map,数组 Array,通道 Chan,指针 Ptr 或者 切片Slice 可以调用的
  • 蓝色的是结构体调用的
  • 黄色的是通道 channel 类型调用的

reflect.Value 实际上是一个 struct,根据这个 struct 还关联了一组方法,这里面存放了数据类型和具体的数据,通过查看其数据结构就可以看出

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

看到此处的 unsafe.Pointer 是不是很熟悉,底层自然就可以将 unsafe.Pointer 转换成 uintptr,然后再修改其数据后,再转换回来,对于 Go 指针不太熟悉的可以查看这篇文章:

写一个简单的 demo 就可以简单的获取到变量的数据类型和值

func main() {   var demoStr string = "now reflect"
   fmt.Println("type:", reflect.TypeOf(demoStr))
   fmt.Println("value:", reflect.ValueOf(demoStr))
}

对于定律二,将 反射类型的对象 转换成 接口类型的变量

我们可以通过将 reflect.Value 类型转换成我们具体的数据类型,因为 reflect.Value 中有对应的 typ *rtype 以及 ptr unsafe.Pointer

例如我们可以 通过 reflect.Value 对象的 interface() 方法来处理

func main() {   var demoStr string = "now reflect"
   fmt.Println("type:", reflect.TypeOf(demoStr))
   fmt.Println("value:", reflect.ValueOf(demoStr))   var res string
   res = reflect.ValueOf(demoStr).Interface().(string)
   fmt.Println("res == ",res)
}

对于定律三,修改反射类型的对象

首先我们看上书的 demo 代码,传入 TypeOfValueOf 的变量实际上也是一个拷贝,那么如果期望在反射类型的对象中修改其值,那么就需要拿到具体变量的地址然后再进行修改,前提是这个变量是可写的

举个例子你就能明白

func main() {
   var demoStr string = "now reflect"
   v := reflect.ValueOf(demoStr)
   fmt.Println("is canset ", v.CanSet())
   //v.SetString("hello world")   // 会panic
   }

可以先调用 reflect.Value 对象的 CanSet 查看是否可写,如果是可写的,我们再写,如果不可写就不要写了,否则会 panic

那么传入变量的地址就可以修改了??

传入地址的思路没有毛病,但是我们去设置值的方式有问题,因此也会出现上述的 panic 情况

此处仔细看能够明白,反射的对象 v 自然是不可修改的,我们应该找到 reflect.Value 里面具体具体的数据指针,那么才是可以修改的,可以使用 reflect.Value TypeOf et fournis dans le package Reflect. La fonction ValueOf

obtient

reflect.Type est en fait une interface, qui contient diverses interfaces qui doivent être implémentées. Elle fournit des informations sur le type

, qui peuvent être visualisées. comme indiqué ci-dessous Pour toutes les méthodes de reflect.Type, les

vertes

sont tous les types de données peuvent être appelés
    rouge
  • sont les données de type de fonction peuvent être appelées Les noirs sont Map, Array, Channel Chan, pointer Ptr ou slice Slice qui peuvent être appelés

Les bleus 🎜 sont appelés par la structure 🎜🎜🎜🎜🎜Les jaunes 🎜 sont appelés par type de chaîne 🎜🎜🎜🎜 🎜 Reflect.Value est en fait une structure. Selon cette structure, un ensemble de méthodes est également associé, qui stocke le type de données et les données spécifiques. Vous pouvez le voir en regardant sa structure de données🎜
type RDemo struct {
   Name  string
   Age   int
   Money float32
   Hobby map[string][]string
}

func main() {
   tmp := &RDemo{
      Name:  "xiaomiong",
      Age:   18,
      Money: 25.6,
      Hobby: map[string][]string{
         "sport": {"basketball", "football"},
         "food":  {"beef"},
      },
   }

   v := reflect.ValueOf(tmp).Elem()  // 拿到结构体对象
   h := v.FieldByName("Hobby")    // 拿到 Hobby 对象
   h1 := h.MapKeys()[0]    // 拿到 Hobby 的第 0 个key
   fmt.Println("key1 name == ",h1.Interface().(string))

   sli := h.MapIndex(h1)    // 拿到 Hobby 的第 0 个key对应的对象
   str := sli.Index(1)      // 拿到切片的第 1 个对象
   fmt.Println(str.CanSet())

   str.SetString("helloworld")
   fmt.Println("tmp == ",tmp)
}
. 🎜Voir ici Connaissez-vous unsafe.Pointer ? La couche inférieure peut naturellement convertir unsafe.Pointer en uintptr, puis modifier ses données, puis les reconvertir. , ce n'est pas le cas. Si vous êtes trop familier, vous pouvez consulter cet article : 🎜🎜🎜Pointeurs dans GO ? 🎜🎜🎜Écrivez une démo simple pour obtenir facilement le type de données et la valeur de la variable🎜
// rtype must be kept in sync with ../runtime/type.go:/^type._type.
type rtype struct {
   size       uintptr
   ptrdata    uintptr
   hash       uint32 
   tflag      tflag
   align      uint8
   fieldAlign uint8
   kind       uint8
   equal     func(unsafe.Pointer, unsafe.Pointer) bool
   gcdata    *byte 
   str       nameOff
   ptrToThis typeOff
}
🎜🎜

🎜Pour la loi 2, convertir les objets de type réflexion en variables de type interface🎜🎜 🎜Nous pouvons convertir les Tapez reflect.Value dans notre type de données spécifique, car il existe un typ *rtype correspondant dans reflect.Value et ptr dangereux. Pointeur🎜🎜Par exemple, nous pouvons le gérer via la méthode interface() de l'objet reflect.Value 🎜🎜🎜
// emptyInterface is the header for an interface{} value.type emptyInterface struct {
   typ  *rtype
   word unsafe.Pointer
}复制代码
🎜🎜

Pour la troisième loi, modifiez l'objet de type réflexion🎜🎜Tout d'abord, nous regardons Dans le code de démonstration du livre, les variables transmises dans 🎜TypeOf🎜 et 🎜ValueOf🎜 sont en fait des copies. Donc, si vous souhaitez modifier sa valeur dans un objet de type réflexion, vous devez obtenir l'adresse de la variable spécifique puis la modifier. 🎜Le principe est que cette variable est accessible en écriture🎜🎜🎜Vous pouvez comprendre avec un exemple🎜rrreee🎜🎜🎜Vous pouvez d'abord appeler le CanSet de l'objet reflect.Value pour vérifier s'il est accessible en écriture. Si c'est le cas , Écrivons à nouveau. Si cela ne peut pas être écrit, ne l'écrivez pas, sinon cela va paniquer🎜🎜🎜🎜Donc l'adresse de la variable passée peut être modifiée ? ? 🎜🎜🎜🎜Adresse entrante Il y a rien de mal avec l'idée, mais il y a quelque chose qui ne va pas dans la façon dont nous définissons la valeur, donc la situation de panique mentionnée ci-dessus se produira également. Si vous regardez attentivement ici, vous pouvez comprendre que l'objet réfléchi v est naturellement non modifiable. reflect.Value Le pointeur de données spécifique dans peut être modifié. Vous pouvez utiliser la méthode 🎜Elem🎜 de reflect.Value pour un cas légèrement plus compliqué. , vous pensez peut-être que c'est un cas si simple, et tout ira bien dès qu'il sera démontré, mais il plantera dès qu'il sera utilisé au travail. Naturellement, il n'a pas encore été entièrement compris, ce qui signifie que. il n'a pas été bien digéré. Voici un autre exemple au travail🎜🎜🎜Une structure a une carte, la clé dans la carte est une chaîne et la valeur est []string🎜🎜🎜🎜La condition est d'accéder au 🎜1🎜ème. élément de la tranche correspondant au champ hobby dans la structure avec la touche map 🎜sport🎜, et modifiez-le en 🎜 hellolworld🎜🎜🎜rrreee🎜🎜🎜

可以看到上述案例运行之后有时可以运行成功,有时会出现 panic 的情况,相信细心的 xdm 就可以看出来,是因为 map 中的 key 是 无序的导致的,此处也提醒一波,使用 map 的时候要注意这一点

看上述代码,是不是就能够明白咱们使用反射去找到对应的数据类型,然后按照数据类型进行处理数据的过程了呢

有需要的话,可以慢慢的去熟练反射包中涉及的函数,重点是要了解其三个规则,对象转换方式,访问方式,以及数据修改方式

反射原理

那么通过上述案例,可以知道关于反射中数据类型和数据指针对应的值是相当重要的,不同的数据类型能够用哪些函数这个需要注意,否则用错直接就会 panic

TypeOf

来看 TypeOf 的接口中涉及的数据结构

在 reflect 包中 rtype 是非常重要的,Go 中所有的类型都会包含这个结构,所以咱们反射可以应用起来,结构如下

// rtype must be kept in sync with ../runtime/type.go:/^type._type.
type rtype struct {
   size       uintptr
   ptrdata    uintptr
   hash       uint32 
   tflag      tflag
   align      uint8
   fieldAlign uint8
   kind       uint8
   equal     func(unsafe.Pointer, unsafe.Pointer) bool
   gcdata    *byte 
   str       nameOff
   ptrToThis typeOff
}

其中可以看到此处的 rtype 的结构保持和 runtime/type.go 一致 ,都是关于数据类型的表示,以及对应的指针,关于这一块的说明和演示可以查看文末的 interface{} 处的内容

ValueOf

ValueOf 的源码中,我们可以看到,重要的是 emptyInterface 结构

// emptyInterface is the header for an interface{} value.type emptyInterface struct {
   typ  *rtype
   word unsafe.Pointer
}复制代码

emptyInterface 结构中有 rtype 类型的指针, word 自然是对应的数据的地址了

reflect.Value 对象中的方法也是非常的多,用起来和上述说到的 reflect.Type 接口中的功能类似

关于源码中涉及到的方法,就不再过多的赘述了,更多的还是需要自己多多实践才能体会的更好

殊不知,此处的 reflect.Value 也是可以转换成 reflect.Type ,可以查看源码中 reflect\value.gofunc (v Value) Type() Type {

其中   reflect.Value  ,reflect.Type ,和任意数据类型 可以相互这样来转换

如下图:

总结

至此,关于反射就聊到这里,一些关于源码的细节并没有详细说,更多的站在一个使用者的角度去看反射需要注意的点

关于反射,大多的人是建议少用,因为是会影响到性能,不过如果不太关注这一点,那么用起来还是非常方便的

高级功能自然也是双刃剑,你用不好就会 panic,如果你期望去使用他,那么就去更多的深入了解和一步一步的吃透他吧

大道至简,反射三定律,活学活用

更多编程相关知识,请访问:编程视频!!

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer