Punteros y Structs en Go: Aplicaciones Prácticas y Funciones

Clase 23 de 36Curso Básico de Programación en Go

Contenido del curso

Resumen

Comprender cómo Go gestiona la memoria es fundamental para escribir código eficiente y escalable. Los punteros permiten trabajar directamente con direcciones de memoria, mientras que las funciones asociadas a structs amplían las capacidades de estas estructuras de datos. Dominar ambos conceptos abre la puerta a patrones de programación más limpios y optimizados.

¿Qué son los punteros y cómo funcionan en Go?

Cuando declaras una variable, el runtime de Go le asigna una dirección de memoria donde almacena su valor. Un puntero es simplemente una variable que guarda esa dirección en lugar del valor en sí [0:30].

Para obtener la dirección de memoria de una variable se utiliza el operador ampersand (&). Para acceder al valor almacenado en esa dirección se usa el asterisco (*), que cumple la función inversa [1:29].

go a := 50 b := &a // b almacena la dirección de memoria de a

fmt.Println(a) // 50 fmt.Println(b) // dirección de memoria, por ejemplo 0xc0000b4008 fmt.Println(*b) // 50, accede al valor mediante el puntero

¿Por qué modificar un puntero afecta a la variable original?

Una característica esencial de los punteros es que, al compartir la misma dirección de memoria, cualquier modificación a través del puntero se refleja en la variable original [2:15].

go *b = 100 fmt.Println(*b) // 100 fmt.Println(a) // 100, también cambió

Tanto a como *b apuntan a la misma ubicación en memoria. Modificar desde cualquiera de los dos puntos actualiza el mismo dato.

¿Cómo agregar funciones a un struct en Go?

Los structs en Go pueden tener métodos asociados que les agregan comportamiento. Para ilustrarlo, se define un struct que representa una PC [3:14]:

go type pc struct { ram int disco int marca string }

Después de instanciarlo, se le asocia una función llamada ping. La clave está en los paréntesis adicionales antes del nombre de la función, donde se declara el receptor (receiver), que vincula el método con el struct [4:07].

go func (myPC pc) ping() { fmt.Println(myPC.marca) fmt.Println("pong") }

miPC := pc{ram: 16, disco: 200, marca: "MSI"} miPC.ping() // Imprime: MSI y pong

El editor de código incluso autocompleta los atributos y métodos disponibles al escribir el punto después de la variable, lo que facilita el desarrollo.

¿Cómo usar punteros dentro de funciones de structs?

Aquí es donde los punteros muestran su verdadero potencial. Para modificar los valores internos de un struct desde un método, el receptor debe ser un puntero. Esto se logra agregando el asterisco antes del tipo de dato en el receptor [5:30].

go func (myPC *pc) duplicateRAM() { myPC.ram = myPC.ram * 2 }

Al ejecutar esta función varias veces, el cambio persiste en la instancia original porque se trabaja directamente con la dirección de memoria:

go fmt.Println(miPC) // {16 200 MSI} miPC.duplicateRAM() fmt.Println(miPC) // {32 200 MSI} miPC.duplicateRAM() fmt.Println(miPC) // {64 200 MSI}

Sin el asterisco en el receptor, Go crearía una copia del struct y los cambios no se reflejarían en la variable original. Esta es una de las formas más comunes de utilizar punteros en Go [6:40].

¿Por qué los punteros en Go son más sencillos que en otros lenguajes?

En muchos lenguajes de programación, el manejo de punteros implica complejidad adicional y riesgos como fugas de memoria. Go simplifica esto reduciendo toda la operación a dos símbolos [7:15]:

  • & (ampersand): obtiene la dirección de memoria.
  • * (asterisco): accede al valor almacenado en esa dirección.

Esta simplicidad no sacrifica potencia. Los punteros permiten crear funciones customizadas, transferir funcionalidad entre paquetes de forma eficiente y modificar estructuras de datos sin copias innecesarias.

Un detalle importante es que al imprimir un struct directamente con fmt.Println, el output puede resultar difícil de interpretar para alguien sin contexto del código. La solución a esto viene de la mano de los stringers, que permiten personalizar cómo se representa un struct al imprimirlo.

Si quieres poner en práctica lo aprendido, el reto propuesto consiste en organizar esta estructura de código en un paquete separado, definir qué métodos serán públicos y cuáles privados, y obtener el mismo resultado. Comparte tu solución en los comentarios.