Tutorial de Golang: aprenda el lenguaje de programación Go para principiantes

¿Qué es ir?

Go (también conocido como Golang) es un lenguaje de programación de código abierto desarrollado por Google. Es un lenguaje compilado de tipado estático. Go admite programación concurrente, es decir, permite ejecutar múltiples procesos simultáneamente. Esto se logra mediante canales, goroutines, etc. El lenguaje Go tiene recolección de basura que se encarga de la gestión de memoria y permite la ejecución diferida de funciones.

Aprenderemos todos los conceptos básicos de Golang en este tutorial de Learn Go Language.

Cómo descargar e instalar GO

Paso 1) Ve a https://golang.org/dl/. Descarga el binario para tu sistema operativo.

Paso 2) Double Haga clic en el instalador y haga clic en Ejecutar.

Paso 3) Haga clic en Siguiente

Paso 4) Seleccione la carpeta de instalación y haga clic en Siguiente.

Paso 5) Haga clic en Finalizar una vez que se complete la instalación.

Paso 6) Una vez completada la instalación podrás verificarla abriendo el terminal y escribiendo

go version

Esto mostrará la versión de go instalada.

Su programa First Go: ¡Go Hello World!

Crea una carpeta llamada StudyGo. En este tutorial de lenguaje Go, crearemos nuestros programas Go dentro de esta carpeta. Los archivos Go se crean con la extensión .ir. Puede ejecutar programas Go usando la sintaxis

go run <filename>

Cree un archivo llamado first.go, agregue el siguiente código y guárdelo.

package main
import ("fmt")

func main() {
	fmt.Println("Hello World! This is my first Go program\n")
}

Navegue a esta carpeta en su terminal. Ejecute el programa usando el comando

Ve a correr primero.

Puedes ver la impresión de salida.

Hello World! This is my first Go program

Ahora analicemos el programa anterior.

paquete principal: cada programa de Go Language debe comenzar con un nombre de paquete. Go nos permite usar paquetes en otros programas de Go y, por lo tanto, admite la reutilización del código. La ejecución de un programa Go comienza con el código dentro del paquete llamado main.

import fmt: importa el paquete fmt. Este paquete implementa las funciones de E/S.

func main(): esta es la función desde la cual comienza la ejecución del programa. La función principal siempre debe colocarse en el paquete principal. En main(), puedes escribir el código dentro de {}.

fmt.Println: esto imprimirá el texto en la pantalla mediante la función Println de fmt.

Nota: En las secciones siguientes de este tutorial de Go, cuando mencionas ejecutar/ejecutar el código, significa guardar el código en un archivo con extensión .go y ejecutarlo usando la sintaxis.

    go run <filename>

Tipos de datos

Los tipos (tipos de datos) representan el tipo de valor almacenado en una variable, el tipo de valor que devuelve una función, etc.

Hay tres tipos básicos en Go Language

Tipos numéricos – Representan valores numéricos que incluyen valores enteros, de punto flotante y complejos. Los distintos tipos numéricos son:

int8: enteros de 8 bits con signo.

int16: enteros de 16 bits con signo.

int32: enteros de 32 bits con signo.

int64: enteros de 64 bits con signo.

uint8: enteros sin signo de 8 bits.

uint16: enteros sin signo de 16 bits.

uint32: enteros sin signo de 32 bits.

uint64: enteros sin signo de 64 bits.

float32: números de coma flotante de 32 bits.

float64: números de coma flotante de 64 bits.

complex64 – tiene partes reales e imaginarias float32.

complex128 – tiene partes reales e imaginarias float32.

Tipos de cuerdas – Representa una secuencia de bytes (caracteres). Se pueden realizar varias operaciones con cadenas, como concatenación de cadenas, extracción de subcadenas, etc.

Tipos booleanos – Representa 2 valores, verdadero o falso.

Interfaz de Golang

Interfaz de Golang es una colección de firmas de métodos utilizadas por un tipo para implementar el comportamiento de los objetos. El objetivo principal de la interfaz Golang es proporcionar firmas de métodos con nombres, argumentos y tipos de retorno. Depende de un tipo declarar e implementar el método. Se puede declarar una interfaz en Golang utilizando la palabra clave "interfaz".

Variables

Las variables apuntan a una ubicación de memoria que almacena algún tipo de valor. El parámetro de tipo (en la sintaxis siguiente) representa el tipo de valor que se puede almacenar en la ubicación de la memoria.

La variable se puede declarar usando la sintaxis

    var <variable_name> <type>

Una vez que declara una variable de un tipo, puede asignar la variable a cualquier valor de ese tipo.

También puede dar un valor inicial a una variable durante la declaración misma usando

    var <variable_name> <type> = <value>

Si declara la variable con un valor inicial, vaya e infiera el tipo de variable a partir del tipo de valor asignado. Entonces puedes omitir el tipo durante la declaración usando la sintaxis

    var <variable_name> = <value>

Además, puedes declarar múltiples variables con la sintaxis

    var <variable_name1>, <variable_name2>  = <value1>, <value2>

El siguiente programa en este tutorial de Go tiene algunos ejemplos de declaraciones de variables en Golang.

 
package main
import "fmt"

func main() {
    //declaring a integer variable x
    var x int
    x=3 //assigning x the value 3 
    fmt.Println("x:", x) //prints 3
    
    //declaring a integer variable y with value 20 in a single statement and prints it
    var y int=20
    fmt.Println("y:", y)
    
    //declaring a variable z with value 50 and prints it
    //Here type int is not explicitly mentioned 
    var z=50
    fmt.Println("z:", z)
    
    //Multiple variables are assigned in single line- i with an integer and j with a string
    var i, j = 100,"hello"
    fmt.Println("i and j:", i,j)
}

La salida será

x: 3
y: 20
z: 50
i and j: 100 hello

Go Language también proporciona una manera fácil de declarar las variables con valor omitiendo la palabra clave var usando

    <variable_name> := <value>

Tenga en cuenta que utilizó := en lugar de =. No puedes usar := solo para asignar un valor a una variable que ya está declarada. := se utiliza para declarar y asignar valor.

