Skip to content

Latest commit

 

History

History
455 lines (298 loc) · 19.8 KB

File metadata and controls

455 lines (298 loc) · 19.8 KB

2.2 Principios de Go

En esta sección, vamos a aprender como definir constantes, variables con tipos básicos y algunos conocimientos mas de la programación en Go.

Definición de variables

Tenemos muchas formas diferentes de definir variables con la sintaxis de Go.

Utilizando la palabra reservada var es la forma básica de definir una variable, observe como en Go se coloca el tipo de variable luego del nombre de la misma.

// definición de una variables de nombre “variableName” y tipo "type"
var variableName type

Definición de múltiples variables.

// definimos tres variables donde su tipo es "type"
var vname1, vname2, vname3 type

Definición de una variable con un valor inicial.

// definición de una variable de nombre “variableName”, tipo "type" y valor "value"
var variableName type = value

Definición de múltiples variables con valores iniciales.

/*
Definición de tres variables de tipo "type", y sus valores iniciales.
vname1 de valor v1, vname2 de valor v2, vname3 de valor v3
*/
var vname1, vname2, vname3 type = v1, v2, v3

¿Crees que es muy tedioso utilizar las anteriores formas para definir variables? Entonces no te preocupes, porque el equipo de desarrollo de Go también se encontró con este problema. Por lo que si deseas definir una variable con valores iniciales, podemos omitir el tipo de cada una, por lo que el código quedaría algo así:

/*
Definición de tres variables de tipo "type", y sus valores iniciales.
vname1 de valor v1,vname2 de valor v2,vname3 de valor v3
*/
var vname1, vname2, vname3 = v1, v2, v3

Bueno, se que aun no es lo suficientemente sencillo. Vamos a ver como lo mejoramos.

/*
Definición de tres variables de tipo "type", y sus valores iniciales.
vname1 de valor v1,vname2 de valor v2,vname3 de valor v3
*/
vname1, vname2, vname3 := v1, v2, v3

Ahora se ve mucho mejor. Utilice := para reemplazar a var y type, esto se llama declaración breve. Pero espere, esto tiene una limitación, esta forma de declaración solo puede ser utilizada dentro de una función. Si intenta utilizarla fuera del cuerpo de una función va a recibir un error de compilación. Por lo que normalmente utilizamos var para definir variables globales, y podemos utilizar la declaración breve en var().

_ (guión bajo) es un nombre especial de variable, cualquier valor que le sea otorgado será ignorado. Por ejemplo, le otorgamos 35 a b, y descartamos el 34.( Este ejemplo nos muestra como esto funciona. Parece inútil aquí porque normalmente lo utilizamos cuando tomamos los valores de retorno de una función. )

_, b := 34, 35

Si no utilizas alguna de las variables declaradas en el programa, el compilador les lanzara un error. Intente compilar el siguiente código, vea que sucede.

package main

func main() {
    var i int
}

Constantes

Las llamadas constantes son los valores que se determinan en el momento de compilación, y que no se pueden modificar durante la ejecución. En Go, podemos utilizar números, booleans o string como tipos de constantes.

Definimos una constante de la siguiente manera.

const nombreConstante = valor
// podemos asignar el tipo de una constante si es necesario
const Pi float32 = 3.1415926

Otros ejemplos.

const Pi = 3.1415926
const i = 10000
const MaxThread = 10
const prefix = "astaxie_"

Tipos básicos

Booleanos

En Go, podemos usar bool para definir variables de tipo booleanas, su valor puede ser unicamente true o false, y false sera el valor por defecto. ( No puede convertir el tipos variables entre números y booleanos! )

// ejemplo de código
var isActive bool  // variable global
var enabled, disabled = true, false  // omitimos el tipo de las variables
func test() {
	var available bool  // variable local
	valid := false      // definición breve de una variable
	available = true    // asignación de un valor a una variable
}

Tipos Numéricos

