Punteros a Structs
Parte de la sección Programación Orientada a Objetos del Journey de GO de Coddy — lección 20 de 107.
Al trabajar con structs, a menudo usarás punteros para evitar copiar grandes estructuras de datos y para modificar el struct original. Un puntero a un struct contiene la dirección de memoria de esa instancia de struct.
Puedes crear un puntero a una estructura utilizando el operador & en una estructura existente, o utilizando el operador de dirección directamente con un literal de estructura.
type Person struct {
Name string
Age int
}
p := Person{Name: "Alice", Age: 30}
ptr := &p // puntero a una estructura existente
// O crear directamente
ptr2 := &Person{Name: "Bob", Age: 25}Go proporciona un atajo conveniente al acceder a los campos de una estructura a través de un puntero. En lugar de escribir (*ptr).Name, simplemente puedes escribir ptr.Name. Go desreferencia automáticamente el puntero por ti.
ptr := &Person{Name: "Alice", Age: 30}
// Ambos funcionan de forma idéntica
fmt.Println((*ptr).Name) // desreferencia explícita
fmt.Println(ptr.Name) // desreferencia automáticaEsta desreferenciación automática también se aplica al modificar campos. Los cambios a través del puntero afectan a la estructura original.
p := Person{Name: "Alice", Age: 30}
ptr := &p
ptr.Age = 31 // modifica el original
fmt.Println(p.Age) // 31Los punteros a struct son esenciales en los patrones de OOP de Go. Permiten que los métodos modifiquen el estado del receptor, permiten pasar structs grandes a funciones de manera eficiente y forman la base para construir estructuras de datos enlazadas como árboles y listas.
Desafío
FácilVamos a construir un sistema de cuenta bancaria que demuestre cómo los punteros a estructuras permiten modificar los datos originales de manera eficiente. Crearás una cuenta que se puede actualizar a través de operaciones con punteros.
Organizarás tu código en dos archivos:
account.go: Define una estructuraBankAccountcon los camposOwner(string),Balance(float64) yAccountNumber(string). Crea una función llamadaNewAccountque reciba un nombre de propietario y un número de cuenta como parámetros y devuelva un puntero a una nuevaBankAccountcon un saldo inicial de 0. También crea una funciónDepositque reciba un puntero*BankAccounty un monto, y luego sume ese monto al saldo de la cuenta a través del puntero.main.go: Lee la información de la cuenta desde la entrada, crea una cuenta usando tu función constructora (que devuelve un puntero), realiza un depósito usando el puntero y muestra los detalles de la cuenta para confirmar que el saldo fue modificado.
Se proporcionarán las siguientes entradas:
- Línea 1: Nombre del propietario
- Línea 2: Número de cuenta
- Línea 3: Monto del depósito (float)
Formato de salida:
Account: [AccountNumber]
Owner: [Owner]
Balance: [Balance]Por ejemplo, dados John Smith, ACC-12345 y 500.50, tu salida debería ser:
Account: ACC-12345
Owner: John Smith
Balance: 500.50Recuerda que tu función NewAccount debe devolver *BankAccount usando el operador &, y tu función Deposit puede modificar el saldo directamente a través del puntero usando la desreferenciación automática de Go (por ejemplo, account.Balance en lugar de (*account).Balance).
Hoja de referencia
Un puntero a una estructura contiene la dirección de memoria de una instancia de estructura. Crea punteros a estructuras usando el operador &:
type Person struct {
Name string
Age int
}
p := Person{Name: "Alice", Age: 30}
ptr := &p // puntero a una estructura existente
// O crear directamente
ptr2 := &Person{Name: "Bob", Age: 25}Go proporciona desreferenciación automática al acceder a los campos de una estructura a través de un puntero. En lugar de (*ptr).Name, puedes escribir ptr.Name:
ptr := &Person{Name: "Alice", Age: 30}
// Ambos funcionan de forma idéntica
fmt.Println((*ptr).Name) // desreferenciación explícita
fmt.Println(ptr.Name) // desreferenciación automáticaLos cambios a través de un puntero afectan a la estructura original:
p := Person{Name: "Alice", Age: 30}
ptr := &p
ptr.Age = 31 // modifica el original
fmt.Println(p.Age) // 31Los punteros a estructuras permiten que los métodos modifiquen el estado del receptor, permiten pasar estructuras grandes a funciones de manera eficiente y forman la base para estructuras de datos enlazadas.
Pruébalo tú mismo
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Leer el nombre del propietario
ownerName, _ := reader.ReadString('\n')
ownerName = strings.TrimSpace(ownerName)
// Leer el número de cuenta
accountNumber, _ := reader.ReadString('\n')
accountNumber = strings.TrimSpace(accountNumber)
// Leer el monto del depósito
amountStr, _ := reader.ReadString('\n')
amountStr = strings.TrimSpace(amountStr)
amount, _ := strconv.ParseFloat(amountStr, 64)
// TODO: Crear una nueva cuenta usando la función NewAccount (devuelve un puntero)
// TODO: Realizar un depósito usando la función Deposit con el puntero de la cuenta
// TODO: Imprimir los detalles de la cuenta en el formato requerido:
// Account: [AccountNumber]
// Owner: [Owner]
// Balance: [Balance]
}
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