Crea un archivo llamado allow.go con el siguiente código

package main
import ("fmt")

func main() {
	a := 20
	fmt.Println(a)

	//gives error since a is already declared
	a := 30
	fmt.Println(a)
}

Ejecute go run asignar.go para ver el resultado como

./assign.go:7:4: no new variables on left side of :=		

Las variables declaradas sin un valor inicial tendrán 0 para tipos numéricos, falso para booleanos y cadena vacía para cadenas.

Constantes

Las variables constantes son aquellas variables cuyo valor no se puede cambiar una vez asignado. Una constante en el lenguaje de programación Go se declara utilizando la palabra clave "const"

Crea un archivo llamado constant.go y con el siguiente código

package main
import ("fmt")

func main() {
	const b =10
	fmt.Println(b)
	b = 30
	fmt.Println(b)
}

Ejecute go run constante.go para ver el resultado como

.constant.go:7:4: cannot assign to b

Para ejemplos de bucles

Los bucles se utilizan para ejecutar un bloque de declaraciones repetidamente según una condición. La mayoría de los lenguajes de programación proporcionan 3 tipos de bucles: for, while y do while. Pero el lenguaje de programación Go solo admite bucles.

La sintaxis de un bucle for de Golang es

for initialisation_expression; evaluation_expression; iteration_expression{
   // one or more statement
}

La expresión_inicialización se ejecuta primero (y solo una vez) en el bucle for de Golang.

Luego se evalúa la expresión_evaluación y, si es verdadera, se ejecuta el código dentro del bloque.

Se ejecuta el ID de iteration_expression y se vuelve a evaluar la expresión_evaluación. Si es cierto, el bloque de instrucciones se ejecuta nuevamente. Esto continuará hasta que la expresión_evaluación se vuelva falsa.

Copie el siguiente programa en un archivo y ejecútelo para ver Golang for loop imprimiendo números del 1 al 5

package main
import "fmt"

func main() {  
var i int
for i = 1; i <= 5; i++ {
fmt.Println(i)
    }
}

La salida es

1
2
3
4
5

Si mas

Si no, es una declaración condicional. La sinaxis es

if condition{
// statements_1
}else{
// statements_2
}

Aquí se evalúa la condición y, si es verdadera, se ejecutarán las declaraciones_1; de lo contrario, se ejecutará la declaración_2.

También puedes usar la declaración if sin más. También puede tener declaraciones if else encadenadas. Los siguientes programas explicarán más sobre if else.

Ejecute el siguiente programa. Comprueba si un número, x, es menor que 10. Si es así, imprimirá "x es menor que 10".

package main
import "fmt"

func main() {  
    var x = 50
    if x < 10 {
        //Executes if x < 10
        fmt.Println("x is less than 10")
    } 
}

Aquí, dado que el valor de x es mayor que 10, la declaración dentro de la condición del bloque if no se ejecutará.

Ahora vea el siguiente programa. En este tutorial del lenguaje de programación Go, tenemos un bloque else que se ejecutará si falla la evaluación if.

package main
import "fmt"

func main() {  
    var x = 50
    if x < 10 {
        //Executes if x is less than 10
        fmt.Println("x is less than 10")
    } else {
        //Executes if x >= 10
        fmt.Println("x is greater than or equals 10")
    }
}

Este programa le dará resultados.

x is greater than or equals 10

Ahora, en este tutorial de Go, veremos un programa con múltiples bloques if else (encadenados if else). Ejecute el siguiente ejemplo de Go. Comprueba si un número es menor que 10, entre 10 y 90 o mayor que 90.

package main
import "fmt"

func main() {  
    var x = 100
    if x < 10 {
        //Executes if x is less than 10
        fmt.Println("x is less than 10")
    } else if x >= 10 && x <= 90 {
        //Executes if x >= 10 and x<=90
        fmt.Println("x is between 10 and 90")
    } else {
        //Executes if both above cases fail i.e x>90
        fmt.Println("x is greater than 90")
    }
}

Aquí primero la condición if verifica si x es menor que 10 y no lo es. Entonces verifica la siguiente condición (si no) si está entre 10 y 90, lo cual también es falso. Luego ejecuta el bloque en la sección else que proporciona la salida

x is greater than 90

Switch

Switch es otra declaración condicional. Las declaraciones de cambio evalúan una expresión y el resultado se compara con un conjunto de valores disponibles (casos). Una vez que se encuentra una coincidencia, se ejecutan las declaraciones asociadas con esa coincidencia (caso). Si no se encuentra ninguna coincidencia, no se ejecutará nada. También puede agregar un caso predeterminado para cambiar que se ejecutará si no se encuentran otras coincidencias. La sintaxis del interruptor es

switch expression {
    case value_1:
        statements_1
    case value_2:
        statements_2
    case value_n:
        statements_n
    default:
        statements_default
    }

Aquí el valor de la expresión se compara con los valores de cada caso. Una vez que se encuentra una coincidencia, se ejecutan las declaraciones asociadas con ese caso. Si no se encuentra ninguna coincidencia, se ejecutan las declaraciones de la sección predeterminada.

Ejecute el siguiente programa

package main
import "fmt"

func main() {  
    a,b := 2,1
    switch a+b {
    case 1:
        fmt.Println("Sum is 1")
    case 2:
        fmt.Println("Sum is 2")
    case 3:
        fmt.Println("Sum is 3")
    default:
        fmt.Println("Printing default")
    }
}

Obtendrá la salida como

Sum is 3		

Cambie el valor de a y b a 3 y el resultado será

Printing default

También puede tener varios valores en un caso separándolos con una coma.

Matrices

La matriz representa una secuencia nombrada de tamaño fijo de elementos del mismo tipo. No puede tener una matriz que contenga números enteros y caracteres. No puede cambiar el tamaño de una matriz una vez que define el tamaño.

La sintaxis para declarar una matriz es

var arrayname [size] type

A cada elemento de la matriz se le puede asignar un valor usando la sintaxis

