Un puntero en Go es una variable que nos permite acceder a la dirección en memoria de otra variable.
¿Para qué nos sirven los punteros?
Los punteros son muy útiles en los casos en los que queremos pasar una variable como argumento a una función para que su valor sea modificado, lo que se conoce como pasar valores por referencia a una función.
Cuando creamos una función y le pasamos una variable como argumento, lo que hace la función es hacer una copia del valor de la variable y trabajar con ese valor, por lo que la variable que pasamos como argumento no se modifica.
Veamos un ejemplo para entender esta situación.
1package main 2 3import ( 4 "fmt" 5) 6 7func Increase(v int) { 8 v++ 9} 10 11func main() { 12 var v int = 19 13 Increase(v) 14 fmt.Println("El valor de v es:", v) 15} 16
Este código produce el siguiente resultado.
El valor de v es: 19
Podemos ver entonces que el valor de la variable v
no se incrementa y sigue siendo 19
, ya que la función Increase
hace una copia de la variable v
y realiza el incremento sobre la copia y no sobre la variable.
A continuación veremos algunos conceptos que debemos entender para trabajar con punteros, y al finalizar refactorizaremos el código anterior para lograr incrementar el valor de la variable v
.
¿Cómo creamos un puntero?
Como Go es un lenguaje estáticamente tipado las variables puntero tienen que ser de un tipo de dato especifico.
Para crear un puntero utilizamos el operador \*
antes del tipo de dato que necesitamos almacenar en esa dirección de memoria.
1var p *int 2
Adicionalmente, Go nos proporciona dos formas más para crear punteros:
- Mediante la función
new()
que recibe como argumento un tipo de dato. - Y a través del del short hand de declaración de variables
:=
.
A continuación veremos un ejemplo de su uso:
1package main 2 3import ( 4 "fmt" 5) 6 7func main() { 8 v := 19 9 10 var p1 *int 11 var p2 = new(int) 12 p3 := &v 13 14 // %T nos permite imprimir el tipo de dato de la variable 15 fmt.Printf("p1: %T \n", p1) 16 fmt.Printf("p2: %T \n", p2) 17 fmt.Printf("p3: %T \n", p3) 18} 19
Este código produce el siguiente resultado.
p1: *int
p2: *int
p3: *int
Podemos ver entonces que el tipo de dato de las variables p1
, p2
y p3
es un puntero de tipo entero \*int
.
El operador de dirección &
Para obtener la referencia o dirección de memoria de una variable, debemos anteponer a la variable el operador de dirección &
.
Veamos un ejemplo:
1package main 2 3import ( 4 "fmt" 5) 6 7func main() { 8 var v int = 19 9 fmt.Println("La dirección de memoria de v es: ", &v) 10} 11
Este código produce el siguiente resultado.
La dirección de memoria de v es: 0x10414020
En este ejemplo, podemos ver que la variable v
de tipo entero tiene el valor 19
y se almacena en la dirección de memoria 0x10414020
.
El operador de desreferenciación *
Desreferenciar un puntero es obtener el valor que esta almacenado en la dirección de memoria a donde hace referencia el puntero, para hacerlo debemos anteponer el operador \*
a la variable puntero.
Veamos un ejemplo:
1package main 2 3import ( 4 "fmt" 5) 6 7func main() { 8 var v int = 19 9 var p *int 10 11 // Hacemos que el puntero p, referencie la dirección 12 // de memoria de la variable v. 13 p = &v 14 15 fmt.Printf("La variable v es: %d \n", v) 16 fmt.Printf("La dirección de memoria de v es: %v \n", &v) 17 fmt.Printf("El puntero p referencia a la dirección de memoria: %v \n", p) 18 fmt.Printf("Al desrefenciar el puntero p obtengo el valor: %d \n", *p) 19} 20
Este código produce el siguiente resultado.
La variable v es: 19
La dirección de memoria de v es: 0x10414020
El puntero p referencia a la dirección de memoria: 0x10414020
Al desrefenciar el puntero p obtengo el valor: 19
Podemos ver que el puntero p
referencia a la dirección de memoria 0x10414020
, y para obtener el valor 19
almacenado en esta dirección, hacemos uso del operador de desreferenciación \*p
.
Incrementemos la variable v
Refactoricemos la función Increase
con el uso de punteros:
1package main 2 3import ( 4 "fmt" 5) 6 7// Increase recibe un puntero de tipo entero 8func Increase(v *int) { 9 // Desreferenciamos la variable v para obtener 10 // su valor e incrementarlo en 1 11 *v++ 12} 13 14func main() { 15 var v int = 19 16 17 // La función Increase recibe un puntero 18 // utilizamos el operador de dirección & 19 // para pasar la dirección de memoria de v 20 Increase(&v) 21 22 fmt.Println("El valor de v ahora vale:", v) 23} 24
Este código produce el siguiente resultado.
El valor de v ahora vale: 20
Y, ¿Cómo utilizo punteros en estructuras?
El uso de punteros en estructuras maneja los mismos conceptos que hemos aprendido, veamos su aplicación a través del siguiente ejemplo:
Crearemos una estructura llamada User
y utilizaremos una función llamada UpdateStatus
para actualizar el estado del usuario, así que esta función recibirá un puntero de tipo User
y actualizará el campo IsActive
.
Veamos el código:
1package main 2 3import ( 4 "fmt" 5) 6 7type User struct { 8 FirstName string 9 LastName string 10 Email string 11 IsActive bool 12} 13 14func UpdateStatus(u *User) { 15 (*u).IsActive = !(*u).IsActive 16} 17 18func main() { 19 20 u := User{ 21 FirstName: "Alejandro", 22 LastName: "Rodríguez", 23 Email: "aj@ed.team", 24 IsActive: false, 25 } 26 27 fmt.Println("User antes de actualización: ", u) 28 29 UpdateStatus(&u) 30 31 fmt.Println("User después de actualización: ", u) 32} 33
Este código produce el siguiente resultado.
User antes de actualización: {Alejandro Rodríguez aj@ed.team false}
User después de actualización: {Alejandro Rodríguez aj@ed.team true}
La función UpdateStatus
recibe un puntero de tipo User
, lo que nos permite acceder a la dirección de memoria en donde se encuentra almacenada la estructura, para modificar el valor del campo IsActive
desreferenciamos el puntero con (\*u).IsActive
y le asignamos el mismo valor con el operador de negación !
, así invertimos el estado del usuario.
Cuando estamos trabajando con punteros de estructura, Go nos permite trabajar sin el operador de desreferenciación \*
, con esto logramos una sintaxis más limpia, así que la función anterior podríamos refactorizarla como:
1func UpdateStatus(u *User) { 2 u.IsActive = !u.IsActive 3} 4
Espero puedas poner en práctica esta información, y así mejorar tus habilidades en Go.