Tipos enteros incluyendo los enteros con signo y sin signo. Para esto Go tiene int y uint al mismo tiempo, ellos tienen la misma longitud, pero la longitud específica va a depender de su sistema operativo. Ellos utilizan 32-bit en sistemas operativos de 32-bit, y 64-bit en sistemas operativos de 64-bit. Go también tiene tipos que tienen una longitud específica incluyendo rune, int8, int16, int32, int64, byte, uint8, uint16, uint32, uint64. Tenga en cuenta que rune es el alias de int32 y byte es el alias para uint8.

Algo importante que debe saber es que no puede asignar valores entre estos tipos, esta operación va a causar un error de compilación.

var a int8

var b int32

c := a + b

Aunque int tiene una longitud mayor que uint8, y tiene la misma longitud que int32, pero no podrá asignar valores entre ellos. ( aquí c afirmará ser de tipo int )

Los tipos Float tienen float32 y float64, y no tiene tipo llamado float, este último solo es el tipo por defecto cuando se utiliza la declaración breve.

Eso es todo? No! Go también tiene número complejos. complex128 (con una parte de 64-bit reales y otra parte de 64-bit imaginarios) es el tipo por defecto, si necesita un tipo mas pequeño, hay uno llamado complex64 (con una parte de 32-bit reales y otra de 32-bit imaginarios). Su forma es RE+IMi, donde RE es la parte real e IM es la parte imaginaria, el último i es el número imaginario. Este es un ejemplo de número complejo.

var c complex64 = 5+5i
//salida: (5+5i)
fmt.Printf("Value is: %v", c)

String

Acabamos de hablar sobre que Go utiliza el juego de caracteres de UTF-8. Las Strings son representadas mediante comillas dobles "" o comillas simples ``` `.

// ejemplo de código
var frenchHello string  // forma básica de definir una string
var emptyString string = ""  // definimos una string con un valor vacío	func test() {
	no, yes, maybe := "no", "yes", "maybe"  // declaración breve
	japaneseHello := "Ohaiou"
	frenchHello = "Bonjour"  // forma básica para asignar un valor
}

Es imposible cambiar el valor de una string por su índice, va a obtener un error cuando compile el siguiente código.

var s string = "hello"
s[0] = 'c'

Pero, y si realmente deseo cambiar el valor de un solo carácter de una string? Intente utilizar el siguiente código.

s := "hello"
c := []byte(s)  // convertimos una string a un tipo []byte
c[0] = 'c'
s2 := string(c)  // volvemos a convertirlo a un tipo string
fmt.Printf("%s\n", s2)

Podemos utilizar el operador + para combinar dos strings.

s := "hello,"
m := " world"
a := s + m
fmt.Printf("%s\n", a)

y también.

s := "hello"
s = "c" + s[1:] // no puede cambiar los valores de una string por índice pero puede obtener sus valores.
fmt.Printf("%s\n", s)

Que pasa si quiero tener una string de varias líneas?

m := `hello
world`