arrayname [index] = value

El índice de matriz comienza desde 0 a tamaño-1.

Puede asignar valores a elementos de matriz durante la declaración usando la sintaxis

arrayname := [size] type {value_0,value_1,…,value_size-1} 

También puede ignorar el parámetro de tamaño al declarar la matriz con valores reemplazando tamaño con ... y el compilador encontrará la longitud a partir del número de valores. La sintaxis es

arrayname :=  […] type {value_0,value_1,…,value_size-1}

Puede encontrar la longitud de la matriz usando la sintaxis

len(arrayname)

Ejecute el siguiente ejemplo de Go para comprender la matriz

package main
import "fmt"

func main() {  
    var numbers [3] string //Declaring a string array of size 3 and adding elements 
    numbers[0] = "One"
    numbers[1] = "Two"
    numbers[2] = "Three"
    fmt.Println(numbers[1]) //prints Two
    fmt.Println(len(numbers)) //prints 3
    fmt.Println(numbers) // prints [One Two Three]

    directions := [...] int {1,2,3,4,5} // creating an integer array and the size of the array is defined by the number of elements 
    fmt.Println(directions) //prints [1 2 3 4 5]
    fmt.Println(len(directions)) //prints 5

    //Executing the below commented statement prints invalid array index 5 (out of bounds for 5-element array)
    //fmt.Println(directions[5]) 
}

Salida

Two
3
[One Two Three]
[1 2 3 4 5]
5

Función Golang Slice y Append

Un sector es una porción o segmento de una matriz. O es una vista o vista parcial de una matriz subyacente a la que apunta. Puede acceder a los elementos de un sector utilizando el nombre del sector y el número de índice tal como lo hace en una matriz. No puede cambiar la longitud de una matriz, pero puede cambiar el tamaño de un segmento.

Los contenidos de un segmento son en realidad punteros a los elementos de una matriz. Significa Si cambia cualquier elemento en un segmento, el contenido de la matriz subyacente también se verá afectado.

La sintaxis para crear un sector es

var slice_name [] type = array_name[start:end]

Esto creará un segmento llamado nombre_sector a partir de una matriz llamada nombre_matriz con los elementos en el índice de principio a fin-1.

Ahora, en este tutorial de Golang, ejecutaremos el siguiente programa. El programa creará una porción de la matriz y la imprimirá. Además, puede ver que modificar el contenido del segmento modificará la matriz real.

package main
import "fmt"

func main() {  
    // declaring array
    a := [5] string {"one", "two", "three", "four", "five"}
    fmt.Println("Array after creation:",a)

    var b [] string = a[1:4] //created a slice named b
    fmt.Println("Slice after creation:",b)

    b[0]="changed" // changed the slice data
    fmt.Println("Slice after modifying:",b)
    fmt.Println("Array after slice modification:",a)
}

Esto imprimirá el resultado como

Array after creation: [one two three four five]
Slice after creation: [two three four]
Slice after modifying: [changed three four]
Array after slice modification: [one changed three four five]

Hay ciertas funciones como Golang len, Golang append que puedes aplicar en sectores

len(nombre_porción) – devuelve la longitud del segmento

agregar(nombre_porción, valor_1, valor_2) – Golang append se utiliza para agregar valor_1 y valor_2 a un segmento existente.

agregar(slice_nale1,slice_name2…) – añade nombre_porción2 a nombre_porción1

Ejecute el siguiente programa.

package main
import "fmt"

func main() {  
	a := [5] string {"1","2","3","4","5"}
	slice_a := a[1:3]
	b := [5] string {"one","two","three","four","five"}
	slice_b := b[1:3]

    fmt.Println("Slice_a:", slice_a)
    fmt.Println("Slice_b:", slice_b)
    fmt.Println("Length of slice_a:", len(slice_a))
    fmt.Println("Length of slice_b:", len(slice_b))

    slice_a = append(slice_a,slice_b...) // appending slice
    fmt.Println("New Slice_a after appending slice_b :", slice_a)
    
    slice_a = append(slice_a,"text1") // appending value
    fmt.Println("New Slice_a after appending text1 :", slice_a)
}

La salida será

Slice_a: [2 3]
Slice_b: [two three]
Length of slice_a: 2
Length of slice_b: 2
New Slice_a after appending slice_b : [2 3 two three]
New Slice_a after appending text1 : [2 3 two three text1]

El programa primero crea 2 cortes e imprime su longitud. Luego agregó un segmento a otro y luego agregó una cadena al segmento resultante.

Funciones

Una función representa un bloque de declaraciones que realiza una tarea específica. Una declaración de función nos dice el nombre de la función, el tipo de retorno y los parámetros de entrada. La definición de función representa el código contenido en la función. La sintaxis para declarar la función es

func function_name(parameter_1 type, parameter_n type) return_type {
//statements
}

Los parámetros y tipos de retorno son opcionales. Además, puede devolver varios valores de una función.

Ahora, en este tutorial de Golang, ejecutemos el siguiente ejemplo de Golang. Aquí, la función denominada calc aceptará dos números y realizará la suma y la resta y devolverá ambos valores.

package main
import "fmt"

//calc is the function name which accepts two integers num1 and num2
//(int, int) says that the function returns two values, both of integer type.
func calc(num1 int, num2 int)(int, int) {  
    sum := num1 + num2
    diff := num1 - num2
    return sum, diff
}

func main() {  
    x,y := 15,10

    //calls the function calc with x and y an d gets sum, diff as output
    sum, diff := calc(x,y) 
    fmt.Println("Sum",sum)
    fmt.Println("Diff",diff) 
}

La salida será

Sum 25
Diff 5

Paquetes

Los paquetes se utilizan para organizar el código. En un proyecto grande, no es factible escribir código en un solo archivo. El lenguaje de programación Go nos permite organizar el código en diferentes paquetes. Esto aumenta la legibilidad y reutilización del código. Un programa Go ejecutable debe contener un paquete llamado main y la ejecución del programa comienza desde la función llamada main. Puede importar otros paquetes en nuestro programa usando la sintaxis

