Punteros
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
| Sintaxis | Descripcion |
|---|---|
var p *int | Puntero a int, zero value es nil |
p := &variable | Puntero 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
}
| Forma | Zero values | Inicializacion | Uso comun |
|---|---|---|---|
new(T) | Si | No | Tipos simples |
&T{} | Si (campos omitidos) | Si | Structs 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:
- Mutabilidad: modificar un valor desde otra funcion
- Evitar copias grandes: structs con muchos campos o datos grandes
- Semantica de referencia: compartir estado entre funciones
- Valores opcionales:
nilindica ausencia de valor
No uses punteros cuando:
- El tipo es pequeno (int, bool, string corto) - la copia es mas eficiente
- No necesitas modificar el valor original
- Quieres inmutabilidad garantizada
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
| Caracteristica | Go | C |
|---|---|---|
| Declaracion | var p *int | int *p; |
| Direccion de | &x | &x |
| Dereferencia | *p | *p |
| Aritmetica | No permitida | Permitida |
| Null/Nil | nil (panic al dereferenciar) | NULL (undefined behavior) |
| Memoria | Garbage collector | Manual (malloc/free) |
| Acceso a campo | p.Campo (azucar sintactico) | p->campo |
Ejercicios
- Escribe una funcion
intercambiar(a, b *int)que intercambie los valores de dos variables - Crea una funcion
encontrarMax(nums []int) *intque retorne un puntero al elemento mayor del slice - Implementa una funcion
incrementarEdad(p *Persona)que aumente la edad en 1
Resumen
&obtiene la direccion,*dereferenciar el valor- Zero value de puntero es
nil- siempre verificar antes de usar - Go simplifica acceso a structs:
p.Campoen vez de(*p).Campo - No hay aritmetica de punteros por seguridad
- Retornar punteros locales es seguro gracias al escape analysis
- Usar punteros para mutabilidad y structs grandes; valor para tipos pequenos