Go
Struktury
Szukaj…
Wprowadzenie
Struktury są zestawami różnych zmiennych spakowanych razem. Sama struktura jest tylko pakietem zawierającym zmienne i ułatwiającym ich dostęp.
W przeciwieństwie do C, struktury Go mogą mieć dołączone metody. Pozwala im także na implementację interfejsów. To sprawia, że struktury Go są podobne do obiektów, ale brakuje im (prawdopodobnie celowo) niektórych głównych funkcji znanych w językach obiektowych, takich jak dziedziczenie.
Podstawowa deklaracja
Podstawową strukturę deklaruje się następująco:
type User struct {
FirstName, LastName string
Email string
Age int
}
Każda wartość nazywana jest polem. Pola są zwykle zapisywane po jednym w wierszu, a nazwa pola poprzedza jego typ. Kolejne pola tego samego typu mogą być łączone, jak FirstName
i LastName
w powyższym przykładzie.
Pola wyeksportowane a niewyportowane (prywatne vs publiczne)
Pola struktur, których nazwy rozpoczynają się od dużej litery, są eksportowane. Wszystkie pozostałe nazwy nie są importowane.
type Account struct {
UserID int // exported
accessToken string // unexported
}
Do niewyportowanych pól można uzyskać dostęp tylko za pomocą kodu w tym samym pakiecie. W związku z tym, jeśli kiedykolwiek uzyskujesz dostęp do pola z innego pakietu, jego nazwa musi zaczynać się wielką literą.
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
}
}
Jednak z pakietu bank
można bez problemu uzyskać dostęp zarówno do UserId, jak i accessToken.
bank
pakietów można zaimplementować w następujący sposób:
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
}
Skład i osadzanie
Kompozycja stanowi alternatywę dla dziedziczenia. Struktur może zawierać w deklaracji inny typ według nazwy:
type Request struct {
Resource string
}
type AuthenticatedRequest struct {
Request
Username, Password string
}
W powyższym przykładzie Request
AuthenticatedRequest
będzie zawierać czterech członków publicznych: Resource
, Request
, nazwę Username
i Password
.
Struktury złożone mogą być tworzone i używane w taki sam sposób, jak zwykłe struktury:
func main() {
ar := new(AuthenticatedRequest)
ar.Resource = "example.com/request"
ar.Username = "bob"
ar.Password = "P@ssw0rd"
fmt.Printf("%#v", ar)
}
Osadzanie
W poprzednim przykładzie Request
jest polem osadzonym. Kompozycję można również osiągnąć poprzez osadzenie innego typu. Jest to przydatne na przykład do dekorowania Structa większą funkcjonalnością. Na przykład, kontynuując przykład z zasobem, chcemy, aby funkcja formatująca zawartość pola zasobu poprzedzała go http://
lub https://
. Mamy dwie opcje: utwórz nowe metody w AuthenticatedRequest lub osadz ją z innej struktury:
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
}
A teraz główna funkcja może wykonywać następujące czynności:
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)
}
Spójrz, że AuthenticatedRequest
z osadzoną strukturą ResourceFormatter
.
Ale wadą jest to, że nie można uzyskać dostępu do obiektów poza kompozycją. Dlatego ResourceFormatter
nie może uzyskać dostępu do członków z AuthenticatedRequest
.
Metody
Metody struktur są bardzo podobne do funkcji:
type User struct {
name string
}
func (u User) Name() string {
return u.name
}
func (u *User) SetName(newName string) {
u.name = newName
}
Jedyną różnicą jest dodanie odbiornika metody. Może być zadeklarowany jako instancja typu lub wskaźnik do instancji typu. Ponieważ SetName()
mutuje instancję, odbiornik musi być wskaźnikiem, aby spowodować trwałą zmianę w instancji.
Na przykład:
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())
}
Anonimowa struktura
Możliwe jest utworzenie anonimowej struktury:
data := struct {
Number int
Text string
} {
42,
"Hello world!",
}
Pełny przykład:
package main
import (
"fmt"
)
func main() {
data := struct {Number int; Text string}{42, "Hello world!"} // anonymous struct
fmt.Printf("%+v\n", data)
}
Tagi
Z polami strukturalnymi mogą być powiązane tagi. Te tagi mogą być odczytywane przez pakiet reflect
, aby uzyskać niestandardowe informacje o polu przez dewelopera.
struct Account {
Username string `json:"username"`
DisplayName string `json:"display_name"`
FavoriteColor string `json:"favorite_color,omitempty"`
}
W powyższym przykładzie tagi służą do zmiany nazw kluczy używanych przez pakiet encoding/json
podczas zestawiania lub rozszyfrowywania JSON.
Chociaż znacznikiem może być dowolna wartość ciągu, najlepszym rozwiązaniem jest użycie par key:"value"
oddzielonych spacjami key:"value"
:
struct StructName {
FieldName int `package1:"customdata,moredata" package2:"info"`
}
Znaczniki struct używane w pakiecie encoding/xml
i encoding/json
są używane w całym standardowym lobby.
Robienie kopii struktur.
Strukturę można po prostu skopiować za pomocą przypisania.
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"
}
W powyższym przypadku 't'
i „u” są teraz oddzielnymi obiektami (wartościami struktury).
Ponieważ T
nie zawiera żadnych typów odniesień (wycinków, mapy, kanałów), ponieważ jego pola t
i u
powyżej można modyfikować bez wpływu na siebie.
fmt.Printf("t.I = %d, u.I = %d\n", t.I, u.I) // t.I = 100, u.I = 1
Jeśli jednak T
zawiera typ odniesienia, na przykład:
type T struct {
I int
S string
xs []int // a slice is a reference type
}
Następnie prosta kopia według przypisania skopiowałaby również wartość pola typu plasterka do nowego obiektu. Powoduje to, że dwa różne obiekty odnoszą się do tego samego obiektu plasterka.
// 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
Ponieważ zarówno u i t odnoszą się do tego samego wycinka przez ich pole xs, aktualizacja wartości w wycinku jednego obiektu odzwierciedlałaby zmianę w drugim.
// 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]
Dlatego należy zwrócić szczególną uwagę, aby ta właściwość typu odniesienia nie powodowała niezamierzonego zachowania.
Aby na przykład skopiować powyższe obiekty, można wykonać jawną kopię pola plasterka:
// 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
Wartość typu struct można zapisać za pomocą literału struct, który określa wartości dla jej pól.
type Point struct { X, Y int }
p := Point{1, 2}
Powyższy przykład określa każde pole we właściwej kolejności. Co nie jest przydatne, ponieważ programiści muszą pamiętać dokładne pola w kolejności. Częściej strukturę można zainicjować, wyświetlając niektóre lub wszystkie nazwy pól i odpowiadające im wartości.
anim := gif.GIF{LoopCount: nframes}
Pominięte pola są ustawiane na wartość zerową dla tego typu.
Uwaga: Te dwie formy nie mogą być mieszane w tym samym literale.
Pusta struktura
Struktur jest sekwencją nazwanych elementów, zwanych polami, z których każdy ma nazwę i typ. Pusta struktura nie ma pól, jak ten anonimowy pusty struct:
var s struct{}
Lub jak ten o nazwie pusty typ struktury:
type T struct{}
Ciekawą rzeczą w pustej strukturze jest to, że jej rozmiar wynosi zero (wypróbuj The Go Playground ):
fmt.Println(unsafe.Sizeof(s))
Wyświetla 0
, więc sama pusta struktura nie zajmuje pamięci. więc jest to dobra opcja dla wyjścia z kanału, na przykład (wypróbuj 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.")
}