import package_name

Veremos y discutiremos en este tutorial de Golang, cómo crear y usar paquetes en el siguiente ejemplo de Golang.

Paso 1) Cree un archivo llamado package_example.go y agregue el siguiente código

package main
import "fmt"
//the package to be created
import "calculation"

func main() {  
	x,y := 15,10
	//the package will have function Do_add()
sum := calculation.Do_add(x,y)
fmt.Println("Sum",sum) 
}

En el programa anterior, fmt es un paquete que el lenguaje de programación Go nos proporciona principalmente para fines de E/S. Además, puede ver un paquete llamado cálculo. Dentro de main() puedes ver una suma de pasos: = cálculo.Do_add(x,y). Significa que estás invocando la función Do_add desde el cálculo del paquete.

Paso 2) Primero, debe crear el cálculo del paquete dentro de una carpeta con el mismo nombre en la carpeta src de go. La ruta instalada de go se puede encontrar en la variable PATH.

Para mac, busque la ruta ejecutando echo $PATH

Entonces la ruta es /usr/local/go

Para Windows, busque la ruta ejecutando echo %GOROOT%

Aquí la ruta es C:\Go\

Paso 3) Vaya a la carpeta src (/usr/local/go/src para Mac y C:\Go\src para Windows). Ahora, según el código, el nombre del paquete es cálculo. Go requiere que el paquete se coloque en un directorio con el mismo nombre dentro del directorio src. Cree un directorio llamado cálculo en la carpeta src.

Paso 4) Cree un archivo llamado calc.go (puede darle cualquier nombre, pero el nombre del paquete en el código importa. Aquí debería estar el cálculo) dentro del directorio de cálculo y agregue el siguiente código

package calculation
  
func Do_add(num1 int, num2 int)(int) {
    sum := num1 + num2
    return sum
}

Paso 5) Ejecute el comando go install desde el directorio de cálculo que compilará calc.go.

Paso 6) Ahora regrese a package_example.go y ejecute go run package_example.go. La salida será Sum 25.

Tenga en cuenta que el nombre de la función Do_add comienza con una letra mayúscula. Esto se debe a que en Go, si el nombre de la función comienza con una letra mayúscula, significa que otros programas pueden verla (acceder), de lo contrario, otros programas no pueden acceder a ella. Si el nombre de la función fuera do_add, entonces habría recibido el error

no se puede hacer referencia al nombre no exportado cálculo.calc.

Aplazar y apilar aplazamientos

Las declaraciones diferidas se utilizan para diferir la ejecución de una llamada a función hasta que la función que contiene la declaración diferida complete su ejecución.

Aprendamos esto con un ejemplo:

package main
import "fmt"

func sample() {  
    fmt.Println("Inside the sample()")
}
func main() {  
    //sample() will be invoked only after executing the statements of main()
    defer sample()
    fmt.Println("Inside the main()")
}

La salida será

Inside the main()
Inside the sample()

Aquí la ejecución de sample() se difiere hasta que se completa la ejecución de la función adjunta (main()).

Apilar aplazar consiste en utilizar múltiples declaraciones de aplazar. Suponga que tiene varias declaraciones diferidas dentro de una función. Go coloca todas las llamadas a funciones diferidas en una pila y, una vez que regresa la función adjunta, las funciones apiladas se ejecutan en el Orden último en entrar, primero en salir (LIFO). Puedes ver esto en el siguiente ejemplo.

Ejecute el siguiente código

package main
import "fmt"

func display(a int) {  
    fmt.Println(a)
}
func main() {  
    defer display(1)
    defer display(2)
    defer display(3)
    fmt.Println(4)
}

La salida será

4
3
2
1			

Aquí el código dentro de main() se ejecuta primero, y luego las llamadas a funciones diferidas se ejecutan en orden inverso, es decir, 4, 3,2,1.

Punteros

Antes de explicar los punteros, analicemos primero el operador "&". El operador "&" se utiliza para obtener la dirección de una variable. Esto significa que "&a" imprimirá la dirección de memoria de la variable a.

En este tutorial de Golang, ejecutaremos el siguiente programa para mostrar el valor de una variable y la dirección de esa variable.

package main
import "fmt"

func main() {
	a := 20
	fmt.Println("Address:",&a)
	fmt.Println("Value:",a)
}

El resultado será

Address: 0xc000078008
Value: 20

Una variable de puntero almacena la dirección de memoria de otra variable. Puede definir un puntero usando la sintaxis

	var variable_name *type

El asterisco (*) representa que la variable es un puntero. Comprenderá más ejecutando el siguiente programa.

package main
import "fmt"

func main() {
	//Create an integer variable a with value 20
	a := 20
	
	//Create a pointer variable b and assigned the address of a
	var b *int = &a

	//print address of a(&a) and value of a  
	fmt.Println("Address of a:",&a)
	fmt.Println("Value of a:",a)

	//print b which contains the memory address of a i.e. &a
	fmt.Println("Address of pointer b:",b)

	//*b prints the value in memory address which b contains i.e. the value of a
	fmt.Println("Value of pointer b",*b)

	//increment the value of variable a using the variable b
	*b = *b+1

	//prints the new value using a and *b
	fmt.Println("Value of pointer b",*b)
	fmt.Println("Value of a:",a)}

La salida será

Address of a: 0x416020
Value of a: 20
Address of pointer b: 0x416020
Value of pointer b 20
Value of pointer b 21
Value of a: 21

Estructuras

Una estructura es un tipo de datos definido por el usuario que a su vez contiene un elemento más del mismo o diferente tipo.

Usar una estructura es un proceso de 2 pasos.

Primero, cree (declare) un tipo de estructura.

En segundo lugar, cree variables de ese tipo para almacenar valores.

Las estructuras se utilizan principalmente cuando se desea almacenar datos relacionados juntos.

Considere una información del empleado que tenga nombre, edad y dirección. Puedes manejar esto de 2 maneras.

Cree 3 matrices: una matriz almacena los nombres de los empleados, otra almacena la edad y la tercera almacena la edad.

Declare un tipo de estructura con 3 campos: nombre, dirección y edad. Cree una matriz de ese tipo de estructura donde cada elemento sea un objeto de estructura que tenga nombre, dirección y edad.

El primer enfoque no es eficiente. En este tipo de escenarios, las estructuras son más convenientes.

La sintaxis para declarar una estructura es

type structname struct {
   variable_1 variable_1_type
   variable_2 variable_2_type
   variable_n variable_n_type
}

Un ejemplo de declaración de estructura es

type emp struct {
    name string
    address string
    age int
}

Aquí se crea un nuevo tipo definido por el usuario llamado emp. Ahora puedes crear variables del tipo emp usando la sintaxis

	var variable_name struct_name

Un ejemplo es

var empdata1 emp 

Puede establecer valores para empdata1 como

empdata1.name = "John"
	empdata1.address = "Street-1, Bangalore"
	empdata1.age = 30

También puede crear una variable de estructura y asignar valores mediante

empdata2 := emp{"Raj", "Building-1, Delhi", 25}

Aquí es necesario mantener el orden de los elementos. Raj se asignará al nombre, el siguiente elemento a la dirección y el último a la edad.

Ejecute el siguiente código

package main
import "fmt"

//declared the structure named emp
type emp struct {
        name string
        address string
        age int
}       

//function which accepts variable of emp type and prints name property
func display(e emp) {
          fmt.Println(e.name)
}

func main() {
// declares a variable, empdata1, of the type emp
var empdata1 emp
//assign values to members of empdata1
empdata1.name = "John"
empdata1.address = "Street-1, London"
empdata1.age = 30

//declares and assign values to variable empdata2 of type emp
empdata2 := emp{"Raj", "Building-1, Paris", 25}

//prints the member name of empdata1 and empdata2 using display function
display(empdata1)
display(empdata2)
}

Salida

John
Raj

Métodos (no funciones)

Un método es una función con un argumento receptor. ArchiTécnicamente, está entre la palabra clave func y el nombre del método. La sintaxis de un método es

func (variable variabletype) methodName(parameter1 paramether1type) {  
}

Convirtamos el programa de ejemplo anterior para usar métodos en lugar de funciones.

package main
import "fmt"

//declared the structure named emp
type emp struct {
    name string
    address string
    age int
}

//Declaring a function with receiver of the type emp
func(e emp) display() {
    fmt.Println(e.name)
}

func main() {
    //declaring a variable of type emp
    var empdata1 emp
    
    //Assign values to members
    empdata1.name = "John"
    empdata1.address = "Street-1, Lodon"
    empdata1.age = 30

    //declaring a variable of type emp and assign values to members
    empdata2 := emp {
        "Raj", "Building-1, Paris", 25}

    //Invoking the method using the receiver of the type emp
   // syntax is variable.methodname()
    empdata1.display()
    empdata2.display()
}

Go no es un lenguaje orientado a objetos y no tiene el concepto de clase. Los métodos dan una idea de lo que se hace en los programas orientados a objetos donde las funciones de una clase se invocan usando la sintaxis nombreobjeto.nombrefunción()

Concurrencia

Go admite la ejecución simultánea de tareas, es decir, puede ejecutar varias tareas simultáneamente. Es diferente del concepto de paralelismo. En el paralelismo, una tarea se divide en pequeñas subtareas y se ejecutan en paralelo, pero en la concurrencia, se ejecutan varias tareas simultáneamente. La concurrencia se logra en Go mediante Goroutines y Canales.

gorutinas

Una gorutina es una función que puede ejecutarse simultáneamente con otras funciones. Por lo general, cuando se invoca una función, el control se transfiere a la función llamada y, una vez completada la ejecución, el control regresa a la función que llama. La función que llama luego continúa su ejecución. La función que llama espera a que la función invocada complete la ejecución antes de continuar con el resto de las declaraciones.

Pero en el caso de goroutine, la función que llama no esperará a que se complete la ejecución de la función invocada. Continuará ejecutándose con las siguientes declaraciones. Puede tener varias gorutinas en un programa.

Además, el programa principal saldrá una vez que complete la ejecución de sus declaraciones y no esperará a que se completen las rutinas invocadas.

Goroutine se invoca utilizando la palabra clave go seguida de una llamada a función.

Ejemplo

go add(x,y)

Comprenderá las gorutinas con los siguientes ejemplos de Golang. Ejecute el siguiente programa

package main
import "fmt"
    
func display() {
	for i:=0; i<5; i++ {
		fmt.Println("In display")
	}
}

func main() {
	//invoking the goroutine display()
	go display()
	//The main() continues without waiting for display()
	for i:=0; i<5; i++ {
		fmt.Println("In main")
	}
}

La salida será

In main
In main
In main
In main
In main

Aquí el programa principal completó la ejecución incluso antes de que comenzara la rutina. display() es una rutina que se invoca usando la sintaxis

go function_name(parameter list)

En el código anterior, main() no espera a que se complete display(), y main() completó su ejecución antes de que display() ejecutara su código. Entonces la declaración de impresión dentro de display() no se imprimió.

Ahora modificamos el programa para imprimir también las declaraciones desde display(). Agregamos un retraso de 2 segundos en el bucle for de main() y un retraso de 1 segundo en el bucle for de display().

package main
import "fmt"
import "time"
    
func display() {
	for i:=0; i<5; i++ {
		time.Sleep(1 * time.Second)
		fmt.Println("In display")
	}
}

func main() {
	//invoking the goroutine display()
	go display()
	for i:=0; i<5; i++ {
		time.Sleep(2 * time.Second)
		fmt.Println("In main")
	}
}

La salida será algo similar a

In display
In main
In display
In display
In main
In display
In display
In main
In main
In main

Aquí puede ver que ambos bucles se ejecutan de forma superpuesta debido a la ejecución simultánea.

Canales

Los canales son una forma para que las funciones se comuniquen entre sí. Se puede pensar como un medio donde una rutina coloca los datos y otra rutina accede a ellos en el servidor Golang.

