Ha llegado el momento de tratar una de las herramientas más útiles de lenguajes como C y Go: los apuntadores. Un apuntador es una variable que, como su nombre lo indica, apunta a algo, es decir, es una variable que almacena la dirección de memoria de otra variable.
Tal vez sea lógico, pero todas las variables en ejecución se encuentran almacenadas en un espacio de memoria y, a su vez, cada espacio de memoria tiene asignada una dirección; por ende, cada variable tiene asignada una dirección en memoria.
Cuando asignamos un identificador a una variable, tras bambalinas (en el compilador) se crea una entrada en la tabla de símbolos que indica en qué dirección se encuentra su valor. Cualquier consulta a una variable por medio de un identificador se traduce en una consulta al espacio de memoria en el que esta se encuentra. Esto es:

Analizando un poco la imagen anterior, podemos identificar que la tabla de símbolos tiene una entrada para nuestra variable llamada mi_variable en donde se especifica en qué espacio de memoria podemos localizarla. En este caso mi_variable tiene almacenado un valor imaginario VALOR en la dirección de memoria 100000. Este ejemplo es arbitrario, ni las tablas de símbolos son exactamente así, ni las direcciones de memoria se dividen de ese modo (VALOR tampoco es un dato válido).
Apuntadores en Go
Para declarar un apuntador en Go se utiliza la siguiente sintaxis:
TipoApuntador = "*" TipoBase TipoBase = TipoDato
En la siguiente línea de código se declara un apuntador a un entero:
var apunta_entero *int
¿Cómo acceder a la dirección de una variable?
En la tabla de símbolos todas las variables tienen asignada la dirección de memoria donde se localizan, la cual se puede acceder si le colocamos el operador ampersand (&) al inicio de ellas. En el siguiente ejemplo puedes observar dicho operador siendo utilizado para imprimir la dirección de una variable:
package main import "fmt" func main(){ var mi_var = 3 fmt.Printf("Contenido de variable 'a': %d \n", mi_var) fmt.Printf("Dirección de variable 'a' (decimal): %d \n", &mi_var) fmt.Printf("Dirección de variable 'a' (hexadecimal): %x \n", &mi_var) }
Salida:
¿Cómo utilizar los apuntadores?
Los pasos para utilizar un apuntador son:
- Declararlo.
- Asignarle la dirección de otra variable.
- Acceder a la variable desde el apuntador.
Para completar el paso 3 es necesario utilizar el operador asterisco (*) para consultar el valor de la variable ubicada en la dirección que almacena el apuntador. En el siguiente ejemplo podemos observar esto último:
package main import "fmt" func main(){ var mi_var = 100 var dir_var *int dir_var = &mi_var fmt.Printf("Valor de la variable 'mi_var': %d \n", mi_var) fmt.Printf("Dirección almacenada en 'dir_var': %x \n", dir_var) fmt.Printf("Valor de la variable que apunta 'dir_var': %d \n", *dir_var) fmt.Printf("Dirección que ocupa el apuntador 'dir_var' en memoria: %x \n", &dir_var) }
Salida:
Como se puede observar en la última línea de la salida, los apuntadores también ocupan un lugar en memoria.
Apuntadores a nil
Si no se asigna una dirección a la cual apuntar, por defecto los apuntadores almacenan la dirección de memoria 0 o nil (nula). Dicha constante tiene un significado particular en el lenguaje: cuando se apunta a nil, el compilador lo interpreta como si se intentara acceder a una dirección de memoria inválida. Esta característica puede ser utilizada en conjunto con algunas sentencias como las condicionales:
package main import "fmt" func main(){ var mi_var = 100 var apunt_1 *int var apunt_2 *int //Asigna una dirección válida apunt_2 = &mi_var if(apunt_1 == nil){ fmt.Println("El apuntador 'apunt_1' apunta a nil") }else{ fmt.Println("El apuntador 'apunt_1' NO apunta a nil") } if(apunt_2 == nil){ fmt.Println("El apuntador 'apunt_2' apunta a nil") }else{ fmt.Println("El apuntador 'apunt_2' NO apunta a nil") } }
Salida:
Arreglos de apuntadores
También es posible crear arreglos de apuntadores:
package main import "fmt" func main(){ mis_num:= []int{5, 10, 33, 42, 59} var apuntadores[5]*int //Asigna las direcciones de los elementos a los apuntadores for i:=0; i < 5 ; i++ { apuntadores[i] = &mis_num[i] fmt.Printf("-----------------------------\n") //Se accede al arreglo 'mis_num' por medio de apuntadores fmt.Printf("Contenido de mis_num[%d]: %d \n", i, *apuntadores[i]) fmt.Printf("Dirección de mis_num[%d]: %x \n", i, apuntadores[i]) } }
Salida:
Apuntadores a apuntadores
Por si fuera poco, también es posible crear apuntadores a apuntadores:
package main import "fmt" func main(){ var val int = 100 var ap_val *int var ap_ap_val **int //Recupera la dirección de la variable ap_val = &val //Recupera la dirección del apuntador ap_ap_val = &ap_val fmt.Printf("Variable original: %d \n", val) fmt.Printf("Variable desde apuntador: %d \n", *ap_val) fmt.Printf("Variable desde apuntador de apuntador: %d \n", **ap_ap_val) }
Salida:
Funciones con paso de parámetros por referencia
Como complemento a la entrada anterior en la que tratamos el tema de las funciones: es posible utilizar apuntadores con funciones para acceder y modificar las variables originales que se hayan pasado como argumentos, a esto se le conoce como paso de parámetros por referencia. En el siguiente ejemplo podemos observar la diferencia entre un llamado por referencia y uno por valor (cuando no se utilizan apuntadores):
package main import "fmt" func main(){ var mi_var int = 10 fmt.Println("Variable antes de llamados a funciones: ", mi_var) //Llamado por valor sumaPorValor(mi_var) fmt.Println("Variable después de llamado por valor: ", mi_var) //Llamado por referencia sumaPorReferencia(&mi_var) fmt.Println("Variable después de llamado por referencia: ", mi_var) } func sumaPorValor(a int){ a = a + 10 } func sumaPorReferencia(a *int){ *a = *a + 10 }
Salida:
Finalizando…
Espero que esta entrada te haya servido, no dejes de repasar los apuntadores, te serán de mucha utilidad. Para cualquier duda o aclaración puedes dejar un comentario y te responderé a la brevedad. Hasta la próxima, see ya!
Hola la verdad conozco muy poco del lenguaje, tengo que hacer un compilador e interprete en este lenguaje, me podrías asesorar?
Hola, giselaram, claro, puedes comunicarte conmigo a kyoyajesus@gmail.com