Riflessione
La riflessione è un meccanismo che consente di ispezionare la struttura del linguaggio stesso a runtime. Può affrontare alcuni problemi in modo molto flessibile, ma allo stesso tempo presenta anche evidenti svantaggi, come problemi di prestazioni, ecc. In Go, la riflessione è strettamente correlata a interface{}. In gran parte, ovunque appaia interface{}, ci sarà riflessione. Le API di riflessione in Go sono fornite dal pacchetto standard reflect.
Interfacce
Prima di iniziare, diamo una semplice occhiata a due interfacce nel pacchetto runtime. In Go, le interfacce sono essenzialmente strutture. Go divide le interfacce in due grandi categorie a runtime: interfacce senza insieme di metodi e interfacce con insieme di metodi. Per le interfacce con insieme di metodi, a runtime sono rappresentate dalla seguente struttura iface:
type iface struct {
tab *itab // contiene tipo di dati, tipo di interfaccia, insieme di metodi, ecc.
data unsafe.Pointer // puntatore al valore
}Per le interfacce senza insieme di metodi, a runtime sono rappresentate dalla struttura eface, come segue:
type eface struct {
_type *_type // tipo
data unsafe.Pointer // puntatore al valore
}Queste due strutture hanno tipi di struttura corrispondenti nel pacchetto reflect. iface corrisponde a nonEmptyInterface:
type nonEmptyInterface struct {
itab *struct {
ityp *rtype // tipo di interfaccia statica
typ *rtype // tipo concreto dinamico
hash uint32 // hash del tipo
_ [4]byte
fun [100000]unsafe.Pointer // insieme di metodi
}
word unsafe.Pointer // puntatore al valore
}E eface corrisponde a emptyInterface:
type emptyInterface struct {
typ *rtype // tipo concreto dinamico
word unsafe.Pointer // valore che punta al puntatore
}Per questi due tipi, l'ufficiale ha dato definizioni molto chiare:
nonEmptyInterface: nonEmptyInterface is the header for an interface value with methodsemptyInterface: emptyInterface is the header for an interface{} value
Come menzionato sopra, il tipo concreto dinamico, il termine originale è dynamic concrete type. Innanzitutto, Go è un linguaggio di tipi statici al 100%. La staticità si riflette nel fatto che il tipo di interfaccia astratto manifestato esternamente è invariabile, mentre dinamico indica che il tipo concreto memorizzato sottostante dell'interfaccia può cambiare. Fin qui, per il semplice principio delle interfacce, è sufficiente conoscere quanto sopra per soddisfare l'apprendimento successivo della riflessione.
Ponte
Nel pacchetto reflect, ci sono rispettivamente il tipo di interfaccia reflect.Type per rappresentare i tipi in Go e il tipo di struttura reflect.Value per rappresentare i valori in Go:
type Type interface {
...
Name() string
PkgPath() string
Size() uintptr
String() string
Kind() Kind
...
}
type Value struct {
typ *rtype
ptr unsafe.Pointer
flag
}Il codice sopra omette molti dettagli. Per ora, basta conoscere l'esistenza di questi due tipi. Tutte le operazioni di riflessione in Go si basano su questi due tipi. Il pacchetto reflect fornisce due funzioni per convertire i tipi in Go nei due tipi sopra menzionati per eseguire operazioni di riflessione: reflect.TypeOf:
func TypeOf(i any) TypeE reflect.ValueOf:
func ValueOf(i any) ValueCome si può vedere, i tipi di parametro di entrambe le funzioni sono any, ovvero l'alias di interface{}. Se si desidera eseguire operazioni di riflessione, è necessario prima convertire il tipo in interface{}. Questo è il motivo per cui è stato menzionato in precedenza che la riflessione non può fare a meno dell'interfaccia vuota. Non rigorosamente parlando, l'interfaccia vuota è il ponte che collega il sistema di tipi di Go e la riflessione. La figura seguente descrive vividamente il processo:

