Recherche…
Introduction
Les structures sont des ensembles de différentes variables regroupés. La structure elle-même n'est qu'un package contenant des variables et les rendant facilement accessibles.
Contrairement à C, les structures de Go peuvent être associées à des méthodes. Cela leur permet également de mettre en œuvre des interfaces. Cela rend les structures de Go similaires aux objets, mais il manque (probablement intentionnellement) des fonctionnalités majeures connues dans les langages orientés objet tels que l'héritage.
Déclaration de base
Une structure de base est déclarée comme suit:
type User struct {
FirstName, LastName string
Email string
Age int
}
Chaque valeur est appelée un champ. Les champs sont généralement écrits un par ligne, le nom du champ précédant son type. Des champs consécutifs du même type peuvent être combinés, comme FirstName
et LastName
dans l'exemple ci-dessus.
Champs exportés et non exportés (privés et publics)
Les champs de structure dont les noms commencent par une lettre majuscule sont exportés. Tous les autres noms sont non exportés.
type Account struct {
UserID int // exported
accessToken string // unexported
}
Les champs non exportés ne sont accessibles que par code dans le même package. En tant que tel, si vous accédez à un champ depuis un autre package, son nom doit commencer par une lettre majuscule.
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
}
}
Cependant, à partir du package de bank
, vous pouvez accéder à UserId et à accessToken sans problème.
La bank
paquets pourrait être implémentée comme ceci:
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
}
Composition et enrobage
La composition offre une alternative à l'héritage. Une structure peut inclure un autre type par nom dans sa déclaration:
type Request struct {
Resource string
}
type AuthenticatedRequest struct {
Request
Username, Password string
}
Dans l'exemple ci-dessus, AuthenticatedRequest
contiendra quatre membres publics: Resource
, Request
, Username
et Password
.
Les structures composites peuvent être instanciées et utilisées de la même manière que les structures normales:
func main() {
ar := new(AuthenticatedRequest)
ar.Resource = "example.com/request"
ar.Username = "bob"
ar.Password = "P@ssw0rd"
fmt.Printf("%#v", ar)
}
Enrobage
Dans l'exemple précédent, Request
est un champ incorporé. La composition peut également être obtenue en intégrant un type différent. Ceci est utile, par exemple, pour décorer une structure avec plus de fonctionnalités. Par exemple, en continuant avec l'exemple Resource, nous voulons une fonction qui formate le contenu du champ Resource pour le préfixer avec http://
ou https://
. Nous avons deux options: créer les nouvelles méthodes sur AuthenticatedRequest ou l’ incorporer à partir d’une structure différente:
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
}
Et maintenant, la fonction principale pourrait faire ce qui suit:
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)
}
Regardez ce que AuthenticatedRequest
a une structure incorporée ResourceFormatter
.
Mais l'inconvénient est que vous ne pouvez pas accéder à des objets en dehors de votre composition. Donc, ResourceFormatter
ne peut pas accéder aux membres de AuthenticatedRequest
.
Les méthodes
Les méthodes Struct sont très similaires aux fonctions:
type User struct {
name string
}
func (u User) Name() string {
return u.name
}
func (u *User) SetName(newName string) {
u.name = newName
}
La seule différence est l'ajout du récepteur de méthode. Il peut être déclaré soit comme une instance du type, soit comme un pointeur sur une instance du type. Étant donné que SetName()
modifie l'instance, le récepteur doit être un pointeur afin d'effectuer un changement permanent dans l'instance.
Par exemple:
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())
}
Structure anonyme
Il est possible de créer une structure anonyme:
data := struct {
Number int
Text string
} {
42,
"Hello world!",
}
Exemple complet:
package main
import (
"fmt"
)
func main() {
data := struct {Number int; Text string}{42, "Hello world!"} // anonymous struct
fmt.Printf("%+v\n", data)
}
Mots clés
Les champs de structure peuvent être associés à des balises. Ces balises peuvent être lues par le package de reflect
pour obtenir des informations personnalisées spécifiées sur un champ par le développeur.
struct Account {
Username string `json:"username"`
DisplayName string `json:"display_name"`
FavoriteColor string `json:"favorite_color,omitempty"`
}
Dans l'exemple ci-dessus, les balises sont utilisées pour modifier les noms de clé utilisés par le package encoding/json
lors du marshaling ou de la suppression de JSON.
Bien que la balise puisse être n'importe quelle valeur de chaîne, il est conseillé d'utiliser des key:"value"
séparées par des espaces key:"value"
paires key:"value"
:
struct StructName {
FieldName int `package1:"customdata,moredata" package2:"info"`
}
Les balises struct utilisées avec le package encoding/xml
et encoding/json
sont utilisées dans la bibliothèque standard.
Faire des copies de structure
Une structure peut simplement être copiée en utilisant l'affectation.
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"
}
Dans le cas ci-dessus, 't'
et «u» sont maintenant des objets séparés (valeurs de structure).
Comme T
ne contient aucun type de référence (tranches, carte, canaux), ses champs, t
et u
ci-dessus peuvent être modifiés sans se toucher.
fmt.Printf("t.I = %d, u.I = %d\n", t.I, u.I) // t.I = 100, u.I = 1
Cependant, si T
contient un type de référence, par exemple:
type T struct {
I int
S string
xs []int // a slice is a reference type
}
Ensuite, une simple copie par affectation copiera également la valeur du champ de type de tranche dans le nouvel objet. Cela se traduirait par deux objets différents faisant référence au même objet de tranche.
// 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
Étant donné que les deux u et t se réfèrent à la même tranche dans leur champ, xs mettant à jour une valeur dans la tranche d'un objet reflète le changement dans l'autre.
// 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]
Par conséquent, des précautions supplémentaires doivent être prises pour s'assurer que cette propriété de type référence ne produit pas de comportement involontaire.
Pour copier des objets ci-dessus par exemple, une copie explicite du champ de la tranche peut être effectuée:
// 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]
Struct Literals
Une valeur d'un type de structure peut être écrite en utilisant un littéral struct qui spécifie des valeurs pour ses champs.
type Point struct { X, Y int }
p := Point{1, 2}
L'exemple ci-dessus spécifie chaque champ dans le bon ordre. Ce qui n'est pas utile, car les programmeurs doivent se rappeler les champs exacts dans l'ordre. Plus souvent, une structure peut être initialisée en listant certains ou tous les noms de champs et leurs valeurs correspondantes.
anim := gif.GIF{LoopCount: nframes}
Les champs omis sont définis sur la valeur zéro pour son type.
Remarque: Les deux formes ne peuvent pas être mélangées dans le même littéral.
Structure vide
Une structure est une séquence d'éléments nommés, appelés champs, chacun ayant un nom et un type. La structure vide n'a pas de champs, comme cette structure vide anonyme:
var s struct{}
Ou comme ce type de structure vide nommée:
type T struct{}
La chose intéressante à propos de la structure vide est que sa taille est zéro (essayez The Go Playground ):
fmt.Println(unsafe.Sizeof(s))
Ceci imprime 0
, donc la structure vide elle-même ne prend pas de mémoire. c'est donc une bonne option pour quitter canal, comme (essayez The Play 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.")
}