Reflexão
Reflexão é um mecanismo que permite verificar a estrutura da própria linguagem em tempo de execução. Ela pode lidar com alguns problemas de forma flexível, mas também traz desvantagens óbvias, como problemas de desempenho, etc. Em Go, a reflexão está intimamente relacionada com interface{}. Em grande parte, onde quer que interface{} apareça, haverá reflexão. A API de reflexão em Go é fornecida pelo pacote padrão reflect.
Interfaces
Antes de começar, vamos entender brevemente duas interfaces localizadas no pacote runtime. Em Go, interfaces são essencialmente structs. O Go divide as interfaces em duas categorias principais em tempo de execução: uma categoria são interfaces sem conjuntos de métodos, e a outra categoria são interfaces com conjuntos de métodos. Para interfaces que contêm conjuntos de métodos, elas são representadas em tempo de execução pela struct iface a seguir:
type iface struct {
tab *itab // Contém tipo de dados, tipo de interface, conjunto de métodos, etc.
data unsafe.Pointer // Ponteiro para o valor
}Para interfaces sem conjuntos de métodos, elas são representadas em tempo de execução pela struct eface, como segue:
type eface struct {
_type *_type // Tipo
data unsafe.Pointer // Ponteiro para o valor
}Ambas estas structs têm tipos de struct correspondentes no pacote reflect. iface corresponde a nonEmptyInterface:
type nonEmptyInterface struct {
itab *struct {
ityp *rtype // Tipo de interface estática
typ *rtype // Tipo concreto dinâmico
hash uint32 // Hash do tipo
_ [4]byte
fun [100000]unsafe.Pointer // Conjunto de métodos
}
word unsafe.Pointer // Ponteiro para o valor
}E eface corresponde a emptyInterface:
type emptyInterface struct {
typ *rtype // Tipo concreto dinâmico
word unsafe.Pointer // Valor apontado pelo ponteiro
}Para estes dois tipos, a definição oficial é muito clara:
nonEmptyInterface: nonEmptyInterface is the header for an interface value with methodsemptyInterface: emptyInterface is the header for an interface{} value
Acima foi mencionado o termo "tipo concreto dinâmico",原文为dynamic concrete type. Primeiro, Go é uma linguagem de tipo 100% estática. O termo "estático" é refletido no fato de que o tipo de interface abstrata manifestado externamente é imutável, enquanto "dinâmico" significa que o tipo de implementação concreta armazenado na base da interface pode mudar. Até aqui, entender o princípio simples das interfaces até este ponto é suficiente para satisfazer o aprendizado subsequente de reflexão.
Ponte
No pacote reflect, há o tipo de interface reflect.Type para representar tipos em Go, e o tipo de struct reflect.Value para representar valores em Go:
type Type interface {
...
Name() string
PkgPath() string
Size() uintptr
String() string
Kind() Kind
...
}
type Value struct {
typ *rtype
ptr unsafe.Pointer
flag
}O código acima omite muitos detalhes. Por enquanto, basta saber da existência destes dois tipos. Todas as operações relacionadas à reflexão em Go são baseadas nestes dois tipos. O pacote reflect fornece duas funções para converter tipos em Go nos dois tipos acima para realizar operações de reflexão: a função reflect.TypeOf:
func TypeOf(i any) TypeE a função reflect.ValueOf:
func ValueOf(i any) ValuePodemos ver que os tipos de parâmetros de ambas as funções são any, que é um alias de interface{}. Se quisermos realizar operações de reflexão, precisamos primeiro converter seu tipo para interface{}. É por isso que foi mencionado anteriormente que não há reflexão sem interface vazia. De forma não rigorosa, a interface vazia é a ponte que conecta o sistema de tipos do Go com a reflexão. A figura abaixo descreve vividamente este processo.

