Tipos
Na seção anterior sobre tipos de dados, já apresentamos brevemente todos os tipos de dados embutidos em Go. Estes tipos básicos embutidos são a base para tipos personalizados subsequentes. Go é uma linguagem de tipos estáticos típica, onde os tipos de todas as variáveis são determinados durante a compilação e não mudam durante todo o ciclo de vida do programa. Esta seção apresentará brevemente o sistema de tipos de Go e seu uso básico.
Tipo Estático Forte
Go é uma linguagem de tipo estático forte. Estático significa que os tipos de todas as variáveis em Go já são determinados durante a compilação e não mudam durante o ciclo de vida do programa. Embora a declaração de variáveis curtas em Go seja um pouco semelhante à escrita em linguagens dinâmicas, seus tipos de variáveis são inferidos pelo compilador. A diferença mais fundamental é que uma vez que o tipo é inferido, ele não muda mais, enquanto linguagens dinâmicas são completamente opostas. Portanto, o código abaixo não pode ser compilado, porque a é uma variável do tipo int e não pode receber uma string:
func main() {
var a int = 64
a = "64"
fmt.Println(a) // cannot use "64" (untyped string constant) as int value in assignment
}Tipo forte significa executar verificação rigorosa de tipos no programa. Se ocorrer uma incompatibilidade de tipos, o programador será imediatamente informado de que não deve fazer isso, em vez de tentar inferir possíveis resultados como linguagens dinâmicas. Portanto, o código abaixo não pode ser compilado porque os dois tipos são diferentes e não podem ser operados:
func main() {
fmt.Println(1 + "1") // invalid operation: 1 + "1" (mismatched types untyped int and untyped string)
}Tipo Pós-fixado
Por que Go coloca a declaração de tipo depois em vez de antes? Em grande parte, aprendeu com a lição da linguagem C. Usando um exemplo oficial para mostrar o efeito, este é um ponteiro de função:
int (*(*fp)(int (*)(int, int), int))(int, int)Para ser honesto, é difícil saber que tipo é este sem olhar cuidadosamente. Uma escrita semelhante em Go é a seguinte:
f func(func(int,int) int, int) func(int, int) intO estilo de declaração de Go sempre segue o princípio de nome na frente e tipo atrás. Lendo da esquerda para a direita, podemos saber à primeira vista que esta é uma função e que o valor de retorno é func(int,int) int. Quando os tipos se tornam cada vez mais complexos, o tipo pós-fixado é muito melhor em legibilidade. Muitos aspectos do design de Go servem à legibilidade.
Declaração de Tipo
Em Go, através da declaração de tipo, podemos declarar um novo tipo com um nome personalizado. Declarar um novo tipo geralmente requer um nome de tipo e um tipo básico. Um exemplo simples é o seguinte:
type MyInt int64Na declaração de tipo acima, usamos a palavra-chave type para declarar um tipo chamado MyInt com tipo básico int64. Em Go, cada novo tipo declarado deve ter um tipo básico correspondente, e não é recomendado que o nome do tipo repita identificadores embutidos existentes.
type MyInt int64
type MyFloat64 float64
type MyMap map[string]int
// Pode compilar, mas não é recomendado usar, isso substituirá o tipo original
type int int64Tipos declarados através de declaração de tipo são todos novos tipos. Tipos diferentes não podem ser operados, mesmo que os tipos básicos sejam os mesmos.
type MyFloat64 float64
var f1 MyFloat64
var f float64
f1 = 0.2
f = 0.1
fmt.Println(f1 + f)invalid operation: f1 + f (mismatched types MyFloat64 and float64)Alias de Tipo
Alias de tipo é diferente de declaração de tipo. Alias de tipo é apenas um alias, não cria um novo tipo. Um exemplo simples é o seguinte:
type Int = intAmbos são o mesmo tipo, apenas os nomes são diferentes, então podem ser operados. Portanto, o exemplo abaixo naturalmente pode ser compilado:
type Int = int
var a Int = 1
var b int = 2
fmt.Println(a + b)3Alias de tipo é muito útil para tipos particularmente complexos. Por exemplo, agora temos um tipo map[string]map[string]int, que é um map bidimensional. Agora temos um parâmetro de função que é do tipo map[string]map[string]int, como segue:
func PrintMyMap(mymap map[string]map[string]int) {
fmt.Println(mymap)
}Neste caso, não é necessário usar declaração de tipo, porque o primeiro declara um novo tipo que não pode ser usado como parâmetro desta função. O exemplo após usar alias de tipo é o seguinte:
type TwoDMap = map[string]map[string]int
func PrintMyMap(mymap TwoDMap) {
fmt.Println(mymap)
}Usar alias de tipo parece mais conciso.
TIP
O tipo embutido any é um alias de tipo de interface{}, ambos são completamente equivalentes, apenas os nomes são diferentes.
Conversão de Tipo
Em Go, existe apenas conversão de tipo explícita, não existe conversão de tipo implícita. Portanto, variáveis de tipos diferentes não podem ser operadas e não podem ser passadas como parâmetros. A premissa para aplicar conversão de tipo é conhecer o tipo da variável a ser convertida e o tipo de destino para o qual converter. Um exemplo é o seguinte:
type MyFloat64 float64
var f1 MyFloat64
var f float64
f1 = 0.2
f = 0.1
fmt.Println(float64(f1) + f)0.30000000000000004Somente através da conversão explícita de MyFloat64 para o tipo float64 podemos realizar a operação de adição. Outra premissa para conversão de tipo é: o tipo a ser convertido deve poder ser representado pelo tipo de destino (Representability). Por exemplo, int pode ser representado pelo tipo int64 e também pode ser representado pelo tipo float64, então podem realizar conversão de tipo explícita entre si. Mas o tipo int não pode ser representado pelos tipos string e bool, portanto não pode realizar conversão de tipo.
TIP
Para a definição de Representability, vá para Referência da Linguagem - Representability para mais detalhes.
Mesmo que dois tipos possam se representar mutuamente, o resultado da conversão de tipo nem sempre está correto. Veja o exemplo abaixo:
var num1 int8 = 1
var num2 int32 = 512
fmt.Println(int32(num1), int8(num2))1 0num1 foi convertido corretamente para o tipo int32, mas num2 não. Este é um problema típico de overflow numérico. int32 pode representar inteiros de 31 bits, int8 só pode representar inteiros de 7 bits. Ao converter inteiros de alta precisão para inteiros de baixa precisão, os bits altos são descartados e os bits baixos são mantidos. Portanto, o resultado da conversão de num2 é 0. Na conversão de tipo de números, geralmente é recomendado converter de pequeno para grande, e não é recomendado converter de grande para pequeno.
Ao usar conversão de tipo, para alguns tipos é necessário evitar ambiguidade. Exemplos são os seguintes:
*Point(p) // Equivalente a *(Point(p))
(*Point)(p) // Converte p para o tipo *Point
<-chan int(c) // Equivalente a <-(chan int(c))
(<-chan int)(c) // Converte c para o tipo <-chan int
(func())(x) // Converte x para o tipo func()
(func() int)(x) // Converte x para o tipo func() intAssertiva de Tipo
Assertiva de tipo é geralmente usada para julgar se uma variável do tipo interface pertence a um determinado tipo. O exemplo é o seguinte:
var b int = 1
var a interface{} = b
if intVal, ok := a.(int); ok {
fmt.Println(intVal)
} else {
fmt.Println("error type")
}1Como interface{} é um tipo de interface vazia, o tipo de interface vazia pode representar todos os tipos, mas o tipo int não pode representar o tipo interface{}, então não pode usar conversão de tipo. A assertiva de tipo pode julgar se o tipo subjacente é o tipo desejado. A declaração de assertiva de tipo tem dois valores de retorno: um é o valor após a conversão de tipo e o outro é o valor booleano do resultado da conversão.
Julgamento de Tipo
Em Go, a declaração switch também suporta uma escrita especial. Através desta escrita, podemos fazer diferentes tratamentos lógicos de acordo com diferentes case. A premissa de uso é que o parâmetro de entrada deve ser do tipo interface. O exemplo é o seguinte:
var a interface{} = 2
switch a.(type) {
case int: fmt.Println("int")
case float64: fmt.Println("float")
case string: fmt.Println("string")
}intTIP
Através das operações fornecidas pelo pacote unsafe, podemos contornar o sistema de tipos de Go e realizar operações de conversão de tipo que originalmente não poderiam ser compiladas.
