Cet article parlera principalement de GolangMedium Reflection, dans l'espoir d'acquérir une nouvelle compréhension de vous.
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
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 :
. faites d'abord attention aux règles de réflexionSeulement quand. nous ne connaissons pas les règles, toujours des problèmes étranges se produiront lorsque la clause est déclenchée
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émentLes 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 包中提供的 TypeOf 和 ValueOf 函数得到
其中 reflect.Type
实际上是一个 interface ,他里面包含了各种接口需要进行实现,它里面提供了关于类型相关的信息
其中如下图可以查看到 reflect.Type
的所有方法,其中
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 代码,传入 TypeOf 和 ValueOf 的变量实际上也是一个拷贝,那么如果期望在反射类型的对象中修改其值,那么就需要拿到具体变量的地址然后再进行修改,前提是这个变量是可写的
举个例子你就能明白
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
où reflect.Type
est en fait une interface, qui contient diverses interfaces qui doivent être implémentées. Elle fournit des informations sur le type
reflect.Type
, les vertes
sont tous les types de données peuvent être appelés 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) }
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 }
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 }复制代码
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 的接口中涉及的数据结构
在 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 的源码中,我们可以看到,重要的是 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.go
的 func (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!