El lenguaje de programación Go, también conocido como Golang, proporciona un conjunto de tipos de datos básicos robustos que son esenciales para la construcción de cualquier aplicación. Comprender estos tipos es fundamental para escribir código eficiente y correcto.
Tipos Numéricos Enteros
Go ofrece una variedad de tipos enteros, que se dividen en dos categorías principales: con signo y sin signo, y se diferencian por su tamaño en bits:
- Con signo:
int8,int16,int32,int64. Estos pueden almacenar números positivos, negativos y cero. - Sin signo:
uint8,uint16,uint32,uint64. Estos solo pueden almacenar números no negativos (cero y positivos).
El tipo uint8 es comúnmente conocido como byte y es utilizado para representar datos binarios de un solo octeto. A continuación, se detallan los rangos de valores para cada tipo:
| Tipo | Descripción | Rango de Valores |
|---|---|---|
uint8 |
Entero sin signo de 8 bits | 0 a 255 |
uint16 |
Entero sin signo de 16 bits | 0 a 65535 |
uint32 |
Entero sin signo de 32 bits | 0 a 4,294,967,295 |
uint64 |
Entero sin signo de 64 bits | 0 a 18,446,744,073,709,551,615 |
int8 |
Entero con signo de 8 bits | -128 a 127 |
int16 |
Entero con signo de 16 bits | -32768 a 32767 |
int32 |
Entero con signo de 32 bits | -2,147,483,648 a 2,147,483,647 |
int64 |
Entero con signo de 64 bits | -9,223,372,036,854,775,808 a 9,223,372,036,854,775,807 |
Tipos Enteros Especiales
Go también proporciona tipos enteros que se adaptan a la arquitectura del sistema operativo:
int: Un entero con signo cuyo tamaño es de 32 bits en sistemas de 32 bits y de 64 bits en sistemas de 64 bits.uint: Un entero sin signo cuyo tamaño es de 32 bits en sistemas de 32 bits y de 64 bits en sistemas de 64 bits.uintptr: Un entero sin signo capaz de almacenar el valor de un puntero.
Importante: Al utilizar int y uint, no se debe asumir que siempre son de 32 o 64 bits. Su tamaño es dependiente de la plataforma de compilación. Para operaciones que requieren un tamaño de byte fijo, como la serialización o la interoperabilidad de red, es preferible usar los tipos de tamaño explícito (ej., int32, uint64).
La función incorporada len(), que devuelve la longitud de muchos objetos (como slices o mapas), siempre regresa un valor de tipo int, lo que es apropiado para la mayoría de los casos de uso.
Sintaxis de Literales Numéricos
Desde Go 1.13, se introdujo una sintaxis mejorada para los literales numéricos, facilitando la definición de números en diferentes bases y con separadores visuales:
0bpara binarios:valor := 0b101101(decimal 45)0opara octales:valor := 0o377(decimal 255)0xpara hexadecimales:valor := 0x1p-2(hexadecimal 1 dividido por 2², es decir, 0.25 en punto flotante)
Además, se permite el uso del guion bajo (_) como separador de dígitos para mejorar la legibilidad de números largos: numeroGrande := 1_234_567 (equivalente a 1234567).
Podemos utilizar el paquete fmt para mostrar enteros en diferentes bases:
package main
import "fmt"
func main() {
numeroDecimal := 42
fmt.Printf("Decimal: %d\n", numeroDecimal) // Salida: Decimal: 42
fmt.Printf("Binario: %b\n", numeroDecimal) // Salida: Binario: 101010
valorOctal := 0o52 // Equivalente a 42 en decimal
fmt.Printf("Octal: %o\n", valorOctal) // Salida: Octal: 52
valorHex := 0x2A // Equivalente a 42 en decimal
fmt.Printf("Hexadecimal (minúsculas): %x\n", valorHex) // Salida: Hexadecimal (minúsculas): 2a
fmt.Printf("Hexadecimal (MAYÚSCULAS): %X\n", valorHex) // Salida: Hexadecimal (MAYÚSCULAS): 2A
}
Tipos de Punto Flotante
Go soporta dos tipos para números de punto flotante, ambos conforme al estándar IEEE 754:
float32: Rango aproximado hasta 3.4e38. Su valor máximo se puede encontrar enmath.MaxFloat32.float64: Rango aproximado hasta 1.8e308. Su valor máximo se puede encontrar enmath.MaxFloat64.
Para la mayoría de las aplicaciones, se recomienda usar float64 debido a su mayor precisión.
Para imprimir números de punto flotante, se utiliza el paquete fmt con el verbo %f:
package main
import (
"fmt"
"math"
)
func main() {
const constanteEuler float64 = 2.718281828459045
fmt.Printf("Valor de Euler: %f\n", constanteEuler) // Salida: Valor de Euler: 2.718282
fmt.Printf("Pi con 2 decimales: %.2f\n", math.Pi) // Salida: Pi con 2 decimales: 3.14
fmt.Printf("Pi con 6 decimales: %.6f\n", math.Pi) // Salida: Pi con 6 decimales: 3.141593
}
Tipos Complejos
Go incluye soporte nativo para números complejos con los tipos complex64 y complex128. Un número complejo se compone de una parte real y una parte imaginaria.
complex64: Ambas partes (real e imaginaria) sonfloat32.complex128: Ambas partes (real e imaginaria) sonfloat64.
package main
import "fmt"
func main() {
numComplex64 := 1.5 + 2.3i
var numComplex128 complex128 = complex(4.0, 5.0) // Usando la función complex()
fmt.Printf("Número complejo de 64 bits: %v\n", numComplex64) // Salida: Número complejo de 64 bits: (1.5+2.3i)
fmt.Printf("Número complejo de 128 bits: %v\n", numComplex128) // Salida: Número complejo de 128 bits: (4+5i)
}
Tipo Booleano
El tipo bool en Go representa valores lógicos y solo puede tener dos estados: true (verdadero) o false (falso).
Consideraciones importantes sobre los booleanos:
- El valor por defecto de una variable booleana no inicializada es
false. - Go no permite la conversión implícita o explícita de enteros a booleanos.
- Los valores booleanos no pueden participar en operaciones aritméticas ni ser convertidos a otros tipos numéricos.
Tipo String (Cadena de Caracteres)
Las cadenas de caracteres en Go son tipos de datos nativos e inmutables. Internamente, Go utiliza la codificación UTF-8 para las cadenas, lo que permite manejar una amplia gama de caracteres Unicode directamente.
Las cadenas se definen encerrando el texto entre comillas dobles ("). Por ejemplo:
s1 := "Hola"
s2 := "Mundo"
s3 := "你好" // Caracteres chinos
Caracteres de Escape en Cadenas
Go soporta los caracteres de escape comunes para representar caracteres especiales dentro de una cadena:
| Carácter de Escape | Significado |
|---|---|
\r |
Retorno de carro |
\n |
Nueva línea |
\t |
Tabulación |
\' |
Comilla simple |
\" |
Comilla doble |
\\ |
Barra invertida |
Ejemplo de uso de caracteres de escape:
package main
import "fmt"
func main() {
// Ruta de archivo con barras invertidas que necesitan ser escapadas
rutaArchivo := "C:\\Program Files\\Go\\bin\\go.exe"
fmt.Printf("Ruta: %s\n", rutaArchivo)
// Texto con saltos de línea y tabulaciones
mensaje := "Línea 1\n\tLínea 2 con tabulación"
fmt.Println(mensaje)
// Comillas dentro de una cadena
cita := "Dijo: \"Hola mundo Go\""
fmt.Println(cita)
}
Cadenas Multi-línea (Raw String Literals)
Para definir cadenas que abarcan varias líneas o que contienen caracteres especiales sin necesidad de escaparlos, se utilizan los acentos graves (```). En estas "raw strings", los caracteres de escape no se interpretan y el texto se reproduce exactamente como se escribe.
package main
import "fmt"
func main() {
poema := `
Este es un ejemplo
de una cadena multi-línea.
Los saltos de línea y
los caracteres especiales como \n o \t
se incluyen literalmente.
`
fmt.Println(poema)
}
Operaciones Comunes con Cadenas
El lenguaje Go y el paquete estándar strings ofrecen una gran cantidad de funciones para manipular cadenas:
| Función/Operador | Descripción |
|---|---|
len(str) |
Devuelve la longitud de la cadena en bytes. |
+ o fmt.Sprintf |
Concatenación de cadenas. |
strings.Split(str, sep) |
Divide una cadena en un slice de subcadenas usando un separador. |
strings.Contains(str, substr) |
Verifica si una cadena contiene una subcadena. |
strings.HasPrefix(str, prefix) |
Verifica si una cadena comienza con un prefijo dado. |
strings.HasSuffix(str, suffix) |
Verifica si una cadena termina con un sufijo dado. |
strings.Index(str, substr) |
Devuelve el índice de la primera ocurrencia de una subcadena. |
strings.LastIndex(str, substr) |
Devuelve el índice de la última ocurrencia de una subcadena. |
strings.Join(a[]string, sep) |
Une los elementos de un slice de cadenas en una sola cadena con un separador. |
package main
import (
"fmt"
"strings"
)
func main() {
cadenaBase := "Golang es un lenguaje moderno"
fmt.Printf("Longitud de la cadena: %d bytes\n", len(cadenaBase)) // 27 bytes (contando espacios)
// Concatenación
saludo := "Hola"
nombre := "Mundo"
mensajeCompleto := saludo + " " + nombre + "!"
fmt.Printf("Mensaje: %s\n", mensajeCompleto)
// División
palabras := strings.Split(cadenaBase, " ")
fmt.Printf("Palabras: %v\n", palabras) // Salida: Palabras: [Golang es un lenguaje moderno]
// Contiene
fmt.Printf("Contiene 'moderno'? %t\n", strings.Contains(cadenaBase, "moderno")) // Salida: Contiene 'moderno'? true
// Unión
frutas := []string{"manzana", "banana", "cereza"}
listaFrutas := strings.Join(frutas, ", ")
fmt.Printf("Lista de frutas: %s\n", listaFrutas) // Salida: Lista de frutas: manzana, banana, cereza
}
Tipos byte y rune
En Go, los caracteres individuales dentro de una cadena se pueden manejar como byte o rune, dependiendo de su naturaleza:
byte: Es un alias parauint8. Se utiliza para representar caracteres ASCII (un solo byte por carácter).rune: Es un alias paraint32. Se utiliza para representar puntos de código Unicode (caracteres UTF-8 que pueden ocupar de 1 a 4 bytes).
Cuando se trabaja con texto que contiene caracteres fuera del conjunto ASCII (como caracteres chinos, japoneses, emojis, etc.), es esencial usar el tipo rune para asegurar un manejo correcto de Unicode.
Consideremos un ejemplo de cómo iterar sobre una cadena usando ambos tipos:
package main
import "fmt"
func main() {
explorarCaracteres()
}
func explorarCaracteres() {
textoMultilenguaje := "Hello 世界" // "Hello" (5 bytes) + " " (1 byte) + "世界" (6 bytes) = 12 bytes
fmt.Println("--- Recorrido por bytes (uint8) ---")
for i := 0; i < len(textoMultilenguaje); i++ {
// Al imprimir como %c, si un byte forma parte de un carácter multibyte,
// no se mostrará correctamente.
fmt.Printf("%v (%c)\n", textoMultilenguaje[i], textoMultilenguaje[i])
}
fmt.Println("\n--- Recorrido por runas (Unicode int32) ---")
// El bucle range itera sobre runas
for indice, runa := range textoMultilenguaje {
fmt.Printf("Índice: %d, Código Unicode (decimal): %d, Caracter: %c\n", indice, runa, runa)
}
}
La salida del recorrido por bytes mostrará los valores numéricos de cada byte. Para los caracteres "世" y "界", que son multibyte en UTF-8, cada byte individaul no representará un carácter legible por sí mismo. En cambio, el recorrido por runas interpretará correctamente los caracteres Unicode, mostrando cada uno como una unidad.
En resumen: una cadena en Go es una secuencia de bytes inmutable. La función len() devuelve el número de bytes. El tipo rune es fundamental para manejar correctamente los caracteres Unicode, ya que una sola rune puede componerse de múltiples bytes.
Modificar Cadenas
Dado que las cadenas son inmutables en Go, para "modificar" una cadena, se debe convertir a un slice de bytes ([]byte) o un slice de runas ([]rune), realizar la modificación y luego convertir el slice de nuevo a una cadena. Este proceso implica la asignación de nueva memoria y la copia de los datos.
package main
import "fmt"
func main() {
manipularCadena()
}
func manipularCadena() {
palabraInglesa := "table"
// Convertir a []byte para modificar caracteres ASCII
bytesPalabra := []byte(palabraInglesa)
bytesPalabra[0] = 'c' // Cambiar 't' por 'c'
fmt.Printf("Cadena modificada (bytes): %s\n", string(bytesPalabra)) // Salida: cable
palabraEspañola := "árbol"
// Convertir a []rune para modificar caracteres Unicode
runasPalabra := []rune(palabraEspañola)
runasPalabra[0] = 'U' // Cambiar 'á' por 'U'
fmt.Printf("Cadena modificada (runas): %s\n", string(runasPalabra)) // Salida: Úrbol
}
Conversión de Tipos
Go es un lenguaje con tipado estático y fuerte, lo que significa que no hay conversiones de tipo implícitas. Todas las conversiones entre tipos deben ser explícitas. La sintaxis para la conversión de tipos es:
T(expresión)
Donde T es el tipo al que se desea convertir la expresión. Esta expresión puede ser una variable, un literal, el resultado de una función, etc. La conversión solo es posible entre tipos compatibles.
Por ejemplo, si necesitamos calcular la hipotenusa de un triángulo rectángulo usando la función math.Sqrt(), que espera un argumento de tipo float64, debemos convertir los enteros a float64 antes de pasarlos a la función.
package main
import (
"fmt"
"math"
)
func main() {
demostrarConversionTipo()
}
func demostrarConversionTipo() {
catetoA, catetoB := 6, 8 // Lados de un triángulo rectángulo
// math.Sqrt requiere float64, por lo que convertimos la suma de los cuadrados
sumaCuadrados := float64(catetoA*catetoA + catetoB*catetoB)
hipotenusaFlotante := math.Sqrt(sumaCuadrados)
// Para obtener un resultado entero, convertimos de float64 a int (trunca el valor)
hipotenusaEntera := int(hipotenusaFlotante)
fmt.Printf("Cateto A: %d, Cateto B: %d\n", catetoA, catetoB)
fmt.Printf("Hipotenusa calculada (float64): %.2f\n", hipotenusaFlotante) // Salida: Hipotenusa calculada (float64): 10.00
fmt.Printf("Hipotenusa truncada (int): %d\n", hipotenusaEntera) // Salida: Hipotenusa truncada (int): 10
}