TIP
No texto a seguir, para conveniência, usaremos uniformemente o alias any para substituir interface{}.
Núcleo
Em Go, há três leis clássicas de reflexão. Combinando com o conteúdo explicado acima, elas são muito fáceis de entender:
- A reflexão pode converter variáveis do tipo
interface{}em objetos de reflexão - A reflexão pode restaurar objetos de reflexão em variáveis do tipo
interface{} - Para modificar um objeto de reflexão, seu valor deve ser definível
Estas três leis são o núcleo da reflexão em Go. Quando precisamos acessar informações relacionadas ao tipo, precisamos usar reflect.TypeOf. Quando precisamos modificar valores de reflexão, precisamos usar reflect.ValueOf.
Tipo
reflect.Type representa tipos em Go. Usando a função reflect.TypeOf(), podemos converter uma variável em reflect.Type. O exemplo de código é o seguinte:
func main() {
str := "hello world!"
reflectType := reflect.TypeOf(str)
fmt.Println(reflectType)
}O resultado é:
stringKind
Para Type, o Go usa internamente reflect.Kind para representar tipos básicos em Go. Sua essência é um inteiro sem sinal uint:
type Kind uintO pacote reflect enumera todos os tipos básicos em Go usando Kind, como mostrado abaixo:
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
)O tipo Kind implementa apenas o método String() da interface Stringer. Este tipo tem apenas este método. O valor de retorno do método String() vem de um slice interno, como mostrado abaixo. Esta写法 parece muito com map à primeira vista, mas na verdade é uma escrita especial em Go: sintaxe de inicialização de índice (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
}Através de Kind, podemos saber qual tipo básico o valor armazenado na interface vazia é. Por exemplo:
func main() {
// Declara uma variável do tipo any
var eface any
// Atribui valor
eface = 100
// Obtém seu tipo através do método Kind
fmt.Println(reflect.TypeOf(eface).Kind())
}Resultado:
intElem
type Type interface{
Elem() Type
}Usando o método Type.Elem(), podemos determinar o tipo de elemento armazenado por uma estrutura de dados do tipo any. Os tipos de parâmetros subjacentes aceitáveis devem ser um dos seguintes: ponteiro, slice, array, canal, mapa. Caso contrário, ocorrerá panic. Aqui está um exemplo de código:
func main() {
var eface any
eface = map[string]int{}
rType := reflect.TypeOf(eface)
// key() retorna o tipo de reflexão da chave do map
fmt.Println(rType.Key().Kind())
fmt.Println(rType.Elem().Kind())
}Output:
string
intUm ponteiro também pode ser entendido como um contêiner. Para ponteiros, usar Elem() obterá o tipo de reflexão do elemento apontado. Exemplo de código:
func main() {
var eface any
// Atribui ponteiro
eface = new(strings.Builder)
rType := reflect.TypeOf(eface)
// Obtém o tipo de reflexão do elemento apontado pelo ponteiro
vType := rType.Elem()
// Output do caminho do pacote
fmt.Println(vType.PkgPath())
// Output do seu nome
fmt.Println(vType.Name())
}strings
BuilderPara arrays, slices e canais, o uso é semelhante.
Size
type Type interface{
Size() uintptr
}Através do método Size, podemos obter o tamanho em bytes ocupado pelo tipo correspondente. Exemplo:
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())
}Resultados:
8
16
16
8
24TIP
Usar unsafe.Sizeof() pode alcançar o mesmo efeito.
Comparable
type Type interface{
Comparable() bool
}Através do método Comparable, podemos determinar se um tipo pode ser comparado. Exemplo:
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
}Através do método Implements, podemos determinar se um tipo implementa uma determinada interface:
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))
}Resultado:
true
falseConvertibleTo
type Type interface{
ConvertibleTo(u Type) bool
}Usando o método ConvertibleTo, podemos determinar se um tipo pode ser convertido para outro tipo especificado:
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
falseValor
reflect.Value representa o valor da interface de reflexão. Usando a função reflect.ValueOf(), podemos converter uma variável em reflect.Value. Exemplo de código:
func main() {
str := "hello world!"
reflectValue := reflect.ValueOf(str)
fmt.Println(reflectValue)
}Resultado:
hello world!Type
func (v Value) Type() TypeO método Type pode obter o tipo de um valor de reflexão:
func main() {
num := 114514
rValue := reflect.ValueOf(num)
fmt.Println(rValue.Type())
}Output:
intElem
func (v Value) Elem() ValueObtém o valor de reflexão do elemento de um valor de reflexão:
func main() {
num := new(int)
*num = 114514
// Usando ponteiro como exemplo
rValue := reflect.ValueOf(num).Elem()
fmt.Println(rValue.Interface())
}Output:
114514Ponteiros
Há duas maneiras de obter o ponteiro de um valor de reflexão:
// Retorna um valor de reflexão de ponteiro representando o endereço de v
func (v Value) Addr() Value
// Retorna um uintptr apontando para o valor original de v, equivalente a uintptr(Value.Addr().UnsafePointer())
func (v Value) UnsafeAddr() uintptr
// Retorna um uintptr apontando para o valor original de v
// Apenas quando o Kind de v for Chan, Func, Map, Pointer, Slice, UnsafePointer, caso contrário panic
func (v Value) Pointer() uintptr
// Retorna um unsafe.Pointer apontando para o valor original de v
// Apenas quando o Kind de v for Chan, Func, Map, Pointer, Slice, UnsafePointer, caso contrário panic
func (v Value) UnsafePointer() unsafe.PointerExemplo:
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 refletirá o tipo do parâmetro. Se for do tipo reflect.Value, chamará automaticamente Value.Interface() para obter seu valor original.
Usando um map como exemplo:
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 0xc00010e4b0Definir Valor
func (v Value) Set(x Value)Se quisermos modificar um valor de reflexão através de reflexão, seu valor deve ser endereçável. Neste caso, devemos modificar o valor do elemento através de um ponteiro, em vez de tentar modificar o valor do elemento diretamente.
func main() {
// *int
num := new(int)
*num = 114514
rValue := reflect.ValueOf(num)
// Obtém o elemento apontado pelo ponteiro
ele := rValue.Elem()
fmt.Println(ele.Interface())
ele.SetInt(11)
fmt.Println(ele.Interface())
}Output:
114514
11Obter Valor
func (v Value) Interface() (i any)Através do método Interface(), podemos obter o valor original do valor de reflexão:
func main() {
var str string
str = "hello"
rValue := reflect.ValueOf(str)
if v, ok := rValue.Interface().(string); ok {
fmt.Println(v)
}
}Output:
helloFunção
Através de reflexão, podemos obter todas as informações de uma função e também chamar funções via reflexão.
Informações
Obter todas as informações de uma função através do tipo de reflexão:
func Max(a, b int) int {
if a > b {
return a
}
return b
}
func main() {
rType := reflect.TypeOf(Max)
// Output do nome da função, tipos de função literais não têm nome
fmt.Println(rType.Name())
// Output do número de parâmetros e valores de retorno
fmt.Println(rType.NumIn(), rType.NumOut())
rParamType := rType.In(0)
// Output do tipo do primeiro parâmetro
fmt.Println(rParamType.Kind())
rResType := rType.Out(0)
// Output do tipo do primeiro valor de retorno
fmt.Println(rResType.Kind())
}Output:
2 1
int
intChamada
Chamar uma função através de um valor de reflexão:
func (v Value) Call(in []Value) []Valuefunc main() {
// Obtém o valor de reflexão da função
rType := reflect.ValueOf(Max)
// Passa o array de parâmetros
rResValue := rType.Call([]reflect.Value{reflect.ValueOf(18), reflect.ValueOf(50)})
for _, value := range rResValue {
fmt.Println(value.Interface())
}
}Output:
50Struct
Suponha que temos a seguinte struct:
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
}Acessar Campos
A estrutura de reflect.StructField é a seguinte:
type StructField struct {
// Nome do campo
Name string
// Nome do pacote
PkgPath string
// Nome do tipo
Type Type
// Tag
Tag StructTag
// Offset em bytes do campo
Offset uintptr
// Índice
Index []int
// Se é um campo aninhado
Anonymous bool
}Há duas maneiras de acessar campos de uma struct: uma é através de índice, e a outra é através de nome.
type Type interface{
Field(i int) StructField
}Exemplo de acesso através de índice:
func main() {
rType := reflect.TypeOf(new(Person)).Elem()
// Output do número de campos da struct
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)
}Exemplo de acesso através de nome:
func main() {
rType := reflect.TypeOf(new(Person)).Elem()
// Output do número de campos da struct
fmt.Println(rType.NumField())
if field, ok := rType.FieldByName("money"); ok {
fmt.Println(field.Name, field.Type, field.IsExported())
}
}Output:
4
money int falseModificar Campos
Se quisermos modificar o valor de um campo de struct, devemos passar um ponteiro para a struct. Aqui está um exemplo de modificação de campo:
func main() {
// Passa ponteiro
rValue := reflect.ValueOf(&Person{
Name: "",
Age: 0,
Address: "",
money: 0,
}).Elem()
// Obtém o campo
name := rValue.FieldByName("Name")
// Modifica o valor do campo
if (name != reflect.Value{}) { // Se retornar reflect.Value{}, significa que o campo não existe
name.SetString("jack")
}
// Output da struct
fmt.Println(rValue.Interface())
}Output:
{jack 0 0}Para modificar campos privados de uma struct, são necessárias algumas operações extras, como segue:
func main() {
// Passa ponteiro
rValue := reflect.ValueOf(&Person{
Name: "",
Age: 0,
Address: "",
money: 0,
}).Elem()
// Obtém um campo privado
money := rValue.FieldByName("money")
// Modifica o valor do campo
if (money != reflect.Value{}) {
// Constrói um valor de reflexão de ponteiro apontando para o campo não exportado da struct
p := reflect.NewAt(money.Type(), money.Addr().UnsafePointer())
// Obtém o elemento apontado por este ponteiro, que é o campo a ser modificado
field := p.Elem()
// Modifica o valor
field.SetInt(164)
}
// Output da struct
fmt.Printf("%+v\n", rValue.Interface())
}Acessar Tag
Após obter StructField, podemos acessar sua Tag diretamente:
// Se não existir, ok é false
func (tag StructTag) Lookup(key string) (value string, ok bool)
// Se não existir, retorna string vazia
func (tag StructTag) Get(key string) stringExemplo:
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
nameAcessar Métodos
Acessar métodos é muito semelhante ao processo de acessar campos, apenas as assinaturas das funções são ligeiramente diferentes. A struct reflect.Method é a seguinte:
type Method struct {
// Nome do método
Name string
// Nome do pacote
PkgPath string
// Tipo do método
Type Type
// Função correspondente ao método, o primeiro parâmetro é o receiver
Func Value
// Índice
Index int
}Exemplo de acesso a informações de método:
func main() {
// Obtém o tipo de reflexão da struct
rType := reflect.TypeOf(new(Person)).Elem()
// Output do número de métodos
fmt.Println(rType.NumMethod())
// Itera e output das informações dos métodos
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 quisermos obter detalhes dos parâmetros e valores de retorno do método, podemos obtê-los através de Method.Func. O processo é o mesmo que acessar informações de função. Modificando ligeiramente o código acima:
func main() {
// Obtém o tipo de reflexão da struct
rType := reflect.TypeOf(new(Person)).Elem()
// Output do número de métodos
fmt.Println(rType.NumMethod())
// Itera e output das informações dos métodos
for i := 0; i < rType.NumMethod(); i++ {
method := rType.Method(i)
fmt.Println(method.Index, method.Name, method.Type, method.IsExported())
fmt.Println("Parâmetros do método")
for i := 0; i < method.Func.Type().NumIn(); i++ {
fmt.Println(method.Func.Type().In(i).String())
}
fmt.Println("Valores de retorno do método")
for i := 0; i < method.Func.Type().NumOut(); i++ {
fmt.Println(method.Func.Type().Out(i).String())
}
}
}Podemos ver que o primeiro parâmetro é main.Person, que é o tipo do receiver:
1
0 Talk func(main.Person, string) string true
Parâmetros do método
main.Person
string
Valores de retorno do método
stringChamar Métodos
Chamar métodos é semelhante ao processo de chamar funções, e não é necessário passar o receiver manualmente. Exemplo:
func main() {
// Obtém o tipo de reflexão da struct
rValue := reflect.ValueOf(new(Person)).Elem()
// Output do número de métodos
fmt.Println(rValue.NumMethod())
// Itera e output das informações dos métodos
talk := rValue.MethodByName("Talk")
if (talk != reflect.Value{}) {
// Chama o método e obtém o valor de retorno
res := talk.Call([]reflect.Value{reflect.ValueOf("hello,reflect!")})
// Itera e output dos valores de retorno
for _, re := range res {
fmt.Println(re.Interface())
}
}
}Output:
1
hello,reflect!Criação
Através de reflexão, podemos construir novos valores. O pacote reflect também fornece funções mais convenientes para alguns tipos especiais.
Tipos Básicos
// Retorna um valor de reflexão de ponteiro apontando para o valor de reflexão
func New(typ Type) ValueUsando string como exemplo:
func main() {
rValue := reflect.New(reflect.TypeOf(*new(string)))
rValue.Elem().SetString("hello world!")
fmt.Println(rValue.Elem().Interface())
}hello world!Struct
A criação de structs também usa a função 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() {
// Cria valor de reflexão de struct
rType := reflect.TypeOf(new(Person)).Elem()
person := reflect.New(rType).Elem()
fmt.Println(person.Interface())
}Output:
{ 0 0}Slice
Criar slice via reflexão:
func MakeSlice(typ Type, len, cap int) Valuefunc main() {
// Cria valor de reflexão de slice
rValue := reflect.MakeSlice(reflect.TypeOf(*new([]int)), 10, 10)
// Itera e atribui valores
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
Criar Map via reflexão:
func MakeMapWithSize(typ Type, n int) Valuefunc main() {
// Constrói valor de reflexão de map
rValue := reflect.MakeMapWithSize(reflect.TypeOf(*new(map[string]int)), 10)
// Define valor
rValue.SetMapIndex(reflect.ValueOf("a"), reflect.ValueOf(1))
fmt.Println(rValue.Interface())
}map[a:1]Canal
Criar canal via reflexão:
func MakeChan(typ Type, buffer int) Valuefunc main() {
// Cria valor de reflexão de canal
makeChan := reflect.MakeChan(reflect.TypeOf(new(chan int)).Elem(), 0)
fmt.Println(makeChan.Interface())
}Função
Criar função via reflexão:
func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Valuefunc main() {
// Passa o tipo de wrapper e o corpo da função
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)
1024Igualdade Completa
reflect.DeepEqual é uma função fornecida pelo pacote de reflexão para determinar se duas variáveis são completamente iguais. A assinatura é a seguinte:
func DeepEqual(x, y any) boolEsta função lida com cada tipo básico. Aqui estão algumas formas de julgamento de tipo:
- Array: Cada elemento no array é completamente igual
- Slice: Ambos são
nil, julgados como completamente iguais, ou ambos não estão vazios e os elementos dentro do comprimento são completamente iguais - Struct: Todos os campos são completamente iguais
- Map: Ambos são
nil, são completamente iguais. Se ambos não sãonil, cada valor mapeado por cada chave é completamente igual - Ponteiro: Apontam para o mesmo elemento ou os elementos apontados são completamente iguais
- Interface: Quando os tipos concretos das interfaces são completamente iguais
- Função: Apenas quando ambos são
nilsão completamente iguais, caso contrário não são completamente iguais
Aqui estão alguns exemplos:
Slice
func main() {
a := make([]int, 100)
b := make([]int, 100)
fmt.Println(reflect.DeepEqual(a, b))
}Output:
trueStruct
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