Tipos
En la sección anterior sobre tipos de datos, ya se introdujeron brevemente todos los tipos de datos incorporados en Go. Estos tipos básicos incorporados son la base para los tipos personalizados posteriores. Go es un lenguaje de tipos estáticos típico, donde los tipos de todas las variables se determinan durante la compilación y no cambian durante todo el ciclo de vida del programa. Esta sección presentará brevemente el sistema de tipos de Go y su uso básico.
Tipado estático fuerte
Go es un lenguaje de tipado estático fuerte. "Estático" significa que los tipos de todas las variables en Go se determinan durante la compilación y no cambian durante el ciclo de vida del programa. Aunque la declaración de variables cortas en Go se parece un poco a la sintaxis de los lenguajes dinámicos, los tipos de sus variables son inferidos por el compilador. La diferencia fundamental es que una vez que se infiere el tipo, no cambia, mientras que en los lenguajes dinámicos es completamente lo contrario. Por lo tanto, el siguiente código no puede compilarse, porque a es una variable de tipo int y no se le puede asignar una cadena.
func main() {
var a int = 64
a = "64"
fmt.Println(a) // cannot use "64" (untyped string constant) as int value in assignment
}"Fuerte" significa que se realiza una verificación estricta de tipos en el programa. Si hay una incompatibilidad de tipos, se le informará inmediatamente al programador que no debería hacer esto, en lugar de intentar inferir posibles resultados como lo hacen los lenguajes dinámicos. Por lo tanto, el siguiente código no puede compilarse porque los tipos son diferentes y no se pueden realizar operaciones.
func main() {
fmt.Println(1 + "1") // invalid operation: 1 + "1" (mismatched types untyped int and untyped string)
}Tipo posfijo
¿Por qué Go coloca la declaración del tipo después en lugar de antes? En gran medida, aprendió de las lecciones del lenguaje C. Tomemos un ejemplo oficial para mostrar el efecto, este es un puntero a función:
int (*(*fp)(int (*)(int, int), int))(int, int)Para ser honesto, es difícil saber qué tipo es este si no lo lees cuidadosamente. Una escritura similar en Go sería:
f func(func(int,int) int, int) func(int, int) intLa forma de declaración de Go siempre sigue el principio de nombre primero, tipo después. Leyendo de izquierda a derecha, puedes saber a primera vista que esta es una función, y el valor de retorno es func(int,int) int. Cuando los tipos se vuelven más complejos, el tipo posfijo es mucho mejor en términos de legibilidad. El diseño de Go en muchos niveles sirve para la legibilidad.
Declaración de tipos
En Go, a través de la declaración de tipos, puedes declarar un nuevo tipo con un nombre personalizado. Declarar un nuevo tipo generalmente requiere un nombre de tipo y un tipo base. Un ejemplo simple es el siguiente:
type MyInt int64En la declaración de tipo anterior, se declara un tipo llamado MyInt con tipo base int64 usando la palabra clave type. En Go, cada nuevo tipo declarado debe tener un tipo base correspondiente, y no se recomienda que el nombre del tipo se repita con identificadores incorporados existentes.
type MyInt int64
type MyFloat64 float64
type MyMap map[string]int
// Puede compilarse, pero no se recomienda usar, esto sobrescribirá el tipo original
type int int64Los tipos declarados mediante declaración de tipos son todos nuevos tipos. Diferentes tipos no se pueden usar en operaciones, incluso si el tipo base es el mismo.
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 tipos
Los alias de tipos son diferentes de las declaraciones de tipos. Un alias de tipo es solo un alias, no crea un nuevo tipo. Un ejemplo simple es el siguiente:
type Int = intAmbos son el mismo tipo, solo se llaman de manera diferente, por lo que se pueden usar en operaciones. Por lo tanto, el siguiente ejemplo naturalmente puede compilarse.
type Int = int
var a Int = 1
var b int = 2
fmt.Println(a + b)3Los alias de tipos son muy útiles para tipos particularmente complejos. Por ejemplo, ahora hay un tipo map[string]map[string]int, que es un mapa bidimensional. Ahora hay una función cuyo parámetro es de tipo map[string]map[string]int, como sigue:
func PrintMyMap(mymap map[string]map[string]int) {
fmt.Println(mymap)
}En este caso, no es necesario usar una declaración de tipo, porque la primera declara un nuevo tipo que no se puede usar como parámetro de la función. El ejemplo después de usar un alias de tipo es el siguiente:
type TwoDMap = map[string]map[string]int
func PrintMyMap(mymap TwoDMap) {
fmt.Println(mymap)
}Usar un alias de tipo hace que se vea más conciso.
TIP
El tipo incorporado any es un alias de tipo de interface{}, ambos son completamente equivalentes, solo el nombre es diferente.
Conversión de tipos
En Go, solo existen conversiones de tipos explícitas, no existen conversiones de tipos implícitas. Por lo tanto, las variables de diferentes tipos no se pueden usar en operaciones y no se pueden pasar como parámetros. La premisa para aplicar la conversión de tipos es conocer el tipo de la variable que se va a convertir y el tipo de destino al que se va a convertir. Un ejemplo es el siguiente:
type MyFloat64 float64
var f1 MyFloat64
var f float64
f1 = 0.2
f = 0.1
fmt.Println(float64(f1) + f)0.30000000000000004Solo se puede realizar la operación de suma convirtiendo explícitamente MyFloat64 a tipo float64. Otra premisa para la conversión de tipos es: el tipo que se va a convertir debe poder ser representado por el tipo de destino (Representability). Por ejemplo, int puede ser representado por el tipo int64 y también puede ser representado por el tipo float64, por lo que se pueden realizar conversiones de tipos explícitas entre ellos. Pero el tipo int no puede ser representado por los tipos string y bool, por lo que no se pueden realizar conversiones de tipos.
TIP
Para la definición de Representability, puedes ir a Referencia del lenguaje - Representability para obtener más detalles.
Incluso si dos tipos pueden representarse entre sí, el resultado de la conversión de tipos no siempre es correcto. Mira el siguiente ejemplo:
var num1 int8 = 1
var num2 int32 = 512
fmt.Println(int32(num1), int8(num2))1 0num1 se convierte correctamente al tipo int32, pero num2 no. Este es un problema típico de desbordamiento numérico. int32 puede representar enteros de 31 bits, int8 solo puede representar enteros de 7 bits. Cuando los enteros de alta precisión se convierten a enteros de baja precisión, se descartan los bits altos y se conservan los bits bajos, por lo que el resultado de la conversión de num1 es 0. En la conversión de tipos numéricos, generalmente se recomienda convertir de pequeño a grande, y no se recomienda convertir de grande a pequeño.
Al usar la conversión de tipos, para algunos tipos es necesario evitar la ambigüedad. Ejemplos como los siguientes:
*Point(p) // Equivalente a *(Point(p))
(*Point)(p) // Convierte p al tipo *Point
<-chan int(c) // Equivalente a <-(chan int(c))
(<-chan int)(c) // Convierte c al tipo <-chan int
(func())(x) // Convierte x al tipo func()
(func() int)(x) // Convierte x al tipo func() intAserción de tipos
La aserción de tipos se usa generalmente para determinar si una variable de tipo de interfaz pertenece a un tipo específico. Un ejemplo es el siguiente:
var b int = 1
var a interface{} = b
if intVal, ok := a.(int); ok {
fmt.Println(intVal)
} else {
fmt.Println("error type")
}1Dado que interface{} es un tipo de interfaz vacía, el tipo de interfaz vacía puede representar todos los tipos, pero el tipo int no puede representar el tipo interface{}, por lo que no se puede usar la conversión de tipos. La aserción de tipos puede determinar si el tipo subyacente es el tipo deseado. La declaración de aserción de tipos tiene dos valores de retorno: uno es el valor después de la conversión de tipos y el otro es un valor booleano del resultado de la conversión.
Determinación de tipos
En Go, la declaración switch también admite una escritura especial. A través de esta escritura, se puede realizar un procesamiento lógico diferente según diferentes case. La premisa para usar es que el parámetro de entrada debe ser de tipo de interfaz. Un ejemplo es el siguiente:
var a interface{} = 2
switch a.(type) {
case int: fmt.Println("int")
case float64: fmt.Println("float")
case string: fmt.Println("string")
}intTIP
A través de las operaciones proporcionadas por el paquete unsafe, puedes omitir el sistema de tipos de Go y realizar operaciones de conversión de tipos que originalmente no podrían compilarse.
