Go
Estructuras
Buscar..
Introducción
Las estructuras son conjuntos de varias variables empaquetadas juntas. La estructura en sí misma es solo un paquete que contiene variables y que las hace fácilmente accesibles.
A diferencia de en C, las estructuras de Go pueden tener métodos adjuntos. También les permite implementar interfaces. Eso hace que las estructuras de Go sean similares a los objetos, pero faltan (probablemente intencionalmente) algunas características importantes conocidas en lenguajes orientados a objetos como la herencia.
Declaración Básica
Una estructura básica se declara de la siguiente manera:
type User struct {
FirstName, LastName string
Email string
Age int
}
Cada valor se llama un campo. Los campos generalmente se escriben uno por línea, con el nombre del campo precediendo a su tipo. Los campos consecutivos del mismo tipo se pueden combinar, como FirstName
y LastName
en el ejemplo anterior.
Campos exportados frente a no exportados (privado frente a público)
Los campos de Struct cuyos nombres comienzan con una letra mayúscula se exportan. Todos los demás nombres no son exportados.
type Account struct {
UserID int // exported
accessToken string // unexported
}
Solo se puede acceder a los campos no exportados por código dentro del mismo paquete. Como tal, si alguna vez accede a un campo de un paquete diferente , su nombre debe comenzar con una letra mayúscula.
package main
import "bank"
func main() {
var x = &bank.Account{
UserID: 1, // this works fine
accessToken: "one", // this does not work, since accessToken is unexported
}
}
Sin embargo, desde el paquete del bank
, puede acceder tanto a UserId como a accessToken sin problemas.
El bank
paquetes podría implementarse así:
package bank
type Account struct {
UserID int
accessToken string
}
func ProcessUser(u *Account) {
u.accessToken = doSomething(u) // ProcessUser() can access u.accessToken because
// it's defined in the same package
}
Composición e incrustación
La composición proporciona una alternativa a la herencia. Una estructura puede incluir otro tipo por nombre en su declaración:
type Request struct {
Resource string
}
type AuthenticatedRequest struct {
Request
Username, Password string
}
En el ejemplo anterior, AuthenticatedRequest
contendrá cuatro miembros públicos: Resource
, Request
, Username
y Password
.
Las estructuras compuestas se pueden instanciar y utilizar de la misma manera que las estructuras normales:
func main() {
ar := new(AuthenticatedRequest)
ar.Resource = "example.com/request"
ar.Username = "bob"
ar.Password = "P@ssw0rd"
fmt.Printf("%#v", ar)
}
Incrustación
En el ejemplo anterior, la Request
es un campo incrustado. La composición también se puede lograr mediante la incrustación de un tipo diferente. Esto es útil, por ejemplo, para decorar un Struct con más funcionalidad. Por ejemplo, continuando con el ejemplo de Recursos, queremos una función que formatee el contenido del campo de Recursos para prefijarlo con http://
o https://
. Tenemos dos opciones: crear los nuevos métodos en AuthenticatedRequest o incrustarlos desde una estructura diferente:
type ResourceFormatter struct {}
func(r *ResourceFormatter) FormatHTTP(resource string) string {
return fmt.Sprintf("http://%s", resource)
}
func(r *ResourceFormatter) FormatHTTPS(resource string) string {
return fmt.Sprintf("https://%s", resource)
}
type AuthenticatedRequest struct {
Request
Username, Password string
ResourceFormatter
}
Y ahora la función principal podría hacer lo siguiente:
func main() {
ar := new(AuthenticatedRequest)
ar.Resource = "www.example.com/request"
ar.Username = "bob"
ar.Password = "P@ssw0rd"
println(ar.FormatHTTP(ar.Resource))
println(ar.FormatHTTPS(ar.Resource))
fmt.Printf("%#v", ar)
}
Observe que el AuthenticatedRequest
que tiene una estructura incorporada ResourceFormatter
.
Pero la desventaja es que no puedes acceder a objetos fuera de tu composición. Por lo tanto, ResourceFormatter
no puede acceder a los miembros de AuthenticatedRequest
.
Métodos
Los métodos de Struct son muy similares a las funciones:
type User struct {
name string
}
func (u User) Name() string {
return u.name
}
func (u *User) SetName(newName string) {
u.name = newName
}
La única diferencia es la adición del método receptor. Se puede declarar como una instancia del tipo o un puntero a una instancia del tipo. Como SetName()
muta la instancia, el receptor debe ser un puntero para realizar un cambio permanente en la instancia.
Por ejemplo:
package main
import "fmt"
type User struct {
name string
}
func (u User) Name() string {
return u.name
}
func (u *User) SetName(newName string) {
u.name = newName
}
func main() {
var me User
me.SetName("Slim Shady")
fmt.Println("My name is", me.Name())
}
Estructura anónima
Es posible crear una estructura anónima:
data := struct {
Number int
Text string
} {
42,
"Hello world!",
}
Ejemplo completo:
package main
import (
"fmt"
)
func main() {
data := struct {Number int; Text string}{42, "Hello world!"} // anonymous struct
fmt.Printf("%+v\n", data)
}
Etiquetas
Los campos Struct pueden tener etiquetas asociadas con ellos. Estas etiquetas pueden ser leídas por el reflect
paquete para obtener información personalizada especificada sobre un campo por el desarrollador.
struct Account {
Username string `json:"username"`
DisplayName string `json:"display_name"`
FavoriteColor string `json:"favorite_color,omitempty"`
}
En el ejemplo anterior, las etiquetas se usan para cambiar los nombres de clave utilizados por el paquete encoding/json
al calcular o desmarcar JSON.
Si bien la etiqueta puede tener cualquier valor de cadena, se considera una práctica recomendada usar los pares de key:"value"
separados por espacios:
struct StructName {
FieldName int `package1:"customdata,moredata" package2:"info"`
}
Las etiquetas de estructura utilizadas con el paquete encoding/xml
y encoding/json
se utilizan en todo el libarary estándar.
Haciendo copias struct.
Una estructura puede simplemente copiarse usando una asignación.
type T struct {
I int
S string
}
// initialize a struct
t := T{1, "one"}
// make struct copy
u := t // u has its field values equal to t
if u == t { // true
fmt.Println("u and t are equal") // Prints: "u and t are equal"
}
En el caso anterior, 't'
y 'u' ahora son objetos separados (valores de estructura).
Dado que T
no contiene ningún tipo de referencia (segmentos, mapa, canales) ya que sus campos, t
y u
anteriores pueden modificarse sin afectarse entre sí.
fmt.Printf("t.I = %d, u.I = %d\n", t.I, u.I) // t.I = 100, u.I = 1
Sin embargo, si T
contiene un tipo de referencia, por ejemplo:
type T struct {
I int
S string
xs []int // a slice is a reference type
}
Luego, una copia por asignación simple copiaría también el valor del campo de tipo de sector en el nuevo objeto. Esto daría como resultado dos objetos diferentes que se refieren al mismo objeto de sector.
// initialize a struct
t := T{I: 1, S: "one", xs: []int{1, 2, 3}}
// make struct copy
u := t // u has its field values equal to t
Dado que tanto u como t hacen referencia a la misma división a través de su campo xs, la actualización de un valor en la división de un objeto reflejaría el cambio en la otra.
// update a slice field in u
u.xs[1] = 500
fmt.Printf("t.xs = %d, u.xs = %d\n", t.xs, u.xs)
// t.xs = [1 500 3], u.xs = [1 500 3]
Por lo tanto, se debe tener mucho cuidado para garantizar que esta propiedad de tipo de referencia no produzca un comportamiento involuntario.
Para copiar los objetos anteriores, por ejemplo, se podría realizar una copia explícita del campo de división:
// explicitly initialize u's slice field
u.xs = make([]int, len(t.xs))
// copy the slice values over from t
copy(u.xs, t.xs)
// updating slice value in u will not affect t
u.xs[1] = 500
fmt.Printf("t.xs = %d, u.xs = %d\n", t.xs, u.xs)
// t.xs = [1 2 3], u.xs = [1 500 3]
Literales de Struct
Un valor de un tipo de estructura se puede escribir utilizando un literal de estructura que especifica valores para sus campos.
type Point struct { X, Y int }
p := Point{1, 2}
El ejemplo anterior especifica cada campo en el orden correcto. Lo que no es útil, porque los programadores tienen que recordar los campos exactos en orden. Más a menudo, una estructura se puede inicializar enumerando algunos o todos los nombres de campo y sus valores correspondientes.
anim := gif.GIF{LoopCount: nframes}
Los campos omitidos se establecen en el valor cero para su tipo.
Nota: Las dos formas no se pueden mezclar en el mismo literal.
Estructura vacía
Una estructura es una secuencia de elementos con nombre, campos llamados, cada uno de los cuales tiene un nombre y un tipo. La estructura vacía no tiene campos, como esta estructura vacía anónima:
var s struct{}
O como este tipo de estructura vacía nombrada:
type T struct{}
Lo interesante de la estructura vacía es que, su tamaño es cero (prueba The Go Playground ):
fmt.Println(unsafe.Sizeof(s))
Esto imprime 0
, por lo que la estructura vacía no toma memoria. así que es una buena opción para salir del canal, como (prueba The Go Playground ):
package main
import (
"fmt"
"time"
)
func main() {
done := make(chan struct{})
go func() {
time.Sleep(1 * time.Second)
close(done)
}()
fmt.Println("Wait...")
<-done
fmt.Println("done.")
}