A essência da entrevista em linguagem GO - tipo dinâmico e valor dinâmico da interface?

Tipos dinâmicos e valores dinâmicos de interfaces?

Você pode ver no código-fonte: ifaceEle contém dois campos: tabum ponteiro de tabela de interface, que aponta para informações de tipo; datae um ponteiro de dados, que aponta para dados específicos. Eles são chamados respectivamente 动态类型de e 动态值. E os valores da interface incluem 动态类型e 动态值.

[Extensão 1] nilCompare o tipo de interface com

Um valor zero para um valor de interface significa ambos 动态类型e . Este valor de interface será considerado somente se e somente se os valores de ambas as partes forem .动态值nilnil接口值 == nil

Vejamos um exemplo:

package main

import "fmt"

type Coder interface {
	code()
}

type Gopher struct {
	name string
}

func (g Gopher) code() {
	fmt.Printf("%s is coding\n", g.name)
}

func main() {
	var c Coder
	fmt.Println(c == nil)
	fmt.Printf("c: %T, %v\n", c, c)

	var g *Gopher
	fmt.Println(g == nil)

	c = g
	fmt.Println(c == nil)
	fmt.Printf("c: %T, %v\n", c, c)
}

saída:

true
c: <nil>, <nil>
true
false
c: *main.Gopher, <nil>

No início, co tipo dinâmico e o valor dinâmico de são ambos nil, ge nilquando é atribuído ga c, co tipo dinâmico de torna-se *main.Gopher, embora co valor dinâmico de ainda seja nil, mas quando comparado com, o resultado cé .nilfalse

[Extensão 2]
Vamos dar uma olhada em um exemplo e seu resultado:

package main

import "fmt"

type MyError struct {}

func (i MyError) Error() string {
	return "MyError"
}

func main() {
	err := Process()
	fmt.Println(err)

	fmt.Println(err == nil)
}

func Process() error {
	var err *MyError = nil
	return err
}

Resultado da execução da função:

<nil>
false

Aqui primeiro definimos uma MyErrorestrutura, implementamos Errora função e também implementamos errora interface. ProcessA função retorna uma errorinterface, o que implica conversão de tipo. Portanto, embora seu valor seja nil, na verdade seu tipo é *MyErrore, nilquando finalmente comparado com , o resultado é false.

[Extensão 3] Como imprimir o tipo dinâmico e o valor da interface?

Olhe diretamente para o código:

package main

import (
	"unsafe"
	"fmt"
)

type iface struct {
	itab, data uintptr
}

func main() {
	var a interface{} = nil

	var b interface{} = (*int)(nil)

	x := 5
	var c interface{} = (*int)(&x)
	
	ia := *(*iface)(unsafe.Pointer(&a))
	ib := *(*iface)(unsafe.Pointer(&b))
	ic := *(*iface)(unsafe.Pointer(&c))

	fmt.Println(ia, ib, ic)

	fmt.Println(*(*int)(unsafe.Pointer(ic.data)))
}

ifaceUma estrutura é definida diretamente no código , usando dois ponteiros para descrever itabe data, e então o conteúdo de a, b, c na memória é forçado a ser interpretado como nosso iface. Finalmente, você pode imprimir os endereços de tipos dinâmicos e valores dinâmicos.

Os resultados da execução são os seguintes:

{
    
    0 0} {
    
    17426912 0} {
    
    17426912 842350714568}
5

O tipo dinâmico e o endereço do valor dinâmico de a são ambos 0, que é nulo; o tipo dinâmico de b é igual ao tipo dinâmico de c; *intfinalmente, o valor dinâmico de c é 5.

O compilador detecta automaticamente se o tipo implementa a interface

Muitas vezes vejo alguns usos estranhos semelhantes aos seguintes em algumas bibliotecas de código aberto:

var _ io.Writer = (*myWriter)(nil)

Nesse momento você ficará um pouco confuso e não saberá o que o autor quer fazer. Na verdade, esta é a resposta para esta pergunta. O compilador verificará *myWriterse o tipo implementa io.Writera interface.

Vejamos um exemplo:

package main

import "io"

type myWriter struct {

}

/*func (w myWriter) Write(p []byte) (n int, err error) {
	return
}*/

func main() {
    // 检查 *myWriter 类型是否实现了 io.Writer 接口
    var _ io.Writer = (*myWriter)(nil)

    // 检查 myWriter 类型是否实现了 io.Writer 接口
    var _ io.Writer = myWriter{}
}

Após comentar a função Write definida para myWriter, execute o programa:

src/main.go:14:6: cannot use (*myWriter)(nil) (type *myWriter) as type io.Writer in assignment:
	*myWriter does not implement io.Writer (missing Write method)
src/main.go:15:6: cannot use myWriter literal (type myWriter) as type io.Writer in assignment:
	myWriter does not implement io.Writer (missing Write method)

Mensagem de erro: *myWriter/myWriter não implementa a interface io.Writer, ou seja, o método Write não está implementado.

Após remover o comentário, nenhum erro será relatado ao executar o programa.

Na verdade, a instrução de atribuição acima causará conversão implícita de tipo.Durante o processo de conversão, o compilador detectará se o tipo no lado direito do sinal de igual implementa a função especificada pela interface no lado esquerdo do sinal de igual.

