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.
Capítulo 4
APUNTADORES
La memoria de un ordenador podría abstraerse, como si fuera un conjunto de cajones colocados en una gigantesca estantería. Cada cajón tiene un número identificativo único, que permite al ordenador referirse a él a la hora de ir a buscar o guardar datos. Dicho número identificativo se conoce como “dirección de memoria”.
Los apuntadores (o punteros) son variables que no guardan valores útiles como tales, sino las direcciones de memoria donde se encuentran dichos valores. Su nombre hace referencia al hecho de que no guardan una variable sino que “apuntan” a su dirección.
En Go, los apuntadores tienen un tipo asociado, es decir, solo pueden apuntar a variables de un tipo en concreto: un puntero a int solo podrá guardar la dirección de memoria de una variable del tipo int, un puntero a bool solo podrá guardar la dirección de memoria de un bool, etc.
4.1 DEFINICIÓN DE UN APUNTADOR
Un apuntador se define como una variable, añadiendo un asterisco delante del tipo de datos al que este apuntador puede apuntar:
var pi *int // apuntador a ints var pb *bool // apuntador a bools
4.2 LA REFERENCIA A nil
Los apuntadores definidos en la sección anterior no han sido inicializados a ningún valor. Apuntan al valor nil, que podría interpretarse como “a ninguna parte”.
El valor nil se puede utilizar tanto para reiniciar el valor de un apuntador “a nada” como para comprobar si un apuntador apunta a algún lugar válido:
Cuando un apuntador hace referencia a la dirección nil, este no se puede utilizar para leer o modificar valores apuntados, ya que dicho valor apuntado no existe. Si tratáramos de hacerlo, el sistema de memoria segura de Go abortaría la ejecución del programa, mostrando un error similar al siguiente:
panic: runtime error: invalid memory address or nil pointer dere- ference [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x49169f]
4.3 APUNTANDO HACIA UNA VARIABLE
El operador ampersand (&) delante de una variable retorna la dirección de memoria de esta. Este valor se puede asignar directamente a un puntero:
i := 10 var p *int p = &i
En el ejemplo anterior, el apuntador p apuntará a la variable i (Figura 4.1). El código anterior se puede abreviar de la siguiente manera:
i := 10 p := &i
Ya que &i retorna un valor del tipo *int, p será declarado como *int (apuntador a int) y desde el principio apuntará al int i.
Figura 4.1 Apuntador “p” apuntando a la dirección de memoria de “i” (esquema visual).
4.4 LEYENDO O MODIFICANDO EL VALOR APUNTADO
A través de un apuntador, se puede leer o modificar el valor de la variable apuntada. El operador asterisco * delante del nombre del apuntador nos permitirá acceder al valor de la variable apuntada, tanto para su lectura como para su modificación:
i := 10 p := &i a := *p *p = 21
En el ejemplo anterior, donde el apuntador p apunta a la variable i, operando a través de este apuntador se está leyendo y modificando el valor de la variable i. La tercera línea (a := *p) tendría un resultado equivalente a a := i, y la cuarta línea (*p = 21) tendría un resultado equivalente a i = 21. La