Tipos dinâmicos e valores dinâmicos de interfaces?
Você pode ver no código-fonte: iface
Ele contém dois campos: tab
um ponteiro de tabela de interface, que aponta para informações de tipo; data
e 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] nil
Compare 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 .动态值
nil
nil
接口值 == 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, c
o tipo dinâmico e o valor dinâmico de são ambos nil
, g
e nil
quando é atribuído g
a c
, c
o tipo dinâmico de torna-se *main.Gopher
, embora c
o valor dinâmico de ainda seja nil
, mas quando comparado com, o resultado c
é .nil
false
[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 MyError
estrutura, implementamos Error
a função e também implementamos error
a interface. Process
A função retorna uma error
interface, o que implica conversão de tipo. Portanto, embora seu valor seja nil
, na verdade seu tipo é *MyError
e, nil
quando 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)))
}
iface
Uma estrutura é definida diretamente no código , usando dois ponteiros para descrever itab
e 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; *int
finalmente, 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á *myWriter
se o tipo implementa io.Writer
a 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?
Go
A 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:
- Um tipo tem habilidades de vários tipos
- Permitir que diferentes objetos reajam de forma flexível à mesma mensagem
- tratar cada objeto usado de uma maneira comum
- 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 Person
interface, incluindo duas funções:
job()
growUp()
Em seguida, são definidas mais duas estruturas Student
e , ao mesmo tempo , Programmer
o tipo implementa as duas funções definidas pela interface. Observe que o tipo implementa a interface, mas o tipo não.*Student
Programmer
Person
*Student
Student
Depois disso, defini Person
duas funções cujos parâmetros de função são interfaces:
func whatJob(p Person)
func growUp(p Person)
main
Na função, os objetos de Student
e são gerados primeiro Programmer
e depois são passados para as funções whatJob
e 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 person
está vinculada ao tipo de entidade *Student
or Programmer
. De acordo com o código-fonte analisado anteriormente , a função salva iface
será 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.fun
s.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 itab
para fun
implementar 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 itab
em Go fun
sã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 é itab
Este não existe em C++ porque a derivação requer uma declaração explícita de qual classe base ela herda.