Skip to content

Strutture Go

Le strutture possono memorizzare un insieme di dati di tipi diversi, sono un tipo composito. Go ha abbandonato classi ed ereditarietà, ha anche abbandonato i metodi costruttori, indebolendo deliberatamente le funzionalità orientate agli oggetti. Go non è un linguaggio OOP tradizionale, ma Go ha ancora un'ombra di OOP. Tramite strutture e metodi è anche possibile simulare una classe. Di seguito è riportato un semplice esempio di struttura:

go
type Programmer struct {
  Name     string
  Age      int
  Job      string
  Language []string
}

Dichiarazione

La dichiarazione di una struttura è molto semplice. Ecco un esempio:

go
type Person struct {
   name string
   age int
}

La struttura stessa e i suoi campi interni seguono entrambi le regole di denominazione con maiuscole/minuscole per l'esposizione. Per alcuni campi adiacenti dello stesso tipo, non è necessario ripetere la dichiarazione del tipo. Di seguito:

go
type Rectangle struct {
  height, width, area int
  color               string
}

TIP

Quando si dichiarano i campi di una struttura, il nome del campo non può essere duplicato con il nome del metodo

Istanziamento

Go non esiste un metodo costruttore. Nella maggior parte dei casi, si utilizza il seguente modo per istanziare una struttura. Durante l'inizializzazione, è come una map, specificando il nome del campo e poi inizializzando il valore del campo.

go
programmer := Programmer{
   Name:     "jack",
   Age:      19,
   Job:      "coder",
   Language: []string{"Go", "C++"},
}

Tuttavia, è anche possibile omettere i nomi dei campi. Quando si omettono i nomi dei campi, è necessario inizializzare tutti i campi. In genere non si consiglia di utilizzare questo metodo, perché la leggibilità è molto scarsa.

go
programmer := Programmer{
   "jack",
   19,
   "coder",
   []string{"Go", "C++"}}

Se il processo di istanziazione è complesso, puoi anche scrivere una funzione per istanziare la struttura, come mostrato di seguito. Puoi anche intenderla come un costruttore.

go
type Person struct {
  Name    string
  Age     int
  Address string
  Salary  float64
}

func NewPerson(name string, age int, address string, salary float64) *Person {
  return &Person{Name: name, Age: age, Address: address, Salary: salary}
}

Tuttavia, Go non supporta il sovraccarico di funzioni e metodi, quindi non è possibile definire parametri diversi per la stessa funzione o metodo. Se desideri istanziare una struttura in più modi, o crei più funzioni costruttore, o si consiglia di utilizzare il modello options.

Modello options

Il modello options è un modello di progettazione molto comune nel linguaggio Go, può istanziare una struttura in modo più flessibile, ha una forte estensibilità e non è necessario modificare la firma della funzione del costruttore. Supponiamo di avere la seguente struttura

go
type Person struct {
  Name     string
  Age      int
  Address  string
  Salary   float64
  Birthday string
}

Dichiara un tipo PersonOptions, accetta un parametro di tipo *Person. Deve essere un puntatore, perché dobbiamo assegnare un valore a Person nel closure.

go
type PersonOptions func(p *Person)

Successivamente, crea le funzioni option. In genere iniziano con With. Il loro valore di ritorno è una funzione closure.

go
func WithName(name string) PersonOptions {
  return func(p *Person) {
    p.Name = name
  }
}

func WithAge(age int) PersonOptions {
  return func(p *Person) {
    p.Age = age
  }
}

func WithAddress(address string) PersonOptions {
  return func(p *Person) {
    p.Address = address
  }
}

func WithSalary(salary float64) PersonOptions {
  return func(p *Person) {
    p.Salary = salary
  }
}

La firma della funzione costruttore effettivamente dichiarata è la seguente. Accetta un parametro di tipo PersonOptions a lunghezza variabile.

go
func NewPerson(options ...PersonOptions) *Person {
    // Applica prima le options
  p := &Person{}
    for _, option := range options {
        option(p)
    }

  // Gestione dei valori predefiniti
  if p.Age < 0 {
    p.Age = 0
  }
  ......

    return p
}

In questo modo, per diverse esigenze di istanziazione, è sufficiente un solo costruttore. Basta passare diverse funzioni Options.

go
func main() {
  pl := NewPerson(
    WithName("John Doe"),
    WithAge(25),
    WithAddress("123 Main St"),
    WithSalary(10000.00),
  )

  p2 := NewPerson(
    WithName("Mike jane"),
    WithAge(30),
  )
}

Il modello di opzioni funzionali può essere visto in molti progetti open source. Anche il metodo di istanziazione del server gRPC utilizza questo modello di progettazione. Il modello di opzioni funzionali è adatto solo per istanziazioni complesse. Se i parametri sono solo semplici, si consiglia di utilizzare comunque un costruttore ordinario per risolvere.

Composizione

In Go, la relazione tra le strutture è espressa tramite composizione. Può essere composizione esplicita o composizione anonima. Quest'ultima è più simile all'ereditarietà, ma essenzialmente non c'è alcun cambiamento. Ad esempio:

Modo di composizione esplicita

go
type Person struct {
   name string
   age  int
}

type Student struct {
   p      Person
   school string
}

