woops


Si completó el Tour de Go, recordará que Go posee punteros, y que un puntero contiene la dirección de memoria en la que está contenido un valor. 

Un puntero es simplemente una variable que contiene la dirección de memoria de otra variable

Si declaramos en un programa Go una variable de tipo int y otra variable como un puntero a int se vería así:


1
2
3
4
5
6
package main
   
func main() {
    var i int  = 42
    var p *int = &i
}

Donde en la variable i almacenamos el número 42, mientras que en la variable p almacenamos la dirección de memoría donde está alojada la variable i


diagrama 1

¿Se fijó en el ampersand & antes de la i en la definición de la variable puntero? Su nombre es operador de referencia y es la forma de obtener el puntero a un elemento en Go.

Seguramente también observó el asterisco * en la declaración del tipo de la variable puntero p. Es el operador de dereferencia o puntero, indica que la variable contiene un puntero, y también sirve para obtener el dato almacenado en la dirección de memoria a la que el puntero apunta.


  
    package main
    
    import "fmt"
    
    func main() {
        var i int  = 42
        var p *int = &i
        
        fmt.Println(*p) // prints 42
        
    }
    
  
playground

new

Para nuestra conveniencia, existe otra forma de obtener un puntero. la función new, la cual toma un tipo como argumento y devuelve un puntero a él. La expresión new(T) crea una variable sin nombre de tipo T y devuelve su dirección, la cual, como indicamos, es un puntero a T, o, diciéndolo de otra forma, un valor de tipo *T



package main

import "fmt"

func main() {
    p := new(int) // p, de tipo *int, apunta a una variable anónima de tipo int
    fmt.Println(*p) // imprime 0
    *p = 2 // Asigna el valor 2 a la variable anónima
    fmt.Println(*p) // imprime 2
}


playground

Las variables anónimas creadas con new no son distintas a las variables comunes y silvestres de toda la vida, con la diferencia de que no es necesario darles un nombre


¿Y para qué?

Todo esto está muy bien, pero, ¿De qué me sirve? ¿Por qué no usar las variables como lo hemos hecho toda la vida si es más simple?

Los punteros nos ayudan entre otras cosas a acceder a elementos a los cuales normalmente no tendríamos accesos. Imaginemos que quiere escribir una función que intercambie el contenido de dos variables enteras. La primera aproximación podría ser algo como:


  
package main

import "fmt"

func DaSwap(i1 int, i2 int) {
	i1, i2 = i2, i1
}

func main() {
	var a int = 42
	var b int = 16

	DaSwap(a, b)

	fmt.Println(a, " ", b)
}
  

playground

¿Que imprime el programa anterior? Si esperaba que imprimiera 16 42 lamento decirle que espere sentado. Como el intercambio de las variables se hace en la función DaSwap, este se hizo sobre copias de los valores de las variables que le fueron pasadas como argumentos, por lo que estas no fueron afectadas. Si usted es un veterano de las trincheras, entonces de seguro ya está pensando en la frase paso de variables por valor.

Si quisiéramos ver que el intercambio se realice correctamente, nos vemos obligados a usar punteros:


  

package main

import "fmt"

func DaSwap(i1 *int, i2 *int) {
	*i1, *i2 = *i2, *i1
}

func main() {
	var a int = 42
	var b int = 16

	DaSwap(&a, &b)

	fmt.Println(a, " ", b)
}
  

playground

Si de verdad completó el Tour de Go, usted podría debatirme que no necesita punteros, pues podría escribir la función de tal manera que devuelva los valores como una tupla en orden inverso al pasado como argumento, ¡Y yo me vería forzado a darle la razón!

  
package main

import "fmt"

func DaSwap(i1 int, i2 int) (int, int) {
	return i2, i1
}

func main() {
	var a int = 42
	var b int = 16

	a, b = DaSwap(a, b)

	fmt.Println(a, " ", b)
}

  

playground

Entonces, otra vez ¿De que me sirven los punteros? En muchos lenguajes de programación los punteros se usan, a veces sin que nos demos cuenta, por ejemplo para implementar listas y tipos de datos especiales que requieren aumento o disminución dinámica de capacidad; y para manipular objetos y arreglos. También permiten pasar funciones como argumento a otras funciones, pues estando definido un bloque de código en una posición de memoria (o en una ubicación del stack), parece lógico poder acceder a él a través de una referencia a dicha posición de memoria.



  
# Pseudo código

function HolaSoyUnCallback() {
  # Realizo alguna acción
}

func f1(callback) {
  # Realizo alguna acción en f1
  callback()
  # Mas acciones en f1
} 
 
  


Específicamente en Go, entre otras cosas, los punteros nos dan acceso a una característica muy poderosa del lenguaje, sobre la cual se pueden construir muchas cosas muy bonitas; los structs



Resumiendo

  • Un puntero se llama así porqué sirve para apuntar
  • ¿Donde apunta? Apunta a una dirección de memoria