Paso por valor vs referencia
Parte de la sección Programación Orientada a Objetos del Journey de GO de Coddy — lección 21 de 107.
Go es un lenguaje de paso por valor. Cuando pasas una variable a una función, Go crea una copia de ese valor. Esto significa que los cambios dentro de la función no afectan a la variable original.
func double(n int) {
n = n * 2
}
func main() {
x := 10
double(x)
fmt.Println(x) // 10 (sin cambios)
}Lo mismo se aplica a los structs. Pasar un struct a una función copia todos sus campos, por lo que las modificaciones dentro de la función se pierden.
type Person struct {
Name string
Age int
}
func birthday(p Person) {
p.Age++
}
func main() {
alice := Person{Name: "Alice", Age: 30}
birthday(alice)
fmt.Println(alice.Age) // 30 (sin cambios)
}Para modificar el valor original, pasa un puntero en su lugar. El puntero en sí se copia, pero sigue apuntando a la misma dirección de memoria, lo que te permite cambiar los datos originales.
func birthday(p *Person) {
p.Age++
}
func main() {
alice := Person{Name: "Alice", Age: 30}
birthday(&alice)
fmt.Println(alice.Age) // 31 (¡modificado!)
}Usa punteros cuando necesites modificar el valor original o al pasar structs grandes para evitar copias costosas. Usa valores cuando quieras proteger los datos originales de cambios no deseados.
Desafío
FácilVamos a construir un sistema de carrito de compras que demuestre la diferencia entre pasar structs por valor frente a pasarlos por puntero. Verás de primera mano cómo solo los parámetros de puntero pueden modificar los datos originales.
Organizarás tu código en dos archivos:
cart.go: Define un structCartItemcon los camposName(string),Price(float64) yQuantity(int). Crea dos funciones que intenten actualizar la cantidad:UpdateQuantityByValuerecibe unCartItem(por valor) y una nueva cantidad, luego establece la cantidad del artículo al nuevo valor.UpdateQuantityByPointerrecibe un*CartItem(puntero) y una nueva cantidad, luego establece la cantidad del artículo al nuevo valor.
TotalenCartItemque devuelva el precio multiplicado por la cantidad.main.go: Lee los detalles del artículo desde la entrada, crea un CartItem y luego demuestra ambos enfoques de actualización. Primero intenta actualizar con la función por valor y muestra el resultado, luego actualiza con la función por puntero y muestra el resultado nuevamente.
Se proporcionarán las siguientes entradas:
- Línea 1: Nombre del artículo
- Línea 2: Precio (float)
- Línea 3: Cantidad inicial (entero)
- Línea 4: Nueva cantidad a establecer (entero)
Formato de salida:
Initial: [Name] x[Quantity] = $[Total]
After value update: [Name] x[Quantity] = $[Total]
After pointer update: [Name] x[Quantity] = $[Total]Por ejemplo, dados Laptop, 999.99, 1 y 3, tu salida debería ser:
Initial: Laptop x1 = $999.99
After value update: Laptop x1 = $999.99
After pointer update: Laptop x3 = $2999.97Observa cómo la cantidad se mantiene en 1 después de la actualización por valor (la función modificó una copia), pero cambia a 3 después de la actualización por puntero (la función modificó el original). Formatea los totales con dos decimales.
Hoja de referencia
Go es un lenguaje de paso por valor. Cuando pasas una variable a una función, Go crea una copia de ese valor, por lo que los cambios dentro de la función no afectan a la variable original.
func double(n int) {
n = n * 2
}
func main() {
x := 10
double(x)
fmt.Println(x) // 10 (sin cambios)
}Lo mismo se aplica a las estructuras (structs). Pasar una estructura a una función copia todos sus campos:
type Person struct {
Name string
Age int
}
func birthday(p Person) {
p.Age++
}
func main() {
alice := Person{Name: "Alice", Age: 30}
birthday(alice)
fmt.Println(alice.Age) // 30 (sin cambios)
}Para modificar el valor original, pasa un puntero. El puntero en sí se copia, pero sigue apuntando a la misma dirección de memoria:
func birthday(p *Person) {
p.Age++
}
func main() {
alice := Person{Name: "Alice", Age: 30}
birthday(&alice)
fmt.Println(alice.Age) // 31 (¡modificado!)
}Usa punteros cuando necesites modificar el valor original o al pasar estructuras grandes para evitar copias costosas. Usa valores cuando quieras proteger los datos originales de cambios no deseados.
Pruébalo tú mismo
package main
import (
"fmt"
)
func main() {
// Leer entrada
var name string
var price float64
var initialQty, newQty int
fmt.Scanln(&name)
fmt.Scanln(&price)
fmt.Scanln(&initialQty)
fmt.Scanln(&newQty)
// TODO: Crear un CartItem con los valores de entrada
// TODO: Imprimir el estado inicial usando el formato:
// fmt.Printf("Initial: %s x%d = $%.2f\n", ...)
// TODO: Intentar actualizar la cantidad usando UpdateQuantityByValue
// Luego imprimir el resultado (notar que la cantidad no cambiará)
// TODO: Actualizar la cantidad usando UpdateQuantityByPointer
// Luego imprimir el resultado (notar que la cantidad cambiará)
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de POO en Go
Archivos externosEspacio de trabajo y módulos de GoPaquetes e importacionesNombres exportados vs. no exportadosIntroducción a la POO en GoStructs como clasesDefinición de métodos en structsReceptores de puntero vs. de valorInicialización de structsFunciones constructorasResumen - Calculadora simple4Interfaces
Introducción a las interfacesImplementación implícitaLa interfaz como contratoInterfaz vacía (any)Type AssertionType SwitchComposición de interfacesInterfaces Stringer y ErrorResumen: Calculadora de figuras7Encapsulamiento
Campos exportados vs. no exportadosEncapsulamiento a nivel de paqueteMétodos Getter y SetterOcultamiento de información en GoResumen - Registros de estudiantes10Genéricos (Go 1.18+)
Introducción a los GenéricosParámetros de tipoRestricciones de tipoStructs genéricosSolución alternativa para métodos genéricosResumen - Colección genérica13Patrones de diseño - Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón Abstract FactoryPatrón ObserverPatrón Strategy2Inmersión profunda en Tipos y Structs
Tipos básicos y compuestosDefiniciones de tipos personalizadosEtiquetas de StructsStructs anónimosStructs anidadosValores cero y por defectoRepaso - Libreta de contactos5Composición sobre herencia
Por qué Go no tiene herenciaConceptos básicos de incrustación de structsPromoción de métodosIncrustación de múltiples structsIncrustación vs AgregaciónShadowing de métodos incrustadosResumen - Jerarquía de empleados8Manejo de Errores y OOP
La interfaz errorTipos de errores personalizadosEnvoltura de errores (fmt.Errorf)Errores centinelaerrors.Is() y errors.As()Panic, Defer y RecoverResumen - Analizador de archivos3Punteros y Memoria
Fundamentos de punteros en GoPunteros a StructsPaso por valor vs referenciaLa función new()Garbage Collection en GoResumen - Constructor de Linked List6Polimorfismo en Go
Polimorfismo mediante interfacesDuck Typing en GoReglas de satisfacción de interfacesColecciones polimórficasInyección de dependenciasResumen - Procesador de pagos9Concurrencia y POO
Fundamentos de GoroutinesChannels y comunicaciónChannels con buffer vs. sin bufferSentencia Selectsync.Mutex y sync.RWMutexsync.WaitGroupDiseño de estructuras Thread-SafeResumen - Worker Pool