¿Qué son los punteros en Go?

Un puntero en Go es una variable que nos permite acceder a la dirección en memoria de otra variable.

Diseño web
Lectura de 6 minutos
hace 3 años
¿Qué son los punteros en Go?

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}

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

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}

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}

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}

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}

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}

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}

Espero puedas poner en práctica esta información, y así mejorar tus habilidades en Go.

Autor del artículo

Avatar

Alejandro Rodriguez

@ajrdrgzVer perfil

Developer 🚀 - Gopher

Comentarios de los usuarios

Avatar
Hugo Orlando Gonzalez

@hugoorlandogonzalez130

Excelente blog! Muy bien explicado y detallado todo! Muchas gracias!

Avatar
Joshua Mora

@j-mora15

Me encanto el articulo, creo que esta es el area en donde tengo mas problemas para implementar, practicaré mucho, gracias

Avatar

Esta bueno el articulo, no es un tema sencillo de asimilar si es la primera vez que lo ves

Avatar

Como siempre, un maravilloso artículo de su parte Prof. Alejandro.

Hace ya un tiempo (de hecho, fue un poco después de que me empezara a gustar la Informática), platiqué con un amigo mío que estudió Ingeniería en Software (¡ahora hace videojuegos en Japón!).

Naturalmente, en la universidad le enseñaron a programar en C++ (y creo que también vio un poco de Java), y antes de que se fuese al país del sol naciente me comentó sobre los Punteros y que, al principio, se le complicó mucho comprender el funcionamiento de estos.

Yo creía que se refería a que la teoría era bastante complicada; pero, ahora veo que lo verdaderamente difícil es saber cómo y cuando implementarlos durante un algoritmo, ja, ja.

Supongo, que uno se va dando cuenta de como usarlos con la práctica constante.

Recuerda iniciar sesión para comentar este articulo