type Employee struct {
   p   Person
   job string
}

Durante l'uso, è necessario specificare esplicitamente il campo p

go
student := Student{
   p:      Person{name: "jack", age: 18},
   school: "lili school",
}
fmt.Println(student.p.name)

La composizione anonima non richiede di specificare esplicitamente il campo

go
type Person struct {
  name string
  age  int
}

type Student struct {
  Person
  school string
}

type Employee struct {
  Person
  job string
}

Il nome del campo anonimo è per impostazione predefinita il nome del tipo. Il chiamante può accedere direttamente ai campi e ai metodi di quel tipo, ma oltre a essere più comodo, non c'è alcuna differenza rispetto al primo metodo.

go
student := Student{
   Person: Person{name: "jack",age: 18},
   school: "lili school",
}
fmt.Println(student.name)

Puntatori

Per un puntatore a struttura, non è necessario dereferenziare per accedere direttamente al contenuto della struttura. Ecco un esempio:

go
p := &Person{
   name: "jack",
   age:  18,
}
fmt.Println(p.age,p.name)

Durante la compilazione, verrà convertito in (*p).name, (*p).age. In realtà, è ancora necessario dereferenziare, ma durante la codifica è possibile risparmiare, è una sorta di zucchero sintattico.

Tag

I tag di struttura sono una forma di metaprogrammazione. Combinati con la riflessione, possono realizzare molte funzioni meravigliose. Il formato è il seguente

go
`key1:"val1" key2:"val2"`

I tag sono una forma di coppia chiave-valore, separati da spazi. La tolleranza agli errori dei tag di struttura è molto bassa. Se non vengono scritti nel formato corretto, non sarà possibile leggerli normalmente, ma durante la compilazione non ci saranno errori. Di seguito è riportato un esempio di utilizzo.

go
type Programmer struct {
    Name     string `json:"name"`
    Age      int `yaml:"age"`
    Job      string `toml:"job"`
    Language []string `properties:"language"`
}

L'uso più ampio dei tag di struttura è la definizione di alias in vari formati di serializzazione. L'uso dei tag deve essere combinato con la riflessione per esprimere appieno le sue funzionalità.

Allineamento della memoria

La distribuzione della memoria dei campi di una struttura in Go segue le regole di allineamento della memoria. Questo può ridurre il numero di accessi alla memoria da parte della CPU. Di conseguenza, la memoria occupata sarà maggiore. È un mezzo per scambiare spazio con tempo. Supponiamo di avere la seguente struttura

go
type Num struct {
  A int64
  B int32
  C int16
  D int8
    E int32
}

Sono noti i byte occupati da questi tipi

  • int64 occupa 8 byte
  • int32 occupa 4 byte
  • int16 occupa 2 byte
  • int8 occupa un byte

L'occupazione di memoria dell'intera struttura sembra essere 8+4+2+1+4=19 byte? Ovviamente non è così. Secondo le regole di allineamento della memoria, la lunghezza di occupazione della memoria di una struttura deve essere almeno un multiplo intero del campo più grande. Se insufficiente, viene completata. Il più grande in questa struttura è int64 che occupa 8 byte, quindi la distribuzione della memoria è mostrata nella figura seguente

Quindi in realtà occupa 24 byte, di cui 5 byte sono inutili.

Guardiamo di nuovo la seguente struttura

go
type Num struct {
  A int8
  B int64
  C int8
}

Dopo aver compreso le regole precedenti, è possibile comprendere rapidamente che la sua occupazione di memoria è anche di 24 byte. Sebbene abbia solo tre campi, spreca ben 14 byte.

Ma possiamo regolare i campi, cambiando nell'ordine seguente

type Num struct {
  A int8
  C int8
  B int64
}

In questo modo, la memoria occupata diventa 16 byte, spreca 6 byte, riducendo lo spreco di memoria di 8 byte.

Teoricamente, facendo sì che i campi nella struttura siano distribuiti in un ordine ragionevole, è possibile ridurre la loro occupazione di memoria. Tuttavia, nel processo di codifica effettivo, non c'è un motivo necessario per farlo. Non porterà necessariamente un miglioramento sostanziale nella riduzione dell'occupazione di memoria, ma aumenterà sicuramente la pressione sanguigna e il carico cognitivo degli sviluppatori. Soprattutto in alcuni campi della struttura aziendale, il numero di campi può essere fino a decine o centinaia, quindi è sufficiente conoscerlo.

TIP

Se vuoi davvero risparmiare memoria con questo metodo, puoi guardare queste due librerie

Controlleranno le strutture nel tuo codice sorgente, calcoleranno e riorganizzeranno i campi della struttura per minimizzare la memoria occupata dalla struttura.

Struttura vuota

Una struttura vuota non ha campi e non occupa spazio di memoria. Possiamo calcolare la dimensione in byte occupata tramite la funzione unsafe.SizeOf

go
func main() {
   type Empty struct {}
   fmt.Println(unsafe.Sizeof(Empty{}))
}

Output

0

Gli scenari di utilizzo delle strutture vuote sono molti. Ad esempio, come menzionato in precedenza, come tipo di valore di una map, è possibile utilizzare la map come set. Oppure come tipo di canale, indica un canale di tipo di sola notifica.

Golang by www.golangdev.cn edit