← Volver al listado de tecnologías

Control de Flujo

Por: Artiko
gocontrol-flujoifforswitchdefer

Control de Flujo

if / else

En Go, las condiciones if no llevan parentesis pero las llaves son obligatorias:

package main

import "fmt"

func main() {
	edad := 25

	if edad >= 18 {
		fmt.Println("Mayor de edad")
	} else if edad >= 13 {
		fmt.Println("Adolescente")
	} else {
		fmt.Println("Menor de edad")
	}
}

if con init statement

Go permite declarar una variable dentro del if. Su scope se limita al bloque if/else:

package main

import (
	"fmt"
	"os"
)

func main() {
	// La variable err solo existe dentro del if/else
	if err := os.Setenv("APP_MODE", "dev"); err != nil {
		fmt.Println("Error:", err)
	} else {
		fmt.Println("Variable establecida")
	}

	// err no existe aqui
}

Este patron es idiomatico en Go y se usa constantemente para manejo de errores:

package main

import (
	"fmt"
	"strconv"
)

func main() {
	if valor, err := strconv.Atoi("42"); err != nil {
		fmt.Println("Error de conversion:", err)
	} else {
		fmt.Println("Valor:", valor)
	}
}

for: el unico loop en Go

Go tiene un solo tipo de loop: for. No existe while ni do-while.

for clasico (estilo C)

package main

import "fmt"

func main() {
	for i := 0; i < 5; i++ {
		fmt.Print(i, " ")
	}
	// 0 1 2 3 4
}

for como while

package main

import "fmt"

func main() {
	contador := 0
	for contador < 5 {
		fmt.Print(contador, " ")
		contador++
	}
	// 0 1 2 3 4
}

for infinito

package main

import "fmt"

func main() {
	i := 0
	for {
		if i >= 3 {
			break
		}
		fmt.Print(i, " ")
		i++
	}
	// 0 1 2
}

for range

range itera sobre colecciones (slices, maps, strings, channels):

package main

import "fmt"

func main() {
	// Range sobre slice
	frutas := []string{"manzana", "pera", "uva"}
	for indice, fruta := range frutas {
		fmt.Printf("[%d] %s\n", indice, fruta)
	}

	// Ignorar indice con _
	for _, fruta := range frutas {
		fmt.Println(fruta)
	}

	// Solo indices
	for i := range frutas {
		fmt.Println(i)
	}

	// Range sobre string (itera runes, no bytes)
	for i, r := range "Hola" {
		fmt.Printf("byte %d: %c (U+%04X)\n", i, r, r)
	}
}

Tabla: variantes del for

FormaEquivalente en otros lenguajesEjemplo
for i := 0; i < n; i++for clasicoIterar con contador
for condicionwhileRepetir mientras se cumpla
forwhile trueLoop infinito
for i, v := range colfor...of / foreachIterar colecciones

switch

El switch de Go no necesita break: cada caso termina automaticamente. Para continuar al siguiente caso se usa fallthrough.

switch basico

package main

import "fmt"

func main() {
	dia := "martes"

	switch dia {
	case "lunes":
		fmt.Println("Inicio de semana")
	case "martes", "miercoles", "jueves":
		fmt.Println("Entre semana")
	case "viernes":
		fmt.Println("Casi fin de semana")
	case "sabado", "domingo":
		fmt.Println("Fin de semana")
	default:
		fmt.Println("Dia invalido")
	}
	// Entre semana
}

switch sin expresion (reemplaza if/else if)

package main

import "fmt"

func clasificarNota(nota int) string {
	switch {
	case nota >= 90:
		return "Excelente"
	case nota >= 80:
		return "Bueno"
	case nota >= 70:
		return "Regular"
	default:
		return "Insuficiente"
	}
}

func main() {
	fmt.Println(clasificarNota(85)) // Bueno
	fmt.Println(clasificarNota(95)) // Excelente
}

switch con init statement

package main

import (
	"fmt"
	"runtime"
)

func main() {
	switch os := runtime.GOOS; os {
	case "linux":
		fmt.Println("Estas en Linux")
	case "darwin":
		fmt.Println("Estas en macOS")
	case "windows":
		fmt.Println("Estas en Windows")
	default:
		fmt.Println("SO:", os)
	}
}

fallthrough

package main

import "fmt"

func main() {
	nivel := 1

	switch nivel {
	case 1:
		fmt.Println("Acceso basico")
		fallthrough
	case 2:
		fmt.Println("Acceso intermedio")
		fallthrough
	case 3:
		fmt.Println("Acceso completo")
	}
	// Acceso basico
	// Acceso intermedio
	// Acceso completo
}

type switch

Permite verificar el tipo concreto de una interface:

package main

import "fmt"

func describir(i interface{}) string {
	switch v := i.(type) {
	case int:
		return fmt.Sprintf("entero: %d", v)
	case string:
		return fmt.Sprintf("string: %q", v)
	case bool:
		return fmt.Sprintf("booleano: %t", v)
	default:
		return fmt.Sprintf("tipo desconocido: %T", v)
	}
}

func main() {
	fmt.Println(describir(42))      // entero: 42
	fmt.Println(describir("hola"))  // string: "hola"
	fmt.Println(describir(true))    // booleano: true
	fmt.Println(describir(3.14))    // tipo desconocido: float64
}

defer

defer pospone la ejecucion de una funcion hasta que la funcion contenedora retorna. Se ejecutan en orden LIFO (ultimo en entrar, primero en salir):

package main

import "fmt"

func main() {
	fmt.Println("inicio")

	defer fmt.Println("primero en defer")
	defer fmt.Println("segundo en defer")
	defer fmt.Println("tercero en defer")

	fmt.Println("fin")
}
// inicio
// fin
// tercero en defer
// segundo en defer
// primero en defer

Uso practico: cerrar recursos

package main

import (
	"fmt"
	"os"
)

func leerArchivo(ruta string) error {
	archivo, err := os.Open(ruta)
	if err != nil {
		return err
	}
	defer archivo.Close() // Se cierra al salir de la funcion

	buf := make([]byte, 100)
	n, err := archivo.Read(buf)
	if err != nil {
		return err
	}

	fmt.Printf("Leidos %d bytes: %s\n", n, buf[:n])
	return nil
}

func main() {
	if err := leerArchivo("/etc/hostname"); err != nil {
		fmt.Println("Error:", err)
	}
}

defer evalua argumentos inmediatamente

package main

import "fmt"

func main() {
	x := 10
	defer fmt.Println("defer x:", x) // Captura x=10 aqui
	x = 20
	fmt.Println("x:", x)
}
// x: 20
// defer x: 10

Labels con break y continue

Los labels permiten controlar loops anidados:

package main

import "fmt"

func main() {
	// break con label sale del loop externo
externo:
	for i := 0; i < 3; i++ {
		for j := 0; j < 3; j++ {
			if i == 1 && j == 1 {
				break externo
			}
			fmt.Printf("(%d,%d) ", i, j)
		}
	}
	fmt.Println()
	// (0,0) (0,1) (0,2) (1,0)

	// continue con label salta a la siguiente iteracion del loop externo
	fmt.Println("---")
filas:
	for i := 0; i < 3; i++ {
		for j := 0; j < 3; j++ {
			if j == 1 {
				continue filas
			}
			fmt.Printf("(%d,%d) ", i, j)
		}
	}
	// (0,0) (1,0) (2,0)
}

goto

Go soporta goto pero su uso es muy desaconsejado. Solo es util en codigo generado o para salir de multiples niveles de anidacion sin labels:

package main

import "fmt"

func main() {
	i := 0
loop:
	if i < 3 {
		fmt.Println(i)
		i++
		goto loop
	}
	// 0
	// 1
	// 2
}

En la practica, prefiere for con break/continue y labels.

Ejercicios

  1. Escribe una funcion que determine si un numero es primo usando for
  2. Implementa FizzBuzz con switch sin expresion
  3. Crea una funcion que use defer para medir el tiempo de ejecucion

Resumen