Un canal se puede declarar con la sintaxis

channel_variable := make(chan datatype)

Ejemplo:

	ch := make(chan int)

Puedes enviar datos a un canal usando la sintaxis

channel_variable <- variable_name

Ejemplo

    ch <- x

Puede recibir datos de un canal usando la sintaxis

    variable_name := <- channel_variable

Ejemplo

   y := <- ch

En los ejemplos de goroutine del lenguaje Go anteriores, ha visto que el programa principal no espera a la goroutine. Pero ese no es el caso cuando se trata de canales. Supongamos que si una gorutina envía datos al canal, main() esperará la declaración que recibe los datos del canal hasta que obtenga los datos.

Verá esto en los siguientes ejemplos de lenguaje Go. Primero, escriba una rutina normal y observe el comportamiento. Luego modifique el programa para usar canales y vea el comportamiento.

Ejecute el siguiente programa

package main
import "fmt"
import "time"
    
func display() {
	time.Sleep(5 * time.Second)
	fmt.Println("Inside display()")
}

func main() {
	go display()
	fmt.Println("Inside main()")
}

La salida será

Inside main()

main() finalizó la ejecución y salió antes de que se ejecutara la rutina. Entonces la impresión dentro de display() no se ejecutó.

Ahora modifique el programa anterior para usar canales y ver el comportamiento.

package main
import "fmt"
import "time"
    
func display(ch chan int) {
	time.Sleep(5 * time.Second)
	fmt.Println("Inside display()")
	ch <- 1234
}

func main() {
	ch := make(chan int) 
	go display(ch)
	x := <-ch
	fmt.Println("Inside main()")
	fmt.Println("Printing x in main() after taking from channel:",x)
}

La salida será

Inside display()
Inside main()
Printing x in main() after taking from channel: 1234

Aquí lo que sucede es que main() al llegar a x := <-ch esperará datos en el canal ch. El display() tiene una espera de 5 segundos y luego envía datos al canal ch. El main() al recibir los datos del canal se desbloquea y continúa su ejecución.

El remitente que envía datos al canal puede informar a los receptores que no se agregarán más datos al canal al cerrarlo. Esto se utiliza principalmente cuando se utiliza un bucle para enviar datos a un canal. Un canal se puede cerrar usando

close(channel_name)

Y en el extremo del receptor, es posible verificar si el canal está cerrado usando una variable adicional mientras se obtienen datos del canal usando

variable_name, status := <- channel_variable

Si el estado es Verdadero, significa que recibió datos del canal. Si es falso, significa que estás intentando leer desde un canal cerrado.

También puede utilizar canales para la comunicación entre gorutinas. Es necesario utilizar 2 gorutinas: una envía datos al canal y la otra recibe los datos del canal. Vea el programa a continuación

package main
import "fmt"
import "time"

//This subroutine pushes numbers 0 to 9 to the channel and closes the channel
func add_to_channel(ch chan int) {	
	fmt.Println("Send data")
	for i:=0; i<10; i++ {
		ch <- i //pushing data to channel
	}
	close(ch) //closing the channel

}

//This subroutine fetches data from the channel and prints it.
func fetch_from_channel(ch chan int) {
	fmt.Println("Read data")
	for {
		//fetch data from channel
x, flag := <- ch

		//flag is true if data is received from the channel
//flag is false when the channel is closed
if flag == true {
			fmt.Println(x)
		}else{
			fmt.Println("Empty channel")
			break	
		}	
	}
}

func main() {
	//creating a channel variable to transport integer values
	ch := make(chan int)

	//invoking the subroutines to add and fetch from the channel
	//These routines execute simultaneously
	go add_to_channel(ch)
	go fetch_from_channel(ch)

	//delay is to prevent the exiting of main() before goroutines finish
	time.Sleep(5 * time.Second)
	fmt.Println("Inside main()")
}

Aquí hay 2 subrutinas, una envía datos al canal y la otra los imprime. La función add_to_channel suma los números del 0 al 9 y cierra el canal. Simultáneamente, la función fetch_from_channel espera

x, flag := <- ch y una vez que los datos estén disponibles, los imprime. Sale una vez que la bandera es falsa, lo que significa que el canal está cerrado.

La espera en main() se da para evitar la salida de main() hasta que las gorutinas finalicen la ejecución.

Ejecute el código y vea el resultado como

Read data
Send data
0
1
2
3
4
5
6
7
8
9
Empty channel
Inside main()

Seleccionar

Seleccionar puede verse como una declaración de cambio que funciona en canales. Aquí las declaraciones del caso serán una operación de canal. Por lo general, las declaraciones de cada caso se leerán desde el canal. Cuando cualquiera de los casos está listo (se lee el canal), se ejecuta la declaración asociada con ese caso. Si hay varios casos listos, elegirá uno al azar. Puede tener un caso predeterminado que se ejecute si ninguno de los casos está listo.

Veamos el siguiente código.

package main
import "fmt"
import "time"

//push data to channel with a 4 second delay
func data1(ch chan string) {  
    time.Sleep(4 * time.Second)
    ch <- "from data1()"
}

//push data to channel with a 2 second delay
func data2(ch chan string) {  
    time.Sleep(2 * time.Second)
    ch <- "from data2()"
}

func main() {
    //creating channel variables for transporting string values
    chan1 := make(chan string)
    chan2 := make(chan string)
    
    //invoking the subroutines with channel variables
    go data1(chan1)
    go data2(chan2)
    
    //Both case statements wait for data in the chan1 or chan2.
    //chan2 gets data first since the delay is only 2 sec in data2().
    //So the second case will execute and exits the select block
    select {
    case x := <-chan1:
        fmt.Println(x)
    case y := <-chan2:
        fmt.Println(y)
    }
}

La ejecución del programa anterior dará el resultado:

from data2()

Aquí la declaración de selección espera a que los datos estén disponibles en cualquiera de los canales. data2() agrega datos al canal después de un período de suspensión de 2 segundos, lo que hará que se ejecute el segundo caso.