TIP
Nel testo seguente, per comodità, useremo uniformemente l'alias any per sostituire interface{}
Nucleo
In Go ci sono tre leggi classiche della riflessione. Combinando con quanto sopra, sono molto facili da capire:
- La riflessione può convertire una variabile di tipo
interface{}in un oggetto di riflessione - La riflessione può ripristinare un oggetto di riflessione in una variabile di tipo
interface{} - Per modificare un oggetto di riflessione, il suo valore deve essere impostabile
Queste tre leggi sono il nucleo della riflessione in Go. Quando è necessario accedere alle informazioni relative al tipo, è necessario utilizzare reflect.TypeOf. Quando è necessario modificare il valore di riflessione, è necessario utilizzare reflect.ValueOf.
Tipo
reflect.Type rappresenta un tipo in Go. Utilizza la funzione reflect.TypeOf() per convertire una variabile in reflect.Type. Esempio di codice:
func main() {
str := "hello world!"
reflectType := reflect.TypeOf(str)
fmt.Println(reflectType)
}Risultato output:
stringKind
Per Type, Go utilizza internamente reflect.Kind per rappresentare i tipi di base in Go. Essenzialmente è un intero senza segno uint:
type Kind uintIl pacchetto reflect utilizza Kind per enumerare tutti i tipi di base in Go, come mostrato di seguito:
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
Pointer
Slice
String
Struct
UnsafePointer
)Il tipo Kind implementa solo il metodo String() dell'interfaccia Stringer. Questo tipo ha solo questo metodo. Il valore di ritorno del metodo String() proviene da una slice interna, come mostrato di seguito. Questa scrittura a prima vista sembra molto simile a una map, ma in realtà è una scrittura speciale in Go: sintassi di inizializzazione dell'indice (index expressions in slice literals):
var kindNames = []string{
Invalid: "invalid",
Bool: "bool",
Int: "int",
Int8: "int8",
Int16: "int16",
Int32: "int32",
Int64: "int64",
Uint: "uint",
Uint8: "uint8",
Uint16: "uint16",
Uint32: "uint32",
Uint64: "uint64",
Uintptr: "uintptr",
Float32: "float32",
Float64: "float64",
Complex64: "complex64",
Complex128: "complex128",
Array: "array",
Chan: "chan",
Func: "func",
Interface: "interface",
Map: "map",
Pointer: "ptr",
Slice: "slice",
String: "string",
Struct: "struct",
UnsafePointer: "unsafe.Pointer",
}type Type interface{
Kind() Kind
}Attraverso Kind, è possibile sapere quale tipo di base è effettivamente memorizzato nella variabile any. Ad esempio:
func main() {
// Dichiara una variabile di tipo any
var eface any
// Assegna un valore
eface = 100
// Ottieni il suo tipo tramite il metodo Kind
fmt.Println(reflect.TypeOf(eface).Kind())
}Risultato output:
intElem
type Type interface{
Elem() Type
}Utilizzando il metodo Type.Elem(), è possibile determinare il tipo di elemento memorizzato nella struttura dati di tipo any. I tipi di parametro sottostanti accettati devono essere uno tra puntatore, slice, array, canale, mappa. Altrimenti si verificherà panic. Di seguito è riportato un esempio di codice:
func main() {
var eface any
eface = map[string]int{}
rType := reflect.TypeOf(eface)
// key() restituisce il tipo di riflessione della chiave della map
fmt.Println(rType.Key().Kind())
fmt.Println(rType.Elem().Kind())
}Output:
string
intAnche un puntatore può essere inteso come un contenitore. Per un puntatore, l'uso di Elem() otterrà il tipo di riflessione dell'elemento puntato. Esempio di codice:
func main() {
var eface any
// Assegna un puntatore
eface = new(strings.Builder)
rType := reflect.TypeOf(eface)
// Ottieni il tipo di riflessione dell'elemento puntato dal puntatore
vType := rType.Elem()
// Output del percorso del pacchetto
fmt.Println(vType.PkgPath())
// Output del nome
fmt.Println(vType.Name())
}strings
BuilderPer array, slice e canali, l'uso è simile.
Size
type Type interface{
Size() uintptr
}Attraverso il metodo Size, è possibile ottenere la dimensione in byte del tipo corrispondente. Esempio:
func main() {
fmt.Println(reflect.TypeOf(0).Size())
fmt.Println(reflect.TypeOf("").Size())
fmt.Println(reflect.TypeOf(complex(0, 0)).Size())
fmt.Println(reflect.TypeOf(0.1).Size())
fmt.Println(reflect.TypeOf([]string{}).Size())
}Risultato output:
8
16
16
8
24TIP
L'uso di unsafe.Sizeof() può ottenere lo stesso effetto.
Comparable
type Type interface{
Comparable() bool
}Attraverso il metodo Comparable, è possibile determinare se un tipo può essere confrontato. Esempio:
func main() {
fmt.Println(reflect.TypeOf("hello world!").Comparable())
fmt.Println(reflect.TypeOf(1024).Comparable())
fmt.Println(reflect.TypeOf([]int{}).Comparable())
fmt.Println(reflect.TypeOf(struct{}{}).Comparable())
}Output:
true
true
false
trueImplements
type Type interface{
Implements(u Type) bool
}Attraverso il metodo Implements, è possibile determinare se un tipo implementa una determinata interfaccia:
type MyInterface interface {
My() string
}
type MyStruct struct {
}
func (m MyStruct) My() string {
return "my"
}
type HisStruct struct {
}
func (h HisStruct) String() string {
return "his"
}
func main() {
rIface := reflect.TypeOf(new(MyInterface)).Elem()
fmt.Println(reflect.TypeOf(new(MyStruct)).Elem().Implements(rIface))
fmt.Println(reflect.TypeOf(new(HisStruct)).Elem().Implements(rIface))
}Risultato output:
true
falseConvertibleTo
type Type interface{
ConvertibleTo(u Type) bool
}Utilizzando il metodo ConvertibleTo, è possibile determinare se un tipo può essere convertito in un altro tipo specificato:
type MyInterface interface {
My() string
}
type MyStruct struct {
}
func (m MyStruct) My() string {
return "my"
}
type HisStruct struct {
}
func (h HisStruct) String() string {
return "his"
}
func main() {
rIface := reflect.TypeOf(new(MyInterface)).Elem()
fmt.Println(reflect.TypeOf(new(MyStruct)).Elem().ConvertibleTo(rIface))
fmt.Println(reflect.TypeOf(new(HisStruct)).Elem().ConvertibleTo(rIface))
}Output:
true
falseValore
reflect.Value rappresenta il valore dell'interfaccia di riflessione. Utilizza la funzione reflect.ValueOf() per convertire una variabile in reflect.Value. Esempio di codice:
func main() {
str := "hello world!"
reflectValue := reflect.ValueOf(str)
fmt.Println(reflectValue)
}Risultato output:
hello world!Type
func (v Value) Type() TypeIl metodo Type può ottenere il tipo di un valore di riflessione:
func main() {
num := 114514
rValue := reflect.ValueOf(num)
fmt.Println(rValue.Type())
}Output:
intElem
func (v Value) Elem() ValueOttieni il valore di riflessione dell'elemento di un valore di riflessione:
func main() {
num := new(int)
*num = 114514
// Prendiamo un puntatore come esempio
rValue := reflect.ValueOf(num).Elem()
fmt.Println(rValue.Interface())
}Output:
114514Puntatore
Ci sono due modi per ottenere il puntatore di un valore di riflessione:
// Restituisce un valore di riflessione puntatore che rappresenta l'indirizzo di v
func (v Value) Addr() Value
// Restituisce un uintptr che punta al valore originale di v, equivalente a uintptr(Value.Addr().UnsafePointer())
func (v Value) UnsafeAddr() uintptr
// Restituisce un uintptr che punta al valore originale di v
// Solo quando il Kind di v è Chan, Func, Map, Pointer, Slice, UnsafePointer, altrimenti panic
func (v Value) Pointer() uintptr
// Restituisce un unsafe.Pointer che punta al valore originale di v
// Solo quando il Kind di v è Chan, Func, Map, Pointer, Slice, UnsafePointer, altrimenti panic
func (v Value) UnsafePointer() unsafe.PointerEsempio:
func main() {
num := 1024
ele := reflect.ValueOf(&num).Elem()
fmt.Println("&num", &num)
fmt.Println("Addr", ele.Addr())
fmt.Println("UnsafeAddr", unsafe.Pointer(ele.UnsafeAddr()))
fmt.Println("Pointer", unsafe.Pointer(ele.Addr().Pointer()))
fmt.Println("UnsafePointer", ele.Addr().UnsafePointer())
}Output:
&num 0xc0000a6058
Addr 0xc0000a6058
UnsafeAddr 0xc0000a6058
Pointer 0xc0000a6058
UnsafePointer 0xc0000a6058TIP
fmt.Println otterrà il tipo del parametro tramite riflessione. Se è di tipo reflect.Value, chiamerà automaticamente Value.Interface() per ottenere il suo valore originale.
Proviamo con una map:
func main() {
dic := map[string]int{}
ele := reflect.ValueOf(&dic).Elem()
println(dic)
fmt.Println("Addr", ele.Addr())
fmt.Println("UnsafeAddr", *(*unsafe.Pointer)(unsafe.Pointer(ele.UnsafeAddr())))
fmt.Println("Pointer", unsafe.Pointer(ele.Pointer()))
fmt.Println("UnsafePointer", ele.UnsafePointer())
}Output:
0xc00010e4b0
Addr &map[]
UnsafeAddr 0xc00010e4b0
Pointer 0xc00010e4b0
UnsafePointer 0xc00010e4b0Impostare Valore
func (v Value) Set(x Value)Se si desidera modificare il valore di riflessione tramite riflessione, il suo valore deve essere indirizzabile. In questo caso, si dovrebbe modificare il valore dell'elemento tramite un puntatore, invece di tentare direttamente di modificare il valore dell'elemento.
func main() {
// *int
num := new(int)
*num = 114514
rValue := reflect.ValueOf(num)
// Ottieni l'elemento puntato dal puntatore
ele := rValue.Elem()
fmt.Println(ele.Interface())
ele.SetInt(11)
fmt.Println(ele.Interface())
}Output:
114514
11Ottenere Valore
func (v Value) Interface() (i any)Attraverso il metodo Interface(), è possibile ottenere il valore originale del valore di riflessione:
func main() {
var str string
str = "hello"
rValue := reflect.ValueOf(str)
if v, ok := rValue.Interface().(string); ok {
fmt.Println(v)
}
}Output:
helloFunzione
Attraverso la riflessione è possibile ottenere tutte le informazioni su una funzione e anche chiamare la funzione tramite riflessione.
Informazioni
Ottieni tutte le informazioni sulla funzione tramite il tipo di riflessione:
func Max(a, b int) int {
if a > b {
return a
}
return b
}
func main() {
rType := reflect.TypeOf(Max)
// Output del nome della funzione. Le funzioni letterali non hanno nome
fmt.Println(rType.Name())
// Output del numero di parametri e valori di ritorno
fmt.Println(rType.NumIn(), rType.NumOut())
rParamType := rType.In(0)
// Output del tipo del primo parametro
fmt.Println(rParamType.Kind())
rResType := rType.Out(0)
// Output del tipo del primo valore di ritorno
fmt.Println(rResType.Kind())
}Output:
2 1
int
intChiamata
Chiama la funzione tramite il valore di riflessione:
func (v Value) Call(in []Value) []Valuefunc main() {
// Ottieni il valore di riflessione della funzione
rType := reflect.ValueOf(Max)
// Passa l'array di parametri
rResValue := rType.Call([]reflect.Value{reflect.ValueOf(18), reflect.ValueOf(50)})
for _, value := range rResValue {
fmt.Println(value.Interface())
}
}Output:
50Struttura
Supponiamo di avere la seguente struttura:
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Address string `json:"address"`
money int
}
func (p Person) Talk(msg string) string {
return msg
}Accedere ai Campi
La struttura reflect.StructField è la seguente:
type StructField struct {
// Nome del campo
Name string
// Nome del pacchetto
PkgPath string
// Nome del tipo
Type Type
// Tag
Tag StructTag
// Offset in byte del campo
Offset uintptr
// Indice
Index []int
// Se è un campo annidato
Anonymous bool
}Ci sono due modi per accedere ai campi di una struttura: uno è tramite indice, l'altro è tramite nome.
type Type interface{
Field(i int) StructField
}Esempio di accesso tramite indice:
func main() {
rType := reflect.TypeOf(new(Person)).Elem()
// Output del numero di campi della struttura
fmt.Println(rType.NumField())
for i := 0; i < rType.NumField(); i++ {
structField := rType.Field(i)
fmt.Println(structField.Index, structField.Name, structField.Type, structField.Offset, structField.IsExported())
}
}Output:
4
[0] Name string 0 true
[1] Age int 16 true
[2] Address string 24 true
[3] money int 40 falsetype Type interface{
FieldByName(name string) (StructField, bool)
}Esempio di accesso tramite nome:
func main() {
rType := reflect.TypeOf(new(Person)).Elem()
// Output del numero di campi della struttura
fmt.Println(rType.NumField())
if field, ok := rType.FieldByName("money"); ok {
fmt.Println(field.Name, field.Type, field.IsExported())
}
}Output:
4
money int falseModificare Campi
Se si desidera modificare il valore di un campo di struttura, è necessario passare un puntatore alla struttura. Di seguito è riportato un esempio di modifica di un campo:
func main() {
// Passa un puntatore
rValue := reflect.ValueOf(&Person{
Name: "",
Age: 0,
Address: "",
money: 0,
}).Elem()
// Ottieni il campo
name := rValue.FieldByName("Name")
// Modifica il valore del campo
if (name != reflect.Value{}) { // Se restituisce reflect.Value{}, significa che il campo non esiste
name.SetString("jack")
}
// Output della struttura
fmt.Println(rValue.Interface())
}Output:
{jack 0 0}Per modificare i campi privati di una struttura, sono necessarie alcune operazioni extra, come segue:
func main() {
// Passa un puntatore
rValue := reflect.ValueOf(&Person{
Name: "",
Age: 0,
Address: "",
money: 0,
}).Elem()
// Ottieni un campo privato
money := rValue.FieldByName("money")
// Modifica il valore del campo
if (money != reflect.Value{}) {
// Costruisci un valore di riflessione puntatore che punta al campo non esportato della struttura
p := reflect.NewAt(money.Type(), money.Addr().UnsafePointer())
// Ottieni l'elemento puntato dal puntatore, ovvero il campo da modificare
field := p.Elem()
// Modifica il valore
field.SetInt(164)
}
// Output della struttura
fmt.Printf("%+v\n", rValue.Interface())
}Accedere ai Tag
Dopo aver ottenuto StructField, è possibile accedere direttamente al suo Tag:
// Se non esiste, ok è false
func (tag StructTag) Lookup(key string) (value string, ok bool)
// Se non esiste, restituisce una stringa vuota
func (tag StructTag) Get(key string) stringEsempio:
func main() {
rType := reflect.TypeOf(new(Person)).Elem()
name, ok := rType.FieldByName("Name")
if ok {
fmt.Println(name.Tag.Lookup("json"))
fmt.Println(name.Tag.Get("json"))
}
}Output:
name true
nameAccedere ai Metodi
L'accesso ai metodi è simile al processo di accesso ai campi, ma le firme delle funzioni sono leggermente diverse. La struttura reflect.Method è la seguente:
type Method struct {
// Nome del metodo
Name string
// Nome del pacchetto
PkgPath string
// Tipo del metodo
Type Type
// Funzione corrispondente al metodo, il primo parametro è il receiver
Func Value
// Indice
Index int
}Esempio di accesso alle informazioni sul metodo:
func main() {
// Ottieni il tipo di riflessione della struttura
rType := reflect.TypeOf(new(Person)).Elem()
// Output del numero di metodi
fmt.Println(rType.NumMethod())
// Itera e output delle informazioni sul metodo
for i := 0; i < rType.NumMethod(); i++ {
method := rType.Method(i)
fmt.Println(method.Index, method.Name, method.Type, method.IsExported())
}
}Output:
1
0 Talk func(main.Person, string) string trueSe si desidera ottenere i dettagli dei parametri e dei valori di ritorno del metodo, è possibile ottenerli tramite Method.Func. Il processo è simile all'accesso alle informazioni sulla funzione. Modificando leggermente il codice sopra:
func main() {
// Ottieni il tipo di riflessione della struttura
rType := reflect.TypeOf(new(Person)).Elem()
// Output del numero di metodi
fmt.Println(rType.NumMethod())
// Itera e output delle informazioni sul metodo
for i := 0; i < rType.NumMethod(); i++ {
method := rType.Method(i)
fmt.Println(method.Index, method.Name, method.Type, method.IsExported())
fmt.Println("Parametri del metodo")
for i := 0; i < method.Func.Type().NumIn(); i++ {
fmt.Println(method.Func.Type().In(i).String())
}
fmt.Println("Valori di ritorno del metodo")
for i := 0; i < method.Func.Type().NumOut(); i++ {
fmt.Println(method.Func.Type().Out(i).String())
}
}
}Come si può vedere, il primo parametro è main.Person, ovvero il tipo di receiver:
1
0 Talk func(main.Person, string) string true
Parametri del metodo
main.Person
string
Valori di ritorno del metodo
stringChiamare Metodi
Chiamare un metodo è simile al processo di chiamata di una funzione, e non è necessario passare manualmente il receiver. Esempio:
func main() {
// Ottieni il tipo di riflessione della struttura
rValue := reflect.ValueOf(new(Person)).Elem()
// Output del numero di metodi
fmt.Println(rValue.NumMethod())
// Itera e output delle informazioni sul metodo
talk := rValue.MethodByName("Talk")
if (talk != reflect.Value{}) {
// Chiama il metodo e ottieni il valore di ritorno
res := talk.Call([]reflect.Value{reflect.ValueOf("hello,reflect!")})
// Itera e output dei valori di ritorno
for _, re := range res {
fmt.Println(re.Interface())
}
}
}Output:
1
hello,reflect!Creazione
Attraverso la riflessione è possibile costruire nuovi valori. Il pacchetto reflect fornisce contemporaneamente funzioni più convenienti per alcuni tipi speciali.
Tipi di Base
// Restituisce un valore di riflessione puntatore che punta al valore di riflessione
func New(typ Type) ValuePrendiamo string come esempio:
func main() {
rValue := reflect.New(reflect.TypeOf(*new(string)))
rValue.Elem().SetString("hello world!")
fmt.Println(rValue.Elem().Interface())
}hello world!Struttura
Anche la creazione di una struttura utilizza la funzione reflect.New:
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Address string `json:"address"`
money int
}
func (p Person) Talk(msg string) string {
return msg
}
func main() {
// Crea un valore di riflessione della struttura
rType := reflect.TypeOf(new(Person)).Elem()
person := reflect.New(rType).Elem()
fmt.Println(person.Interface())
}Output:
{ 0 0}Slice
Creazione di slice tramite riflessione:
func MakeSlice(typ Type, len, cap int) Valuefunc main() {
// Crea un valore di riflessione della slice
rValue := reflect.MakeSlice(reflect.TypeOf(*new([]int)), 10, 10)
// Itera e assegna valori
for i := 0; i < 10; i++ {
rValue.Index(i).SetInt(int64(i))
}
fmt.Println(rValue.Interface())
}[0 1 2 3 4 5 6 7 8 9]Map
Creazione di Map tramite riflessione:
func MakeMapWithSize(typ Type, n int) Valuefunc main() {
// Costruisci un valore di riflessione della map
rValue := reflect.MakeMapWithSize(reflect.TypeOf(*new(map[string]int)), 10)
// Imposta il valore
rValue.SetMapIndex(reflect.ValueOf("a"), reflect.ValueOf(1))
fmt.Println(rValue.Interface())
}map[a:1]Canale
Creazione di canali tramite riflessione:
func MakeChan(typ Type, buffer int) Valuefunc main() {
// Crea un valore di riflessione del canale
makeChan := reflect.MakeChan(reflect.TypeOf(new(chan int)).Elem(), 0)
fmt.Println(makeChan.Interface())
}Funzione
Creazione di funzioni tramite riflessione:
func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Valuefunc main() {
// Passa il tipo di wrapper e il corpo della funzione
fn := reflect.MakeFunc(reflect.TypeOf(new(func(int))).Elem(), func(args []reflect.Value) (results []reflect.Value) {
for _, arg := range args {
fmt.Println(arg.Interface())
}
return nil
})
fmt.Println(fn.Type())
fn.Call([]reflect.Value{reflect.ValueOf(1024)})
}Output:
func(int)
1024Completamente Uguale
reflect.DeepEqual è una funzione fornita dal pacchetto di riflessione per determinare se due variabili sono completamente uguali. La firma è la seguente:
func DeepEqual(x, y any) boolQuesta funzione gestisce ogni tipo di base separatamente. Di seguito sono riportati alcuni modi di giudizio per tipo:
- Array: ogni elemento nell'array è completamente uguale
- Slice: quando entrambi sono
nil, sono giudicati completamente uguali, oppure quando entrambi non sono vuoti, gli elementi nell'intervallo di lunghezza sono completamente uguali - Struttura: tutti i campi sono completamente uguali
- Mappa: quando entrambi sono
nil, sono completamente uguali. Quando entrambi non sononil, ogni valore mappato da ogni chiave è completamente uguale - Puntatore: puntano allo stesso elemento o gli elementi puntati sono completamente uguali
- Interfaccia: quando il tipo concreto dell'interfaccia è completamente uguale
- Funzione: solo quando entrambi sono
nilsono completamente uguali, altrimenti non sono completamente uguali
Di seguito sono riportati alcuni esempi:
Slice
func main() {
a := make([]int, 100)
b := make([]int, 100)
fmt.Println(reflect.DeepEqual(a, b))
}Output:
trueStruttura
func main() {
mike := Person{
Name: "mike",
Age: 39,
Father: nil,
}
jack := Person{
Name: "jack",
Age: 18,
Father: &mike,
}
tom := Person{
Name: "tom",
Age: 18,
Father: &mike,
}
fmt.Println(reflect.DeepEqual(mike, jack))
fmt.Println(reflect.DeepEqual(tom, jack))
fmt.Println(reflect.DeepEqual(jack, jack))
}Output:
false
false
true