Para resumir, você pode verificar se o tipo implementa a interface adicionando um código semelhante ao seguinte:

var _ io.Writer = (*myWriter)(nil)
var _ io.Writer = myWriter{}

Como usar a interface para obter polimorfismo?

GoA linguagem não foi projetada com conceitos como funções virtuais, funções virtuais puras, herança, herança múltipla, etc., mas suporta recursos orientados a objetos de maneira muito elegante por meio de interfaces.

Polimorfismo é um comportamento em tempo de execução que possui as seguintes características:

  1. Um tipo tem habilidades de vários tipos
  2. Permitir que diferentes objetos reajam de forma flexível à mesma mensagem
  3. tratar cada objeto usado de uma maneira comum
  4. Linguagens não dinâmicas devem ser implementadas por meio de herança e interfaces

Veja um exemplo de código que implementa polimorfismo:

package main

import "fmt"

func main() {
	qcrao := Student{age: 18}
	whatJob(&qcrao)

	growUp(&qcrao)
	fmt.Println(qcrao)

	stefno := Programmer{age: 100}
	whatJob(stefno)

	growUp(stefno)
	fmt.Println(stefno)
}

func whatJob(p Person) {
	p.job()
}

func growUp(p Person) {
	p.growUp()
}

type Person interface {
	job()
	growUp()
}

type Student struct {
	age int
}

func (p Student) job() {
	fmt.Println("I am a student.")
	return
}

func (p *Student) growUp() {
	p.age += 1
	return
}

type Programmer struct {
	age int
}

func (p Programmer) job() {
	fmt.Println("I am a programmer.")
	return
}

func (p Programmer) growUp() {
	// 程序员老得太快 ^_^
	p.age += 10
	return
}

O código primeiro define uma Personinterface, incluindo duas funções:

job()
growUp()

Em seguida, são definidas mais duas estruturas Studente , ao mesmo tempo , Programmero tipo implementa as duas funções definidas pela interface. Observe que o tipo implementa a interface, mas o tipo não.*StudentProgrammerPerson*StudentStudent

Depois disso, defini Personduas funções cujos parâmetros de função são interfaces:

func whatJob(p Person)
func growUp(p Person)

mainNa função, os objetos de Studente são gerados primeiro Programmere depois são passados ​​​​para as funções whatJobe respectivamente growUp. Na função, a função de interface é chamada diretamente. Durante a execução real, depende do tipo de entidade que é finalmente passado e a função implementada pelo tipo de entidade é chamada. Como resultado, diferentes objetos podem ter múltiplas representações da mesma mensagem, 多态o que é conseguido.

Para ir um pouco mais fundo, dentro da função whatJob()or growUp(), a interface personestá vinculada ao tipo de entidade *Studentor Programmer. De acordo com o código-fonte analisado anteriormente , a função salva ifaceserá chamada diretamente aqui , semelhante a:, e como o array armazena funções implementadas por tipos de entidade, quando a função passa em diferentes tipos de entidade, as chamadas são, na verdade, diferentes implementações de função, alcançando assim o polimorfismo.funs.tab->fun[0]fun

Execute o código:

I am a student.
{
    
    19}
I am a programmer.
{
    
    100}

Quais são as semelhanças e diferenças entre as interfaces Go e as interfaces C++?

Uma interface define uma especificação que descreve o comportamento e as funções de uma classe sem implementação específica.

As interfaces C++ são implementadas usando classes abstratas. Se pelo menos uma função na classe for declarada como uma função virtual pura, a classe será uma classe abstrata. Funções virtuais puras são especificadas usando "= 0" na declaração. Por exemplo:

class Shape
{
   public:
      // 纯虚函数
      virtual double getArea() = 0;
   private:
      string name;      // 名称
};

O objetivo de projetar uma classe abstrata é fornecer uma classe base apropriada da qual outras classes possam herdar. Uma classe abstrata não pode ser usada para instanciar objetos, ela só pode ser usada como interface.

Uma classe derivada precisa declarar explicitamente que herda da classe base e precisa implementar todas as funções virtuais puras na classe base.

A forma como C++ define interfaces é chamada de "intrusiva", enquanto Go usa um método "não intrusivo", que não requer declaração explícita. Você só precisa implementar as funções definidas pela interface, e o compilador irá reconhecê-la automaticamente.

A diferença na forma como C++ e Go definem interfaces também leva a diferenças na implementação subjacente. C++ usa tabelas de funções virtuais para implementar classes base que chamam funções de classes derivadas; enquanto Go usa campos itabpara funimplementar variáveis ​​de interface que chamam funções de tipos de entidade. A tabela de funções virtuais em C++ é gerada em tempo de compilação; enquanto os campos itabem Go funsão gerados dinamicamente durante o tempo de execução. A razão é que os tipos de entidade em Go podem implementar inadvertidamente N múltiplas interfaces. Muitas interfaces não são originalmente necessárias, portanto, uma não pode ser gerada para todas as interfaces implementadas pelo tipo. Este é também o impacto de "não intrusivo"; isto é itabEste não existe em C++ porque a derivação requer uma declaração explícita de qual classe base ela herda.

Acho que você gosta

Origin blog.csdn.net/zy_dreamer/article/details/132795705
Recomendado
Clasificación