Agregue un caso predeterminado a la selección en el mismo programa y vea el resultado. Aquí, al llegar al bloque de selección, si ningún caso tiene datos listos en el canal, ejecutará el bloque predeterminado sin esperar a que los datos estén disponibles en ningún canal.

package main
import "fmt"
import "time"

//push data to channel with a 4 second delay
func data1(ch chan string) {  
    time.Sleep(4 * time.Second)
    ch <- "from data1()"
}

//push data to channel with a 2 second delay
func data2(ch chan string) {  
    time.Sleep(2 * time.Second)
    ch <- "from data2()"
}

func main() {
    //creating channel variables for transporting string values  
    chan1 := make(chan string)
    chan2 := make(chan string)
    
    //invoking the subroutines with channel variables
    go data1(chan1)
    go data2(chan2)

    //Both case statements check for data in chan1 or chan2.
    //But data is not available (both routines have a delay of 2 and 4 sec)
    //So the default block will be executed without waiting for data in channels.
    select {
    case x := <-chan1:
        fmt.Println(x)
    case y := <-chan2:
        fmt.Println(y)
    default:
    	fmt.Println("Default case executed")
    }
}

Este programa dará el resultado:

Default case executed			

Esto se debe a que cuando llegó el bloque de selección, ningún canal tenía datos para leer. Entonces, se ejecuta el caso predeterminado.

Mutex

Mutex es la forma abreviada de exclusión mutua. Mutex se utiliza cuando no se desea permitir que varias subrutinas accedan a un recurso al mismo tiempo. Mutex tiene dos métodos: bloquear y desbloquear. Mutex está incluido en el paquete sync, por lo que hay que importar el paquete sync. Las instrucciones que se deben ejecutar de forma mutuamente exclusiva se pueden colocar dentro de mutex.Lock() y mutex.Unlock().

Aprendamos mutex con un ejemplo que cuenta el número de veces que se ejecuta un bucle. En este programa esperamos que la rutina ejecute el bucle 10 veces y el recuento se almacene en suma. Llamas a esta rutina 3 veces, por lo que el recuento total debe ser 30. El recuento se almacena en una variable global.

Primero, ejecuta el programa sin mutex.

package main
import "fmt"
import "time"
import "strconv"
import "math/rand"
//declare count variable, which is accessed by all the routine instances
var count = 0

//copies count to temp, do some processing(increment) and store back to count
//random delay is added between reading and writing of count variable
func process(n int) {
	//loop incrementing the count by 10
	for i := 0; i < 10; i++ {
		time.Sleep(time.Duration(rand.Int31n(2)) * time.Second)
		temp := count
		temp++
		time.Sleep(time.Duration(rand.Int31n(2)) * time.Second)
		count = temp
	}
	fmt.Println("Count after i="+strconv.Itoa(n)+" Count:", strconv.Itoa(count))
}

func main() {
	//loop calling the process() 3 times
	for i := 1; i < 4; i++ {
		go process(i)
	}

	//delay to wait for the routines to complete
	time.Sleep(25 * time.Second)
	fmt.Println("Final Count:", count)
}

Ver el resultado

 Count after i=1 Count: 11
Count after i=3 Count: 12
Count after i=2 Count: 13
Final Count: 13

El resultado podría ser diferente cuando lo ejecutes pero el resultado final no será 30.

Aquí lo que sucede es que 3 gorutinas intentan aumentar el recuento de bucles almacenado en la variable recuento. Supongamos que en un momento el recuento es 5 y goroutine1 va a incrementar el recuento a 6. Los pasos principales incluyen

Copiar recuento a temperatura

Incrementar temperatura

Almacenar la temperatura nuevamente para contar

Supongamos que poco después de realizar el paso 3 de goroutine1; otra gorutina podría tener un valor antiguo, digamos que 3 realiza los pasos anteriores y almacena 4 nuevamente, lo cual es incorrecto. Esto se puede evitar usando mutex, que hace que otras rutinas esperen cuando una rutina ya está usando la variable.

Ahora ejecutará el programa con mutex. Aquí los 3 pasos mencionados anteriormente se ejecutan en un mutex.

package main
import "fmt"
import "time"
import "sync"
import "strconv"
import "math/rand"

//declare a mutex instance
var mu sync.Mutex

//declare count variable, which is accessed by all the routine instances
var count = 0

//copies count to temp, do some processing(increment) and store back to count
//random delay is added between reading and writing of count variable
func process(n int) {
	//loop incrementing the count by 10
	for i := 0; i < 10; i++ {
		time.Sleep(time.Duration(rand.Int31n(2)) * time.Second)
		//lock starts here
		mu.Lock()
		temp := count
		temp++
		time.Sleep(time.Duration(rand.Int31n(2)) * time.Second)
		count = temp
		//lock ends here
		mu.Unlock()
	}
	fmt.Println("Count after i="+strconv.Itoa(n)+" Count:", strconv.Itoa(count))
}

func main() {
	//loop calling the process() 3 times
	for i := 1; i < 4; i++ {
		go process(i)
	}

	//delay to wait for the routines to complete
	time.Sleep(25 * time.Second)
	fmt.Println("Final Count:", count)
}

Ahora la salida será

 Count after i=3 Count: 21
Count after i=2 Count: 28
Count after i=1 Count: 30
Final Count: 30

Aquí obtenemos el resultado esperado como resultado final. Porque las declaraciones de lectura, incremento y reescritura del recuento se ejecutan en un mutex.

Manejo de errores

Los errores son condiciones anormales como cerrar un archivo que no está abierto, abrir un archivo que no existe, etc. Las funciones generalmente devuelven errores como último valor de retorno.

El siguiente ejemplo explica más sobre el error.

package main
import "fmt"
import "os"

//function accepts a filename and tries to open it.
func fileopen(name string) {
    f, er := os.Open(name)

    //er will be nil if the file exists else it returns an error object  
    if er != nil {
        fmt.Println(er)
        return
    }else{
    	fmt.Println("file opened", f.Name())
    }
}

