10. Go to go: apuntadores

Publicado por

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:

acceso-memoria-tabla-símbolos-apuntadores
acceso-memoria-tabla-símbolos-apuntadores

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:

ejemplo-go-golang-1

¿Cómo utilizar los apuntadores?

Los pasos para utilizar un apuntador son:

  1. Declararlo.
  2. Asignarle la dirección de otra variable.
  3. 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:

ejemplo-go-golang-2

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:

ejemplo-go-golang-3

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:

ejemplo-go-golang-4

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:

ejemplo-go-golang-5

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:

ejemplo-go-golang-6

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!

2 comments

Responder a giselaram Cancelar respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *