← Volver al listado de tecnologías

Punteros

Por: Artiko
gopunterosmemoria

Punteros en Go

Un puntero almacena la direccion de memoria de un valor. Go usa punteros de forma segura: no permite aritmetica de punteros, lo que elimina una clase entera de bugs comunes en C/C++.

Operadores & y *

El operador & obtiene la direccion de una variable. El operador * accede al valor en esa direccion (dereferenciacion):

package main

import "fmt"

func main() {
    x := 42
    p := &x // p es *int, apunta a x

    fmt.Println("Valor de x:", x)
    fmt.Println("Direccion de x:", p)
    fmt.Println("Valor apuntado por p:", *p)

    *p = 100 // modificar x a traves del puntero
    fmt.Println("Nuevo valor de x:", x) // 100
}

Declaracion de Punteros

SintaxisDescripcion
var p *intPuntero a int, zero value es nil
p := &variablePuntero desde variable existente
p := new(int)Asigna memoria, retorna puntero
p := &MiStruct{}Puntero a struct literal

Zero Value: nil

El zero value de cualquier puntero es nil. Dereferenciar un puntero nil causa un panic en tiempo de ejecucion:

package main

import "fmt"

func main() {
    var p *int
    fmt.Println(p == nil) // true

    // *p = 10 // PANIC: runtime error: invalid memory address

    // Siempre verificar antes de dereferenciar
    if p != nil {
        fmt.Println(*p)
    } else {
        fmt.Println("Puntero es nil, no se puede dereferenciar")
    }
}

Punteros a Structs

Go simplifica el acceso a campos de structs a traves de punteros. No necesitas escribir (*p).Campo, puedes usar directamente p.Campo:

package main

import "fmt"

type Persona struct {
    Nombre string
    Edad   int
}

func main() {
    p := &Persona{Nombre: "Ana", Edad: 30}

    // Go permite acceso directo sin (*p).Nombre
    fmt.Println(p.Nombre) // "Ana"
    p.Edad = 31
    fmt.Println(p.Edad) // 31

    // Esto tambien funciona pero es innecesario
    fmt.Println((*p).Nombre) // "Ana"
}

new() vs &T{}

Ambos crean un puntero a un valor nuevo. La diferencia es que &T{} permite inicializar campos:

package main

import "fmt"

type Config struct {
    Host string
    Port int
}

func main() {
    // new() asigna memoria con zero values
    c1 := new(Config)
    fmt.Println(c1.Host) // "" (zero value de string)
    fmt.Println(c1.Port) // 0  (zero value de int)

    // &T{} permite inicializar
    c2 := &Config{Host: "localhost", Port: 8080}
    fmt.Println(c2.Host) // "localhost"
    fmt.Println(c2.Port) // 8080
}
FormaZero valuesInicializacionUso comun
new(T)SiNoTipos simples
&T{}Si (campos omitidos)SiStructs con valores iniciales

Punteros como Parametros de Funcion

Pasar un puntero permite que la funcion modifique el valor original:

package main

import "fmt"

// Recibe copia, no modifica el original
func duplicarValor(n int) {
    n *= 2
}

// Recibe puntero, modifica el original
func duplicarPuntero(n *int) {
    *n *= 2
}

func main() {
    x := 10

    duplicarValor(x)
    fmt.Println("Despues de duplicarValor:", x) // 10 (sin cambio)

    duplicarPuntero(&x)
    fmt.Println("Despues de duplicarPuntero:", x) // 20
}

Retornar Punteros desde Funciones

En Go es seguro retornar punteros a variables locales. El compilador mueve automaticamente la variable al heap mediante escape analysis:

package main

import "fmt"

func crearUsuario(nombre string) *Persona {
    // p es local pero Go la mueve al heap automaticamente
    p := Persona{Nombre: nombre, Edad: 25}
    return &p
}

type Persona struct {
    Nombre string
    Edad   int
}

func main() {
    u := crearUsuario("Carlos")
    fmt.Println(u.Nombre) // "Carlos" - perfectamente valido
}

En C esto seria un bug (dangling pointer). En Go el garbage collector se encarga de liberar la memoria cuando ya no se necesita.

Sin Aritmetica de Punteros

Go no permite operaciones aritmeticas sobre punteros. Esto es una decision de diseno para garantizar seguridad de memoria:

// En C puedes hacer:
// int *p = &arr[0];
// p++;  // avanza al siguiente elemento

// En Go esto NO compila:
// p := &x
// p++ // error: invalid operation

El paquete unsafe existe para casos excepcionales (interop con C), pero su uso se desaconseja en codigo normal.

Cuando Usar Punteros

Usa punteros cuando necesites:

  1. Mutabilidad: modificar un valor desde otra funcion
  2. Evitar copias grandes: structs con muchos campos o datos grandes
  3. Semantica de referencia: compartir estado entre funciones
  4. Valores opcionales: nil indica ausencia de valor

No uses punteros cuando:

package main

import "fmt"

type Punto struct {
    X, Y float64
}

// Struct pequeno: pasar por valor es eficiente
func distanciaAlOrigen(p Punto) float64 {
    return p.X*p.X + p.Y*p.Y
}

type MatrizGrande struct {
    Datos [1000][1000]float64
}

// Struct grande: usar puntero evita copiar ~8MB
func procesarMatriz(m *MatrizGrande) {
    m.Datos[0][0] = 1.0
}

func main() {
    p := Punto{X: 3, Y: 4}
    fmt.Println("Distancia:", distanciaAlOrigen(p))

    m := &MatrizGrande{}
    procesarMatriz(m)
    fmt.Println("Primer valor:", m.Datos[0][0])
}

Comparacion con C

CaracteristicaGoC
Declaracionvar p *intint *p;
Direccion de&x&x
Dereferencia*p*p
AritmeticaNo permitidaPermitida
Null/Nilnil (panic al dereferenciar)NULL (undefined behavior)
MemoriaGarbage collectorManual (malloc/free)
Acceso a campop.Campo (azucar sintactico)p->campo

Ejercicios

  1. Escribe una funcion intercambiar(a, b *int) que intercambie los valores de dos variables
  2. Crea una funcion encontrarMax(nums []int) *int que retorne un puntero al elemento mayor del slice
  3. Implementa una funcion incrementarEdad(p *Persona) que aumente la edad en 1

Resumen