func main() {  
    fileopen("invalid.txt")
}

La salida será:

open /invalid.txt: no such file or directory

Aquí intentamos abrir un archivo que no existe y devolvió el error a la variable er. Si el archivo es válido, entonces el error será nulo.

Errores personalizados

Con esta función, puede crear errores personalizados. Esto se hace usando New() del paquete de error. Reescribiremos el programa anterior para utilizar errores personalizados.

Ejecute el siguiente programa

package main
import "fmt"
import "os"
import "errors"

//function accepts a filename and tries to open it.
func fileopen(name string) (string, error) {
    f, er := os.Open(name)

    //er will be nil if the file exists else it returns an error object  
    if er != nil {
        //created a new error object and returns it  
        return "", errors.New("Custom error message: File name is wrong")
    }else{
    	return f.Name(),nil
    }
}

func main() {  
    //receives custom error or nil after trying to open the file
    filename, error := fileopen("invalid.txt")
    if error != nil {
        fmt.Println(error)
    }else{
    	fmt.Println("file opened", filename)
    }  
}

La salida será:

Custom error message:File name is wrong

Aquí el área() devuelve el área de un cuadrado. Si la entrada es menor que 1, entonces area() devuelve un mensaje de error.

Leer archivos

Los archivos se utilizan para almacenar datos. Go nos permite leer datos de los archivos

Primero cree un archivo, data.txt, en su directorio actual con el siguiente contenido.

Line one
Line two
Line three

Ahora ejecute el siguiente programa para ver cómo imprime el contenido del archivo completo como salida.

package main
import "fmt"
import "io/ioutil"

func main() {  
    data, err := ioutil.ReadFile("data.txt")
    if err != nil {
        fmt.Println("File reading error", err)
        return
    }
    fmt.Println("Contents of file:", string(data))
}

Aquí los datos, err := ioutil.ReadFile(“data.txt”) lee los datos y devuelve una secuencia de bytes. Mientras se imprime, se convierte al formato de cadena.

Escribir archivos

Verás esto con un programa.

package main
import "fmt"
import "os"

func main() {  
    f, err := os.Create("file1.txt")
    if err != nil {
        fmt.Println(err)
        return
    }
    l, err := f.WriteString("Write Line one")
    if err != nil {
        fmt.Println(err)
        f.Close()
        return
    }
    fmt.Println(l, "bytes written")
    err = f.Close()
    if err != nil {
        fmt.Println(err)
        return
    }
}

Aquí se crea un archivo, test.txt. Si el archivo ya existe, su contenido se trunca. Writeline() se utiliza para escribir el contenido del archivo. Después de eso, cerró el archivo usando Close().

Hoja de trucos

En este tutorial de Go, cubrimos,

Tema Descripción Sintaxis
Tipos basicos Numérico, cadena, bool
Variables Declarar y asignar valores a variables. var tipo nombre_variable
var nombre_variable tipo = valor
var nombre_variable1, nombre_variable2 = valor1, valor2
nombre_variable := valor
Constantes Variables cuyo valor no se puede cambiar una vez asignado variable constante = valor
En bucle Ejecutar declaraciones en un bucle. para inicialización_expresión; expresión_evaluación; iteración_expresión{
// una o más declaraciones
}
Si mas Es una declaración condicional. si condición {
// declaraciones_1
Else {}
// declaraciones_2
}
cambiar Declaración condicional con múltiples casos. cambiar de expresión {
valor de caso_1:
declaraciones_1
valor de caso_2:
declaraciones_2
valor de caso_n:
declaraciones_n
por defecto:
declaraciones_predeterminadas
}
Formación Secuencia con nombre de tamaño fijo de elementos del mismo tipo nombre de matriz: = [tamaño] escriba {valor_0, valor_1,…, valor_tamaño-1}
Rebanada Porción o segmento de una matriz var nombre_porción [] tipo = nombre_matriz[inicio:fin]
Funciones Bloque de declaraciones que realiza una tarea específica. func nombre_función (tipo parámetro_1, tipo parámetro_n) tipo_retorno {
//declaraciones
}
Paquetes Se utilizan para organizar el código. Aumenta la legibilidad y reutilización del código. importar nombre_paquete
Aplazar Difiere la ejecución de una función hasta que la función que la contiene finaliza su ejecución. diferir nombre_función(lista_parámetros)
Punteros Almacena la dirección de memoria de otra variable. var nombre_variable *tipo
Estructura Tipo de datos definido por el usuario que a su vez contiene un elemento más del mismo o diferente tipo escriba nombre de estructura estructura {
variable_1 variable_1_tipo
variable_2 variable_2_tipo
variable_n variable_n_tipo
}
Métodos Un método es una función con un argumento receptor. func (variable tipo de variable) nombre_método(lista_parámetros) {
}
gorutina Una función que puede ejecutarse simultáneamente con otras funciones. ir nombre_función(lista_parámetros)
Channel Manera de que las funciones se comuniquen entre sí. Medio en el que una rutina coloca datos y al que otra rutina accede. Declarar:
ch:= hacer(chan int)
Enviar datos al canal:
variable_canal <- nombre_variable
Recibir del canal:
nombre_variable := <- variable_canal
Seleccionar Declaración de cambio que funciona en canales. Las declaraciones del caso serán una operación de canal. Cuando cualquiera de los canales está listo con datos, se ejecuta la declaración asociada con ese caso. seleccionar {
caso x := <-chan1:
fmt.Println(x)
caso y := <-chan2:
fmt.Println(y)
}
Mutex Mutex se utiliza cuando no se desea permitir que varias subrutinas accedan a un recurso al mismo tiempo. Mutex tiene 2 métodos: bloquear y desbloquear mutex.Lock()
//declaraciones
mutex.Desbloquear().
Leer archivos Lee los datos y devuelve una secuencia de bytes. Datos, err := ioutil.ReadFile(nombre de archivo)
Escribir archivo Escribe datos en un archivo. l, errar := f.WriteString(text_to_write)