Introducción
Los tipos de datos especifican los tipos de valores que las variables particulares almacenarán cuando estés escribiendo un programa. El tipo de dato también determina qué operaciones se pueden realizar con los datos.
En este artículo, repasaremos los tipos de datos importantes nativos de Go. Esto no es una investigación exhaustiva de los tipos de datos, pero te ayudará a familiarizarte con las opciones que tienes disponibles en Go. Entender algunos tipos de datos básicos te permitirá escribir un código más claro y que funcione de manera eficiente.
Antecedentes
Una forma de pensar en los tipos de datos es considerar los diferentes tipos de datos que usamos en el mundo real. Un ejemplo de datos en el mundo real son los números: podemos usar números enteros (0, 1, 2, …), enteros (…, -1, 0, 1, …), y números irracionales (π), por ejemplo.
Usualmente, en matemáticas, podemos combinar números de diferentes tipos y obtener alguna clase de respuesta. Podríamos querer sumar 5 a π, por ejemplo:
5 + π
Podemos mantener la ecuación como la respuesta para tener en cuenta el número irracional, o redondear π a un número con un número reducido de decimales, y luego sumar los números:
5 + π = 5 + 3.14 = 8.14
Pero, si empezamos a intentar evaluar números con otro tipo de datos, como palabras, las cosas empiezan a tener menos sentido. ¿Cómo resolveríamos la siguiente ecuación?
shark + 8
Para las computadoras, cada tipo de datos es bastante diferente, como las palabras y los números. Como resultado, tenemos que ser cuidadosos con cómo usamos diferentes tipos de datos para asignar valores y cómo los manipulamos a través de operaciones.
Enteros
Al igual que en matemáticas, enteros en programación informática son números enteros que pueden ser positivos, negativos o 0 (…, -1, 0, 1, …). En Go, un entero se conoce como un int
. Al igual que con otros lenguajes de programación, no debes usar comas en números de cuatro o más dígitos, por lo que cuando escribes 1,000 en tu programa, escríbelo como 1000
.
Podemos imprimir un entero de una manera sencilla así:
Output-459
O, podemos declarar una variable, que en este caso es un símbolo del número que estamos usando o manipulando, de esta manera:
Output-459
También podemos hacer matemáticas con enteros en Go. En el siguiente bloque de código, usaremos el operador de asignación :=
para declarar e instanciar la variable sum
:
Output48
Como muestra la salida, el operador matemático -
restó el entero 68
de 116
, resultando en 48
. Aprenderás más sobre la declaración de variables en la sección Declarando Tipos de Datos para Variables.
Los enteros pueden ser utilizados de muchas maneras dentro de los programas en Go. A medida que continúas aprendiendo sobre Go, tendrás muchas oportunidades de trabajar con enteros y construir sobre tus conocimientos de este tipo de datos.
Números de Punto Flotante
Un número de punto flotante o un float se utiliza para representar números reales que no pueden expresarse como enteros. Los números reales incluyen todos los números racionales e irracionales, y debido a esto, los números de punto flotante pueden contener una parte fraccionaria, como 9.0 o -116.42. Para los propósitos de pensar en un float en un programa en Go, es un número que contiene un punto decimal.
Al igual que hicimos con los enteros, podemos imprimir un número de punto flotante de una manera sencilla así:
Output-459.67
También podemos declarar una variable que represente un float, de esta manera:
Output-459.67
Al igual que con los enteros, también podemos hacer matemáticas con floats en Go:
Output929.24
Con enteros y números de punto flotante, es importante tener en cuenta que 3 ≠ 3.0, ya que 3 se refiere a un entero mientras que 3.0 se refiere a un float.
Tamaños de los Tipos Numéricos
Además de la distinción entre enteros y flotantes, Go tiene dos tipos de datos numéricos que se distinguen por la naturaleza estática o dinámica de sus tamaños. El primer tipo es un tipo independiente de la arquitectura, lo que significa que el tamaño de los datos en bits no cambia, independientemente de la máquina en la que se esté ejecutando el código.
La mayoría de las arquitecturas de sistemas actuales son de 32 bits o 64 bits. Por ejemplo, podrías estar desarrollando para una laptop Windows moderna, en la que el sistema operativo se ejecuta en una arquitectura de 64 bits. Sin embargo, si estás desarrollando para un dispositivo como un reloj de fitness, podrías estar trabajando con una arquitectura de 32 bits. Si usas un tipo independiente de la arquitectura como int32
, independientemente de la arquitectura para la que compiles, el tipo tendrá un tamaño constante.
El segundo tipo es un tipo específico de la implementación. En este tipo, el tamaño en bits puede variar según la arquitectura en la que se construye el programa. Por ejemplo, si usamos el tipo int
, cuando Go compila para una arquitectura de 32 bits, el tamaño del tipo de datos será de 32 bits. Si el programa se compila para una arquitectura de 64 bits, la variable tendrá un tamaño de 64 bits.
Además de que los tipos de datos tienen diferentes tamaños, tipos como los enteros también vienen en dos tipos básicos: con signo y sin signo. Un int8
es un entero con signo y puede tener un valor de -128 a 127. Un uint8
es un entero sin signo y solo puede tener un valor positivo de 0 a 255.
Los rangos se basan en el tamaño en bits. Para datos binarios, 8 bits pueden representar un total de 256 valores diferentes. Debido a que un tipo int
necesita soportar tanto valores positivos como negativos, un entero de 8 bits (int8
) tendrá un rango de -128 a 127, para un total de 256 valores únicos posibles.
Go tiene los siguientes tipos enteros independientes de la arquitectura:
uint8 unsigned 8-bit integers (0 to 255)
uint16 unsigned 16-bit integers (0 to 65535)
uint32 unsigned 32-bit integers (0 to 4294967295)
uint64 unsigned 64-bit integers (0 to 18446744073709551615)
int8 signed 8-bit integers (-128 to 127)
int16 signed 16-bit integers (-32768 to 32767)
int32 signed 32-bit integers (-2147483648 to 2147483647)
int64 signed 64-bit integers (-9223372036854775808 to 9223372036854775807)
Los números flotantes y los números complejos también vienen en diferentes tamaños:
float32 IEEE-754 32-bit floating-point numbers
float64 IEEE-754 64-bit floating-point numbers
complex64 complex numbers with float32 real and imaginary parts
complex128 complex numbers with float64 real and imaginary parts
También hay un par de tipos numéricos alias, que asignan nombres útiles a tipos de datos específicos:
byte alias for uint8
rune alias for int32
El propósito del alias byte
es aclarar cuándo tu programa utiliza bytes como una medida común en elementos de cadenas de caracteres, en lugar de pequeños enteros no relacionados con la medida de datos de bytes. Aunque byte
y uint8
son idénticos una vez que el programa se compila, byte
se usa a menudo para representar datos de caracteres en forma numérica, mientras que uint8
está destinado a ser un número en tu programa.
El alias rune
es un poco diferente. Mientras que byte
y uint8
son exactamente los mismos datos, un rune
puede ser un solo byte o cuatro bytes, un rango determinado por int32
. Un rune
se usa para representar un carácter Unicode, mientras que solo los caracteres ASCII pueden ser representados únicamente por un tipo de datos int32
.
Además, Go tiene los siguientes tipos específicos de implementación:
uint unsigned, either 32 or 64 bits
int signed, either 32 or 64 bits
uintptr unsigned integer large enough to store the uninterpreted bits of a pointer value
Los tipos específicos de implementación tendrán su tamaño definido por la arquitectura para la que se compila el programa.
Elegir Tipos de Datos Numéricos
Elegir el tamaño adecuado generalmente tiene más que ver con el rendimiento para la arquitectura objetivo para la que estás programando que con el tamaño de los datos con los que estás trabajando. Sin embargo, sin necesidad de conocer las implicaciones específicas de rendimiento para tu programa, puedes seguir algunas de estas pautas básicas al comenzar por primera vez.
Como se discutió anteriormente en este artículo, existen tipos independientes de la arquitectura y tipos específicos de la implementación. Para datos enteros, es común en Go usar los tipos de implementación como int
o uint
en lugar de int64
o uint64
. Esto generalmente resultará en la velocidad de procesamiento más rápida para tu arquitectura objetivo. Por ejemplo, si usas un int64
y compilas para una arquitectura de 32 bits, tomará al menos el doble de tiempo procesar esos valores ya que requiere ciclos de CPU adicionales para mover los datos a través de la arquitectura. Si en cambio usas un int
, el programa lo definiría como un tamaño de 32 bits para una arquitectura de 32 bits, y sería significativamente más rápido de procesar.
Si sabes que no excederás un rango de tamaño específico, entonces elegir un tipo independiente de la arquitectura puede aumentar la velocidad y disminuir el uso de memoria. Por ejemplo, si sabes que tus datos no excederán el valor de 100
y solo serán números positivos, entonces elegir un uint8
haría que tu programa fuera más eficiente ya que requeriría menos memoria.
Ahora que hemos examinado algunos de los posibles rangos para los tipos de datos numéricos, veamos qué ocurrirá si superamos esos rangos en nuestro programa.
Desbordamiento vs. Wraparound
Go tiene la capacidad de tanto desbordar un número como wraparound un número cuando intentas almacenar un valor mayor que el tipo de datos fue diseñado para almacenar, dependiendo de si el valor se calcula en tiempo de compilación o en tiempo de ejecución. Un error en tiempo de compilación ocurre cuando el programa encuentra un error mientras intenta construir el programa. Un error en tiempo de ejecución ocurre después de que el programa se compila, mientras se está ejecutando.
En el siguiente ejemplo, establecemos maxUint32
a su valor máximo:
Se compilará y ejecutará con el siguiente resultado:
Si agregamos 1
al valor en tiempo de ejecución, se wraparound a 0
:
Por otro lado, cambiemos el programa para agregar 1
a la variable cuando la asignamos, antes del tiempo de compilación:
En tiempo de compilación, si el compilador puede determinar que un valor será demasiado grande para contener en el tipo de datos especificado, lanzará un error de desbordamiento
. Esto significa que el valor calculado es demasiado grande para el tipo de datos que especificaste.
Debido a que el compilador puede determinar que desbordará el valor, ahora lanzará un error:
Outputprog.go:6:36: constant 4294967296 overflows uint32
Comprender los límites de tus datos te ayudará a evitar posibles errores en tu programa en el futuro.
Ahora que hemos cubierto los tipos numéricos, veamos cómo almacenar valores booleanos.
Booleanos
El tipo de dato booleano puede ser uno de dos valores, ya sea true
o false
, y se define como bool
al declararlo como tipo de dato. Los booleanos se utilizan para representar los valores de verdad asociados con la rama lógica de las matemáticas, que informa los algoritmos en la informática.
Los valores true
y false
siempre estarán con una letra minúscula t
y f
respectivamente, ya que son identificadores predefinidos en Go.
Muchas operaciones en matemáticas nos dan respuestas que evalúan a verdadero o falso:
- mayor que
- 500 > 100 true
- 1 > 5 false
- menor que
- 200 < 400 true
- 4 < 2 false
- igual
- 5 = 5 true
- 500 = 400 false
Al igual que con los números, podemos almacenar un valor booleano en una variable:
Podemos imprimir el valor booleano con una llamada a la función fmt.Println()
:
Dado que 5
no es mayor que 8
, recibiremos la siguiente salida:
Outputfalse
A medida que escribas más programas en Go, te familiarizarás más con cómo funcionan los booleanos y cómo diferentes funciones y operaciones que evalúan a true
o false
pueden cambiar el curso del programa.
Cadenas
Una cadena es una secuencia de uno o más caracteres (letras, números, símbolos) que puede ser una constante o una variable. Las cadenas existen dentro de comillas simples `
o comillas dobles "
en Go y tienen diferentes características dependiendo de las comillas que utilices.
Si usas las comillas simples, estás creando un literal de cadena sin procesar. Si usas las comillas dobles, estás creando un literal de cadena interpretado.
Literales de Cadena Sin Procesar
Los literales de cadena sin procesar son secuencias de caracteres entre comillas simples, a menudo llamadas back ticks. Dentro de las comillas, cualquier carácter aparecerá tal como se muestra entre las comillas simples, excepto el carácter de comillas simples en sí.
OutputSay "hello" to Go!
Usualmente, las barras invertidas se utilizan para representar caracteres especiales en las cadenas. Por ejemplo, en una cadena interpretada, \n
representaría un salto de línea en una cadena. Sin embargo, las barras invertidas no tienen un significado especial dentro de los literales de cadena sin procesar:
Debido a que la barra invertida no tiene un significado especial en un literal de cadena, en realidad imprimirá el valor de \n
en lugar de hacer un salto de línea:
OutputSay "hello" to Go!\n
Los literales de cadena sin procesar también pueden utilizarse para crear cadenas de varias líneas:
OutputThis string is on
multiple lines
within a single back
quote on either side.
En los bloques de código anteriores, los saltos de línea se transfirieron literalmente de la entrada a la salida.
Literales de Cadenas Interpretadas
Los literales de cadenas interpretadas son secuencias de caracteres entre comillas dobles, como en "bar"
. Dentro de las comillas, cualquier carácter puede aparecer excepto el salto de línea y las comillas dobles sin escapar. Para mostrar comillas dobles en una cadena interpretada, puedes usar la barra invertida como carácter de escape, de la siguiente manera:
OutputSay "hello" to Go!
Casi siempre usarás literales de cadenas interpretadas porque permiten caracteres de escape dentro de ellas. Para más información sobre cómo trabajar con cadenas, consulta Una Introducción al Trabajo con Cadenas en Go.
Cadenas con Caracteres UTF-8
UTF-8 es un esquema de codificación utilizado para codificar caracteres de ancho variable en uno a cuatro bytes. Go admite caracteres UTF-8 de forma nativa, sin necesidad de configuración especial, bibliotecas o paquetes. Los caracteres romanos como la letra A
pueden representarse mediante un valor ASCII como el número 65. Sin embargo, con caracteres especiales como un carácter internacional de 世
, se requeriría UTF-8. Go utiliza el tipo alias rune
para los datos UTF-8.
Puedes usar la palabra clave range
en un bucle for
para indexar a través de cualquier cadena en Go, incluso una cadena UTF-8. Los bucles for
y range
se tratarán con más profundidad más adelante en la serie; por ahora, es importante saber que podemos usar esto para contar los bytes en una cadena dada:
En el bloque de código anterior, declaramos la variable a
y le asignamos el valor de Hello, 世界
. El texto asignado contiene caracteres UTF-8.
Luego usamos un bucle for
estándar y la palabra clave range
. En Go, la palabra clave range
indexará a través de una cadena devolviendo un carácter a la vez, así como el índice de byte en el que se encuentra el carácter en la cadena.
Al utilizar la función fmt.Printf
, proporcionamos una cadena de formato de %d: %s\n
. %d
es el verbo de impresión para un dígito (en este caso, un entero) y %s
es el verbo de impresión para una cadena. Luego proporcionamos los valores de i
, que es el índice actual del bucle for
, y c
, que es el carácter actual en el bucle for
.
Finalmente, imprimimos la longitud completa de la variable a
utilizando la función incorporada len
.
Anteriormente mencionamos que un rune es un alias para int32
y puede estar compuesto por uno a cuatro bytes. El carácter 世
toma tres bytes para definirse y el índice se mueve en consecuencia al recorrer la cadena UTF-8. Es por eso que i
no es secuencial cuando se imprime.
Output0: H
1: e
2: l
3: l
4: o
5: ,
6:
7: 世
10: 界
length of 'Hello, 世界': 13
Como puedes ver, la longitud es mayor que el número de veces que se necesitó recorrer la cadena.
No siempre estarás trabajando con cadenas UTF-8, pero cuando lo hagas, ahora entenderás por qué son runes y no un solo int32
.
Declarando Tipos de Datos para Variables
Ahora que conoces los diferentes tipos de datos primitivos, repasaremos cómo asignar estos tipos a variables en Go.
En Go, podemos definir una variable con la palabra clave var
seguida del nombre de la variable y el tipo de datos deseado.
En el siguiente ejemplo, declararemos una variable llamada pi
de tipo float64
.
La palabra clave var
es lo primero que se declara:
Seguido por el nombre de nuestra variable, pi
:
Y finalmente el tipo de dato float64
:
Podemos especificar opcionalmente un valor inicial también, como 3.14
:
Go es un lenguaje estáticamente tipado. Estáticamente tipado significa que cada declaración en el programa se verifica en tiempo de compilación. También significa que el tipo de dato está vinculado a la variable, mientras que en lenguajes dinámicamente tipados, el tipo de dato está vinculado al valor.
Por ejemplo, en Go, el tipo se declara al declarar una variable:
Cada una de estas variables podría ser de un tipo de dato diferente si las declararas de manera diferente.
Esto es diferente de un lenguaje como PHP, donde el tipo de dato está asociado al valor:
En el bloque de código anterior, el primer $s
es una cadena porque se le asigna el valor "sammy"
, y el segundo es un entero porque tiene el valor 123
.
A continuación, veamos tipos de datos más complejos como los arrays.
Arrays
Un array es una secuencia ordenada de elementos. La capacidad de un array se define en el momento de su creación. Una vez que un array ha asignado su tamaño, este ya no puede ser cambiado. Debido a que el tamaño de un array es estático, significa que solo asigna memoria una vez. Esto hace que los arrays sean algo rígidos de trabajar, pero aumenta el rendimiento de tu programa. Debido a esto, los arrays se utilizan típicamente cuando se optimizan programas. Slices, que se cubren a continuación, son más flexibles y constituyen lo que pensarías como arrays en otros lenguajes.
Los arrays se definen declarando el tamaño del array, luego el tipo de dato con los valores definidos entre llaves { }
.
Un array de cadenas se ve así:
Podemos almacenar un array en una variable e imprimirlo:
Output[blue coral staghorn coral pillar coral]
Como se mencionó anteriormente, los slices son similares a los arrays, pero son mucho más flexibles. Echemos un vistazo a este tipo de dato mutable.
Slices
Un slice es una secuencia ordenada de elementos que puede cambiar de longitud. Los slices pueden aumentar su tamaño de manera dinámica. Cuando agregas nuevos elementos a un slice, si el slice no tiene suficiente memoria para almacenar los nuevos elementos, solicitará más memoria del sistema según sea necesario. Debido a que un slice puede expandirse para agregar más elementos cuando sea necesario, se utilizan más comúnmente que los arrays.
Las rebanadas se definen declarando el tipo de datos precedido por un corchete de apertura y cierre []
y teniendo valores entre llaves { }
.
Una rebanada de enteros se ve así:
Una rebanada de flotantes se ve así:
Una rebanada de cadenas se ve así:
Definamos nuestra rebanada de cadenas como seaCreatures
:
Podemos imprimirlos llamando a la variable:
La salida se verá exactamente como la lista que creamos:
Output[shark cuttlefish squid mantis shrimp]
Podemos usar la palabra clave append
para agregar un elemento a nuestra rebanada. El siguiente comando agregará el valor de cadena seahorse
a la rebanada:
Puedes verificar que se agregó imprimiéndolo:
Output[shark cuttlefish squid mantis shrimp seahorse]
Como puedes ver, si necesitas gestionar un tamaño desconocido de elementos, una rebanada será mucho más versátil que un array.
Mapas
El mapa es el tipo hash o diccionario incorporado de Go. Los mapas utilizan claves y valores como un par para almacenar datos. Esto es útil en programación para buscar rápidamente valores por un índice, o en este caso, una clave. Por ejemplo, es posible que desees mantener un mapa de usuarios, indexado por su ID de usuario. La clave sería la ID de usuario, y el objeto de usuario sería el valor. Un mapa se construye usando la palabra clave map
seguida del tipo de datos de la clave entre corchetes [ ]
, seguido del tipo de datos del valor y los pares clave-valor entre llaves.
map[key]value{}
Típicamente utilizado para almacenar datos relacionados, como la información contenida en un ID, un mapa se ve así:
Notarás que además de las llaves, también hay dos puntos en todo el mapa. Las palabras a la izquierda de los dos puntos son las claves. Las claves pueden ser cualquier tipo comparable en Go. Los tipos comparables son tipos primitivos como strings
, ints
, etc. Un tipo primitivo está definido por el lenguaje y no se construye combinando otros tipos. Aunque pueden ser tipos definidos por el usuario, se considera una buena práctica mantenerlos simples para evitar errores de programación. Las claves en el diccionario anterior son: name
, animal
, color
y location
.
Las palabras a la derecha de los dos puntos son los valores. Los valores pueden estar compuestos de cualquier tipo de dato. Los valores en el diccionario anterior son: Sammy
, shark
, blue
y ocean
.
Almacenemos el mapa dentro de una variable e imprimámoslo:
Outputmap[animal:shark color:blue location:ocean name:Sammy]
Si queremos aislar el color de Sammy, podemos hacerlo llamando a sammy["color"]
. Imprimamos eso:
Outputblue
Como los mapas ofrecen pares clave-valor para almacenar datos, pueden ser elementos importantes en tu programa Go.
Conclusión
En este punto, deberías tener una mejor comprensión de algunos de los principales tipos de datos que están disponibles para que los utilices en Go. Cada uno de estos tipos de datos se volverá importante a medida que desarrolles proyectos de programación en el lenguaje Go.
Una vez que tengas un sólido conocimiento de los tipos de datos disponibles en Go, podrás aprender Cómo Convertir Tipos de Datos para cambiar tus tipos de datos según la situación.
Source:
https://www.digitalocean.com/community/tutorials/understanding-data-types-in-go