`` ` no escapara a ninguna carácter en una string.

Tipos de Errores

Go tiene un tipo de error para hacer frente a los mensajes de error. También tiene un paquete llamado errors para manejar los errores.

err := errors.New("emit macho dwarf: elf header corrupted")
if err != nil {
	fmt.Print(err)
}

Estructura de datos fundamental

La siguiente imagen viene de un artículo sobre Estructuras de datos en Go en El Blog de Russ Cox. Como puede ver, Go nos da bloques de memoria para almacenar datos.

Imagen 2.1 Go estructura de datos fundamental

Algunas habilidades

Definir por grupo

Si desea definir múltiples constantes, variables o importaciones de paquetes, podemos utilizar formularios de grupos.

Forma básica.

import "fmt"
import "os"

const i = 100
const pi = 3.1415
const prefix = "Go_"

var i int
var pi float32
var prefix string

Forma de grupo.

import(
	"fmt"
	"os"
)

const(
	i = 100
	pi = 3.1415
	prefix = "Go_"
)

var(
	i int
	pi float32
	prefix string
)

A menos que asigne el valor de la constante iota, el primer valor de la constante en el grupo const() va a ser 0. Si a las siguientes constantes no se les asignan valores explícitamente, sus valores serán el mismo que el último. Si el último valor de la constante es iota, los valores de las siguientes constantes que no son asignadas también serán iota.

Enumerar con iota

Go tiene la palabra reservada iota, esta palabra reservada va a crear un enum, que va a comenzar con 0, y se va a ir incrementando en 1.

const(
	x = iota  // x == 0
	y = iota  // y == 1
	z = iota  // z == 2
	w  // No hay expresión de valor después del nombre de la constante, que utiliza la última expresión, entonces aquí nos dice que w = iota implícitamente. Por lo tanto w == 3, e y y x los dos, podrían omitir "= iota" de la misma forma.
)

const v = iota // cuando se vuelve a utilizar la palabra reservada `const` seguida de la asignación iota nuevamente, este reinicia su valor a `0`, por esto v = 0.

const ( 
  e, f, g = iota, iota, iota // e=0,f=0,g=0 mismos valores de iota en una línea.
)

Algunas reglas

La razón por la que Go es conciso es porque tiene algunos comportamientos por defecto.

  • Cualquier variables que comience con mayúscula se va exportar como pública, de otra manera será una variable privada.
  • Se usa la misma regla para funciones y constantes, no existen las palabras reservadas public o private en Go.

array, slice, map

array

array es un array obviamente, lo definimos de la siguiente forma.

var arr [n]type

en [n]type, n es la longitud del array, type es el tipo de elementos que contiene. Como en otros lenguajes, utilizamos [] para obtener o establecer los elementos en el array.

var arr [10]int  // un array de tipo int
arr[0] = 42      // los array arrancan su índice en 0
arr[1] = 13      // asignamos un valor a un elemento
fmt.Printf("El primer elemento es %d\n", arr[0])  // obtenemos el valor del elemento, este nos devolvera 42
fmt.Printf("El último elemento es %d\n", arr[9]) //este nos devolverá el valor por defecto del elemento número 10 en este array, que es 0 en este caso.

Debido a que la longitud es una parte del tipo del array, [3]int y [4]int son diferentes tipos, por lo que no podemos cambiar la longitud del arrays. Cuando utilizamos arrays como argumentos, las funciones reciben copias en lugar de referencias a ellos! Si lo que busca es utilizar referencias, es posible que desee utilizar slice de las que vamos a hablar más adelante.

Es posible usar := cuando definimos arrays.

a := [3]int{1, 2, 3} // define un array de int con 3 elementos

b := [10]int{1, 2, 3} // define array de int con 10 elementos, y los tres primeros son asignados, el resto utiliza el valor por defecto 0.

c := [...]int{4, 5, 6} // usamos `…` para reemplazar el número de la longitud, Go la va a calcular por usted.

Es posible que desee utilizar arrays como elementos de un arrays, vamos a ver como hacerlo.

// definimos un array de dos dimensiones con dos elementos, y cada elemento tiene cuatro elementos.
doubleArray := [2][4]int{[4]int{1, 2, 3, 4}, [4]int{5, 6, 7, 8}}

// podemos escribirlo de una forma más corta.
easyArray := [2][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}}

Array estructuras de datos fundamentales.

Imagen 2.2 Relación de mapeo de array multi-dimensionales

slice

En muchas situaciones, un array no es una buena elección. Por ejemplo, nosotros no sabemos cual va a ser el largo del array cuando lo definimos, entonces vamos a necesitar un "array dinámico". Este es llamado slice en Go.

Un slice no es realmente un array dinámico, es un tipo referenciado. slice apunta a un array subyacente, su declaración es similar a los array, pero no necesitan una longitud.

// al igual que como definimos un array, pero no le pasamos la longitud esta vez
var fslice []int

Entonces definimos un slice, e inicializamos sus datos.

slice := []byte {'a', 'b', 'c', 'd'}

Un slice puede ser re-definido desde otro slices o arrays existente. Un slice usa array[i:j] para tomar sus valores, donde i es el comienzo del indice y j es el final del indice, note que array[j] no será un, ahora la longitud del slice es j-i.

// definimos un slice con 10 elementos donde el tipo es byte
var ar = [10]byte {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}

// definimos dos slices de tipo []byte
var a, b []byte

// a apunta  a los elementos del tercero al quinto elemento en el array ar.
a = ar[2:5]
// ahora tiene los elementos ar[2],ar[3] y ar[4]

// b es otro slice del array ar
b = ar[3:5]
// ahora b tiene los elementos ar[3] y ar[4]

Note la diferencia entre slice y array cuando los definimos. Nosotros usamos […] para que Go calcule la longitud pero usamos[] para definir unicamente slice.

Su estructura de datos subyacente.

Imagen 2.3 Similitud entre slice y array

slice tiene algunas operaciones utiles.

  • slice comienza en 0, ar[:n] es igual a ar[0:n]
  • El segundo indice sera la longitud del slice si lo omitimos, ar[n:] será igual a ar[n:len(ar)].
  • Se puede usar ar[:] para tomar todo el array, la razón de esto se explica en las dos anteriores explicaciones.

Más ejemplos acerca de slice

// definimos un array
var array = [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
// definimos dos slices
var aSlice, bSlice []byte

// Algunas operaciones útiles
aSlice = array[:3] // es igual a aSlice = array[0:3] aSlice tiene los elementos a,b,c
aSlice = array[5:] // es igual a aSlice = array[5:10] aSlice tiene los elementos f,g,h,i,j
aSlice = array[:]  // es igual a aSlice = array[0:10] aSlice tiene todos los elementos

// slice desde slice
aSlice = array[3:7]  // aSlice tiene los elementos d,e,f,g,len=4,cap=7
bSlice = aSlice[1:3] // bSlice contiene aSlice[1], aSlice[2], entonces este tendrá los elementos e,f
bSlice = aSlice[:3]  // bSlice contiene aSlice[0], aSlice[1], aSlice[2], entonces este tiene d,e,f
bSlice = aSlice[0:5] // bSlice se puede expandir, ahora bSlice contiene  d,e,f,g,h
bSlice = aSlice[:]   // bSlice tiene los mismos elementos que aSlice, que son d,e,f,g

slice es un tipo de referencia, por lo que si uno se modifica entonces afectará al resto. Por ejemplo, con los elementos de anteriores aSlice y bSlice, si se modifica el valor de algún elemento en aSlice, bSlice será modificado también.

slice por definición es como una estructura, este contiene tres partes.

  • Un puntero que apunta donde comienza el slice.

  • la longitud del slice.

  • Capacidad, la longitud de donde comienza el indice hacia donde termina el indice del slice.

      Array_a := [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
      Slice_a := Array_a[2:5]

Los fundamentos de la estructura de datos del código anterior es el siguiente.

Imagen 2.4 Array información del slice

Hay algunas funciones integradas para las slice.

  • len nos devuelve la longitud del slice.
  • cap nos devuelve la longitud máxima del slice
  • append añade uno o mas elementos al slice, y nos devuelve el slice .
  • copy copia elementos de un slice hacia otro, y nos devuelve el número de elementos que fueron copiados.

Atención: append va a cambiar el array al que apunta el slice, y afectará a los otros que apuntan al mismo array. Ademas si no tiene una longitud suficiente para el slice ((cap-len) == 0), append nos va a devolver un nuevo array para este slice, en este punto, los otros slices van a apuntan al anterior array nos serán afectados.

map

map es como un diccionario en Python, lo usamos de la siguiente forma map[claveTipo]tipoValor para definirlo.

Vamos a ver algo de código, para configurar o tomar un valor de un map es como en un slice, usamos la key para ello, pero el indice en un slice puede ser solo de tipo int, y en un map podemos usar mucho mas que eso, int, string, o lo que quieras. Ademas, todos ellos pueden utilizar == y != para comparar valores.

// usamos string tipo de clave (key), int como el tipo de valor, y debemos usar `make` para inicializarlo.
var numbers map[string] int
// otra forma de definir un map
numbers := make(map[string]int)
numbers["one"] = 1  // asignamos el valor para la clave
numbers["ten"] = 10 
numbers["three"] = 3

fmt.Println("El tercer número es: ", numbers["three"]) // tomamos el valor
// Esto imprime: El tercer número es: 3

map es como un formularios en nuestras vidas, en el lado izquierdo estan las claves, y en el otro lado el valor.

Algunas cosas a tener en cuenta cuando usamos map.

  • map esta y es desordenado, cada vez que imprimamos map vamos a obtener diferentes resultados. Es imposible obtener un valor por índice, debe usar la clave.
  • map no tiene una longitud fija, es un tipo de referencia al igual que los slice.
  • len también funciona en map, este devuelve el número de claves que tiene el map.
  • Es muy sencillo modificar el valor de un elemento del map, simplemente usamos numbers["one"]=11 para cambiar el valor que contiene la clave one a 11.

Se puede usar la forma clave:valor para inicializar los valores del map, y map tiene internamente los métodos para verificar si la clave existe.

Utilice delete para borrar un elemento del map.

// Inicialice un map
rating := map[string]float32 {"C":5, "Go":4.5, "Python":4.5, "C++":2 }
// map nos devuelve dos valores. El segundo valor ,ok es false, si la clave no existe true de otra forma.
csharpRating, ok := rating["C#"]
if ok {
	fmt.Println("C# se encuentra en el map y su ranking es ", csharpRating)
} else {
fmt.Println("No tenemos un ranking asociado con C# en este map")
}

delete(rating, "C")  // borramos el elemento con la clave "c"

Como se dijo anteriormente, map es un tipo por referencia, si dos maps apuntan a los mismos datos, cualquier cambio van a afectar a ambos.

m := make(map[string]string)
m["Hello"] = "Bonjour"
m1 := m
m1["Hello"] = "Salut"  // ahora el valor de  m["hello"] es Salut

make, new

make realiza la asignación de memoria para construir las estructuras como los map, slice, o channel, new es para la reserva de memoria de cada tipo.

new(T) reservamos la memoria para el valor vacío del tipo T's, devuelve la dirección de memoria, que es el valor del tipo *T. En los términos de Go, este devuelve un puntero, que apunta al valor vacío del tipo T.

new devuelve punteros.

La función incorporada make(T, args) tiene un diferente efecto que new(T), make puede ser usado para slice, map, y channel, y nos devuelve un valor inicializado con valor inicial del tipo T. La razón para hacer esto es porque estos tres tipos por debajo deben ser inicializados antes que los punteros a ellos. Por ejemplo, un slice contiene punteros que por debajo apuntan a un array, longitud y capacidad. Antes de que esta información sea inicializada, un slice es nil, entonces para un slice, map, channel, make inicializa los datos que tienen por debajo, y asigna algunos valores configurables.

make devuelve valores distinto de cero.

La siguiente imagen nos muestra como son diferentes new y make.

Imagen 2.5 Reserva de memoria de fondo de make y new

En cuando a valor cero, no significa valor vacío. Es el valor de las variables cuando no son asignados de forma manual, usualmente es 0, esta es la lista de algunos de estos valores.

int     0
int8    0
int32   0
int64   0
uint    0x0
rune    0 // el tipo actual de rune es int32
byte    0x0 // el tipo actual de byte es uint8
float32 0 // la longitud es 4 byte
float64 0 // la longitud es 8 byte
bool    false
string  ""

Enlaces