le mie informazioni di contatto
Posta[email protected]
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
Se vogliamo scrivere una funzione per confrontare rispettivamente le dimensioni di due numeri interi e di numeri in virgola mobile, dobbiamo scrivere due funzioni. come segue:
func Min(x, y float64) float64 {
if x < y {
return x
}
return y
}
func MinInt(x, y int) int {
if x < y {
return x
}
return y
}
Le due funzioni hanno esattamente la stessa logica di elaborazione ad eccezione dei diversi tipi di dati. Esiste un modo per eseguire la funzione di cui sopra con una funzione? Sì, è generico.
func min[T int | float64](x, y T) T {
if x < y {
return x
}
return y
}
Documento del sito ufficiale:https://go.dev/blog/intro-generics
I generici aggiungono tre importanti nuove funzionalità al linguaggio:
Le funzioni e i tipi ora possono avere parametri di tipo. Un elenco di parametri di tipo è simile a un normale elenco di parametri, tranne per il fatto che utilizza parentesi quadre anziché parentesi tonde.
package main
import (
"fmt"
"golang.org/x/exp/constraints"
)
func GMin[T constraints.Ordered](x, y T) T {
if x < y {
return x
}
return y
}
func main() {
x := GMin[int](2, 3)
fmt.Println(x) // 输出结果为2
}
Tra questi, vincoli.Ordered è un tipo personalizzato (il codice sorgente non è mostrato qui).
Se non capisci, puoi sostituire temporaneamente i vincoli.Ordinato con ·int | float64
。
Fornire un parametro di tipo (int in questo caso) a un GMin è chiamato istanziazione. L'istanziazione avviene in due passaggi.
Dopo aver istanziato con successo, abbiamo una funzione non generica che può essere chiamata come qualsiasi altra funzione.Ad esempio, nel codice like
fmin := GMin[float64]
m := fmin(2.71, 3.14)
Tutti i codici lo sono
package main
import (
"fmt"
"golang.org/x/exp/constraints"
)
func GMin[T constraints.Ordered](x, y T) T {
if x < y {
return x
}
return y
}
func main() {
fmin := GMin[float64] // 相当于func GMin(x, y float64) float64{...}
m := fmin(2.71, 3.14)
fmt.Println(m) // 输出结果为2.71
}
L'istanziazione di GMin[float64] produce in realtà la nostra funzione Min in virgola mobile originale, che possiamo utilizzare nelle chiamate di funzione.
I parametri di tipo possono essere utilizzati anche con i tipi.
type Tree[T interface{}] struct {
left, right *Tree[T]
value T
}
func (t *Tree[T]) Lookup(x T) *Tree[T] { ... }
var stringTree Tree[string]
Qui il tipo generico Tree memorizza il valore del parametro di tipo T. I tipi generici possono avere metodi, come Lookup in questo esempio. Per utilizzare tipi generici,Deve essere istanziato; Tree[string] è un esempio di istanziazione di Tree utilizzando il parametro type string.
Ogni parametro di tipo nell'elenco dei parametri di tipo ha un tipo. Poiché un parametro di tipo è esso stesso un tipo, il tipo del parametro di tipo definisce l'insieme di tipi.Questo metatipo si chiamavincoli di tipo。
Nel metodo generico GMin, i vincoli di tipo vengono importati dal pacchetto vincoli. I vincoli ordinati descrivono tutti i tipi di raccolte che hanno valori che possono essere ordinati o, in altre parole, confrontati con l'operatore < (o <=, >, ecc.). Questo vincolo garantisce che solo i tipi con valori ordinabili possano essere passati a GMin. Ciò significa anche che nel corpo della funzione GMin, il valore di questo parametro di tipo può essere utilizzato per il confronto con l'operatore <.
In Go, i vincoli di tipo devono essere interfacce . Cioè, i tipi di interfaccia possono essere utilizzati come tipi di valore o metatipi. Le interfacce definiscono i metodi, quindi ovviamente possiamo esprimere vincoli di tipo che richiedono la presenza di determinati metodi. Ma anche vincoli.Ordered è un tipo di interfaccia e l'operatore < non è un metodo.
Il duplice scopo dei tipi di interfaccia è infatti un concetto importante nel linguaggio Go. Cerchiamo di comprendere in modo approfondito e illustrare l'affermazione "i tipi di interfaccia possono essere utilizzati come tipi di valore e anche come metatipi" con esempi [1] [2] [3] [4] [5].
Quando un'interfaccia viene utilizzata come tipo di valore, definisce un insieme di metodi che qualsiasi tipo che implementa questi metodi può essere assegnato alla variabile di interfaccia. Questo è l'uso più comune delle interfacce.
Per esempio:
type Stringer interface {
String() string
}
type Person struct {
Name string
}
func (p Person) String() string {
return p.Name
}
var s Stringer = Person{"Alice"} // Person 实现了 Stringer 接口
fmt.Println(s.String()) // 输出: Alice
In questo esempio,Stringer
Le interfacce vengono utilizzate come tipi di valore,Person
Il tipo implementaString()
metodo, quindi può essere assegnato aStringer
tipo variabile.
Quando un'interfaccia viene utilizzata come metatipo, definisce un insieme di vincoli di tipo da utilizzare nella programmazione generica. Questo è un nuovo utilizzo dopo l'introduzione dei generici in Go 1.18.
Per esempio:
type Ordered interface {
int | float64 | string
}
func Min[T Ordered](a, b T) T {
if a < b {
return a
}
return b
}
fmt.Println(Min(3, 5)) // 输出: 3
fmt.Println(Min(3.14, 2.71)) // 输出: 2.71
fmt.Println(Min("a", "b")) // 输出: a
In questo esempio,Ordered
Le interfacce vengono utilizzate come metatipi, che definiscono un insieme di tipi (interi, numeri in virgola mobile e stringhe) che possono essere confrontati.Min
Le funzioni utilizzano questa interfaccia come vincolo di tipo e possono accettarne qualsiasiOrdered
Il tipo di vincolo come argomento.
Questo duplice scopo rende le interfacce di Go molto potenti e flessibili per la programmazione generica. Non solo possono definire il comportamento degli oggetti (come tipi di valore), ma anche definire raccolte di tipi (come metatipi), migliorando così notevolmente l'espressività e la riusabilità del codice pur mantenendo la semplicità del linguaggio.
Fino a poco tempo fa, la specifica Go affermava che un'interfaccia definisce un insieme di metodi, che è più o meno l'insieme dei metodi elencati nell'interfaccia. Qualsiasi tipo che implementa tutti questi metodi implementa questa interfaccia.
Ma un altro modo di vedere la cosa è dire che un'interfaccia definisce un insieme di tipi, cioè tipi che implementano questi metodi. Da questo punto di vista, qualsiasi tipo che sia un elemento di un set di tipi di interfaccia implementa tale interfaccia.
Entrambe le visualizzazioni portano allo stesso risultato: per ogni insieme di metodi possiamo immaginare il corrispondente insieme di tipi che implementano questi metodi, ovvero l'insieme di tipi definiti dall'interfaccia.
Per i nostri scopi, tuttavia, la visualizzazione del set di tipi presenta un vantaggio rispetto alla visualizzazione del set di metodi: possiamo controllare il set di tipi in nuovi modi aggiungendo esplicitamente tipi alla raccolta.
Per raggiungere questo obiettivo abbiamo esteso la sintassi dei tipi di interfaccia. Ad esempio, interfaccia{ int|string|bool } definisce un set di tipi contenente i tipi int, string e bool.
Un altro modo per dirlo è che l'interfaccia è soddisfatta solo da int, string o bool.
Ora diamo un'occhiata alla definizione effettiva di vincoli. Ordinato:
type Ordered interface {
Integer|Float|~string
}
Questa dichiarazione indica che l'interfaccia Ordered è una raccolta di tutti i tipi interi, a virgola mobile e stringa. Le barre verticali rappresentano unioni di tipi (o insiemi di tipi in questo caso). Integer e Float sono tipi di interfaccia definiti in modo simile nel pacchetto vincoli. Tieni presente che l'interfaccia Ordered non definisce alcun metodo.
Per i vincoli di tipo di solito non ci interessano tipi specifici, come le stringhe, siamo interessati a tutti i tipi di stringa;Questo è~
Lo scopo del token.espressione~string
Rappresenta una raccolta di tutti i tipi il cui tipo sottostante è stringa.Ciò include la stringa del tipo stessa e tutti i tipi dichiarati con definizioni, ad es.type MyString string
Ovviamente vogliamo ancora specificare i metodi nell'interfaccia e vogliamo essere compatibili con le versioni precedenti. In Go 1.18, un'interfaccia può contenere metodi e interfacce incorporate come prima, ma può anche incorporare tipi non di interfaccia, unioni e insiemi di tipi sottostanti.
L'interfaccia utilizzata come vincolo può essere denominata (ad esempio Ordered) o essere un'interfaccia letterale in linea nell'elenco dei parametri di tipo. Per esempio:
[S interface{~[]E}, E interface{}]
Qui S deve essere un tipo di sezione e il suo tipo di elemento può essere qualsiasi tipo.
Poiché questa è una situazione comune, per le interfacce che vincolano le posizioni, l'interfaccia chiusa{} può essere omessa e possiamo semplicemente scrivere (zucchero sintattico dei generici nel linguaggio Go e scrittura semplificata dei vincoli di tipo):
[S ~[]E, E interface{}]
Poiché le interfacce vuote sono comuni negli elenchi di parametri di tipo così come nel normale codice Go, Go 1.18 introduce un nuovo identificatore predichiarato any come alias per i tipi di interfaccia vuoti. Quindi, otteniamo questo codice idiomatico:
[S ~[]E, E any]
Con i parametri di tipo è necessario passare parametri di tipo, il che può portare a un codice dettagliato. Torniamo alla nostra funzione generale GMin: