Читать книгу Programación en Go - Mario Macías Lloret - Страница 23
ОглавлениеCapítulo 3
CONTROL DE FLUJO
Los programas de ejemplo mostrados hasta ahora seguían un “flujo secuencial”: las operaciones se ejecutan una a una, según su orden descendiente de escritura.
Como en los demás lenguajes de programación, Go permite alterar el flujo secuencial mediante los bloques de control de flujo, que se agrupan en dos tipos:
• Condicionales. Permiten que un bloque de instrucciones se ejecute o no, dependiendo de si se cumple una condición en el programa.
• Iterativos. Permiten que un bloque de instrucciones se ejecute repetidamente mientras se dé una condición.
Por “condición” entendemos cualquier expresión booleana que retorne true o false, como las mostradas en el capítulo anterior.
3.1 BLOQUES CONDICIONALES
3.1.1 if
El bloque if permite agrupar un conjunto de instrucciones que se ejecutarán si —y solo si— su condición asociada es true. Su estructura es:
Por ejemplo, el siguiente programa genera un número aleatorio, e informa de que el número generado es par. Dicho mensaje de información solo se mostrará si el número es realmente par:
Nótese la importación del paquete math/rand y el uso del comando rand. Int() para la generación de números aleatorios.
En el programa anterior, el mensaje dentro del bloque if se ejecutará solo si el número aleatorio es divisible por dos (el resto de su división entera es 0).
Si no se quiere considerar el valor 0 como número par, la condición del bloque if podría completarse mediante el uso de los operadores lógicos vistos en el capítulo anterior:
if valor != 0 && valor%2 == 0
3.1.2 if ... else
Un bloque if puede ser inmediatamente continuado por un bloque else, que ejecutaría el bloque de instrucciones asociado si —y solo si— la condición del if es false. Basándonos en el anterior ejemplo:
Por brevedad, se ha omitido la definición del paquete y los import.
En el programa anterior, siempre se ejecutará uno de los dos bloques en exclusiva: o el código dentro del bloque if (en caso de que valor sea divisible por dos), o el código dentro del bloque else (en caso de que valor no sea divisible por dos); pero nunca se ejecutarán los dos bloques a la vez.
En los ejemplos anteriores, la variable valor existe y es accesible durante toda la vida de la función main, incluso si solo se necesita en el contexto de los bloques if y else. Es una buena práctica restringir el ciclo de vida de una variable lo máximo que se pueda.
Go permite definir una variable dentro de un if, limitando su ciclo de vida a la evaluación de la condición y las instrucciones de los bloques if y else:
Aplicándolo al ejemplo anterior:
La diferencia es que, mientras anteriormente se podía usar la variable valor en cualquier punto de la función main(), con esta nueva forma la variable valor deja de existir después del bloque else (por ejemplo, no podríamos imprimir su valor en el mensaje de despedida).
CONSEJO: Limitar al máximo la vida de una variable nos ahorrará muchos errores en nuestros programas, algunos de ellos difíciles de detectar.
3.1.3 switch - case
Cuando una variable puede tomar múltiples valores de una enumeración concreta, y se debe realizar una acción distinta para cada uno de estos valores, la forma más concisa de expresarlo es mediante un bloque switch/case:
La orden switch también puede incluir una inicialización de variable, similar a la forma if anteriormente vista:
switch <inicialización variable> ; <expresión a evaluar>
El siguiente ejemplo hace uso del paquete runtime y sus variables globales runtime.GOARCH y runtime.GOOS para extraer información de la arquitectura y del sistema operativo en el que el programa se ejecuta. Como en muchos otros ejemplos a partir de ahora, se omite la definición del paquete, los import y la definición de la función main().
El código anterior mostraría algo similar al siguiente texto (dependiendo del ordenador en el que lo ejecutara):
La arquitectura de su procesador es x86 de 64 bits Su sistema operativo es windows
El primer bloque switch del ejemplo anterior evaluará el valor de la variable arch y mostrará un mensaje distinto dependiendo de si esta variable contiene "386" o "amd64" (valores definidos en las cabeceras de sus respectivos bloques case). El bloque default: se ejecutará si el valor de la variable arch no coincide con ninguno de los valores definidos tras cada case (por ejemplo, si su procesador fuera un ARM, comúnmente usado en tablets y teléfonos inteligentes).
La variable arch, definida en la función main, existirá y será visible desde cualquier punto de la función main (a partir de la declaración de la variable). Sin embargo, observe que el segundo bloque switch define la variable os en la misma línea en la que se comprueba (separado por punto y coma, de manera análoga al bloque if de la sección anterior). En este caso, la existencia y visibilidad de la variable os se limitará al bloque switch.
Si el lector está familiarizado con otros lenguajes de programación, como Java o C, habrá notado la ausencia de la palabra break al final de cada bloque case. En aras de la brevedad, Go la omite. Cuando finaliza el código dentro de un bloque case, el flujo del programa continúa fuera del switch.
Si por algún motivo fuera necesario que, tras acabar el código de un case, el flujo continuara por el siguiente bloque case, se debe usar el comando fallthrough. Ejemplo:
En el ejemplo anterior:
• Si letra == 'a' se mostrará Primera del abecedario.
• Si letra == 'A' se mostrará tanto Mayúscula como primera del abecedario.
Se pueden especificar diferentes valores, separados por comas, detrás de un case. Go ejecutará el código para ese caso si la expresión a comprobar coincide con alguno de esos valores:
La orden switch permite un análisis de patrones más complejo (hasta cierto punto, equivalente a varios if-else encadenados). Por ejemplo:
Observe que, en este caso, la orden switch no especifica ninguna variable, ya que la comprobación de esta se hace en el case.
3.2 ÓRDENES ITERATIVAS (BUCLES for)
Un bucle es un conjunto de órdenes que se repite. El bucle más sencillo que Go permite especificar es:
El bucle anterior es un “bucle infinito”. Si un programa llega a ese punto, el programa jamás continuará más allá del bucle for, a no ser que el bucle se rompa con la instrucción break:
El ejemplo anterior repite un bucle en el que el programa pide un carácter al usuario, y se repite hasta que el usuario introduce el carácter 'S' o 's' , momento en el que el bucle se rompe mediante la instrucción break.
La instrucción continue rompe el flujo de cada iteración de un for. En este caso, el flujo del programa salta de nuevo al inicio del bucle, sin ejecutar las instrucciones restantes de la iteración en que se invoca.
El ejemplo anterior se comportaría de la siguiente manera:
Salir? (s/n): a carácter no reconocido Salir? (s/n): n Salir? (s/n): s adiós!
De acuerdo con el código del ejemplo:
• Si el usuario introduce 'N' o 'n', se ejecutará la instrucción continue, por lo que la ejecución volverá al inicio del for.
• Si el usuario introduce 'S' o 's', se ejecutará la instrucción break, por lo que el for finaliza y la ejecución continúa por el mensaje adiós!
• Solo en el caso de que el usuario introdujera cualquier otro carácter, se mostraría el mensaje carácter no reconocido.
A pesar de lo que promulgan algunas escuelas de programación de corte académico, las órdenes break y continue son totalmente válidas y aceptadas en las convenciones sobre estilo de Go. Sin embargo, a menudo resulta más limpia y útil la forma condicional de for:
Por ejemplo:
El programa anterior repetirá el código dentro del bucle mientras el usuario no introduzca 'S' ni 's'.
El lector que esté familiarizado con otros lenguajes de programación, se habrá dado cuenta de que esta forma de for “condicional” suele llamarse while en otros lenguajes de programación. Los diseñadores de Go decidieron que el código resultaría mucho más simple y legible si cualquier bucle usaba la misma orden for.
Hay una tercera forma de describir un for; la más similar al bucle for de los demás lenguajes:
Este tipo de for:
1. Ejecuta la orden de inicio una sola vez, antes de ejecutar la primera iteración.
2. Antes de cada iteración, se comprobará si la condición es cierta; en caso de que no lo sea, el bucle acabará.
3. Después de cada iteración, antes de volver a comprobar si la condición sigue siendo cierta, se ejecutará la orden de actualización.
Por ejemplo, el siguiente bucle for mostrará una cuenta del 1 al 10:
for i := 1; i <= 10; i++ { fmt.Println(i) }
4. La variable i se inicia al valor 1 (observe que el alcance de esta se limita al bucle).
5. Se muestra el valor de la variable i.
6. Se incrementa la variable i.
7. Se comprueba si el valor de i es menor o igual que 10. Si es el caso, se vuelve al paso 2. Si no es el caso, se sale del bucle for.
En el capítulo 6, sobre estructuras de datos lineales, se mostrará otro uso del bucle for, comúnmente conocido como for-each, que facilita el recorrido por los valores de diferentes colecciones de datos.
3.3 CONTEXTO Y OCULTACIÓN DE VARIABLES
En cada instrucción condicional o cada bucle, cada vez que se define un nuevo contexto entre un par de llaves { y } es posible definir nuevas variables cuya vida y visibilidad se limitarán a ese contexto:
Si, en el ejemplo anterior, intentara mostrar el contenido de la variable i desde fuera del bloque if donde ha sido definida, el compilador le mostraría un mensaje de error.
Una característica de Go, que puede ser útil pero también puede ser una fuente de problemas, es el poder definir nuevas variables con nombres que ya existen en los contextos más globales. A pesar de tener el mismo nombre, serán variables distintas. Este concepto es conocido como “ocultación” o, en inglés, shadowing.
Ejemplo:
En un despiste, se podría pensar que la salida estándar de este programa sería a = 2, b = 2. Sin embargo, es a = 0, b = 2, ya que la variable a ha sido redeclarada y ocultada (eclipsada) dentro de la condición (observe el sutil detalle del operador de declaración := usado con a, frente al de asignación = usado con b). Por tanto, la variable a mostrada en fmt.Printf no ha sido modificada en ningún momento.