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) |