Mi informacion de contacto
Correo[email protected]
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
Si queremos escribir una función para comparar los tamaños de dos números enteros y números de coma flotante respectivamente, tenemos que escribir dos funciones. como sigue:
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
}
Las dos funciones tienen exactamente la misma lógica de procesamiento excepto por los diferentes tipos de datos. ¿Hay alguna forma de realizar la función anterior con una sola función? Sí, eso es genérico.
func min[T int | float64](x, y T) T {
if x < y {
return x
}
return y
}
Documento del sitio web oficial:https://go.dev/blog/intro-generics
Los genéricos añaden tres nuevas características importantes al lenguaje:
Ahora se permite que las funciones y los tipos tengan parámetros de tipo. Una lista de parámetros de tipo es similar a una lista de parámetros normal, excepto que utiliza corchetes en lugar de corchetes.
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
}
Entre ellos, constraints.Ordered es un tipo personalizado (el código fuente no se muestra aquí).
Si no lo comprende, puede reemplazar temporalmente las restricciones. Ordenado con ·int | float64
。
Proporcionar un parámetro de tipo (int en este caso) a un GMin se llama creación de instancias. La creación de instancias se produce en dos pasos.
Después de una creación de instancias exitosa, tenemos una función no genérica que se puede llamar como cualquier otra función.Por ejemplo, en código como
fmin := GMin[float64]
m := fmin(2.71, 3.14)
Todos los códigos son
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
}
La instanciación de GMin[float64] produce en realidad nuestra función Min de punto flotante original, que podemos usar en llamadas a funciones.
Los parámetros de tipo también se pueden utilizar con tipos.
type Tree[T interface{}] struct {
left, right *Tree[T]
value T
}
func (t *Tree[T]) Lookup(x T) *Tree[T] { ... }
var stringTree Tree[string]
Aquí el tipo genérico Árbol almacena el valor del parámetro de tipo T. Los tipos genéricos pueden tener métodos, como Lookup en este ejemplo. Para utilizar tipos genéricos,Debe ser instanciado; Tree[string] es un ejemplo de creación de instancias de Tree utilizando la cadena de parámetro de tipo.
Cada parámetro de tipo en la lista de parámetros de tipo tiene un tipo. Dado que un parámetro de tipo es en sí mismo un tipo, el tipo del parámetro de tipo define el conjunto de tipos.Este metatipo se llamarestricciones de tipo。
En el método genérico GMin, las restricciones de tipo se importan del paquete de restricciones. Las restricciones ordenadas describen todos los tipos de colecciones que tienen valores que se pueden ordenar o, en otras palabras, comparar con el operador < (o <=, >, etc.). Esta restricción garantiza que solo se puedan pasar a GMin tipos con valores ordenables. Esto también significa que en el cuerpo de la función GMin, el valor de este parámetro de tipo se puede utilizar para comparar con el operador <.
En Go, las restricciones de tipo deben ser interfaces. . Es decir, los tipos de interfaz se pueden utilizar como tipos de valor o metatipos. Las interfaces definen métodos, por lo que obviamente podemos expresar restricciones de tipo que requieren la presencia de ciertos métodos. Pero constraints.Ordered también es un tipo de interfaz y el operador < no es un método.
El doble propósito de los tipos de interfaz es de hecho un concepto importante en el lenguaje Go. Comprendamos en profundidad e ilustremos la afirmación "los tipos de interfaz se pueden utilizar como tipos de valor y también como metatipos" con ejemplos [1][2][3][4][5].
Cuando una interfaz se utiliza como tipo de valor, define un conjunto de métodos que cualquier tipo que implemente estos métodos puede asignar a la variable de interfaz. Este es el uso más común de interfaces.
Por ejemplo:
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
En este ejemplo,Stringer
Las interfaces se utilizan como tipos de valores,Person
El tipo implementaString()
método, por lo que se puede asignar aStringer
variable de tipo.
Cuando una interfaz se utiliza como metatipo, define un conjunto de restricciones de tipo para su uso en programación genérica. Este es un nuevo uso después de la introducción de genéricos en Go 1.18.
Por ejemplo:
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
En este ejemplo,Ordered
Las interfaces se utilizan como metatipos, que definen un conjunto de tipos (enteros, números de punto flotante y cadenas) que se pueden comparar.Min
Las funciones utilizan esta interfaz como restricción de tipo y pueden aceptar cualquierOrdered
El tipo de restricción como argumento.
Este doble propósito hace que las interfaces de Go sean muy potentes y flexibles para la programación genérica. No sólo pueden definir el comportamiento de objetos (como tipos de valor), sino también definir colecciones de tipos (como metatipos), mejorando así en gran medida la expresividad y reutilización del código manteniendo la simplicidad del lenguaje.
Hasta hace poco, la especificación Go decía que una interfaz define un conjunto de métodos, que es aproximadamente el conjunto de métodos enumerados en la interfaz. Cualquier tipo que implemente todos estos métodos implementa esta interfaz.
Pero otra forma de ver esto es decir que una interfaz define un conjunto de tipos, es decir, tipos que implementan estos métodos. Desde esta perspectiva, cualquier tipo que sea un elemento de un conjunto de tipos de interfaz implementa esa interfaz.
Ambas vistas conducen al mismo resultado: para cada conjunto de métodos, podemos imaginar el conjunto correspondiente de tipos que implementan estos métodos, es decir, el conjunto de tipos definidos por la interfaz.
Sin embargo, para nuestros propósitos, la vista de conjunto de tipos tiene una ventaja sobre la vista de conjunto de métodos: podemos controlar el conjunto de tipos de nuevas maneras agregando explícitamente tipos a la colección.
Ampliamos la sintaxis de los tipos de interfaz para lograr esto. Por ejemplo, interface{ int|string|bool } define un conjunto de tipos que contiene los tipos int, string y bool.
Otra forma de decir esto es que la interfaz se satisface sólo con int, string o bool.
Ahora veamos la definición real de restricciones. Ordenado:
type Ordered interface {
Integer|Float|~string
}
Esta declaración indica que la interfaz Ordenada es una colección de todos los tipos de enteros, coma flotante y cadenas. Las barras verticales representan uniones de tipos (o conjuntos de tipos en este caso). Integer y Float son tipos de interfaz definidos de manera similar en el paquete de restricciones. Tenga en cuenta que la interfaz Ordenada no define ningún método.
Para las restricciones de tipo normalmente no nos preocupamos por tipos específicos, como cadenas; nos interesan todos los tipos de cadenas;Esto es~
El propósito del token.expresión~string
Representa una colección de todos los tipos cuyo tipo subyacente es una cadena.Esto incluye la cadena de tipo en sí y todos los tipos declarados con definiciones, p.type MyString string
Por supuesto, todavía queremos especificar métodos en la interfaz y queremos ser compatibles con versiones anteriores. En Go 1.18, una interfaz puede contener métodos e interfaces integradas como antes, pero también puede incorporar tipos, uniones y conjuntos de tipos subyacentes que no sean de interfaz.
La interfaz utilizada como restricción puede tener un nombre (como Ordenada) o ser una interfaz literal en línea en la lista de parámetros de tipo. Por ejemplo:
[S interface{~[]E}, E interface{}]
Aquí S debe ser un tipo de sector y su tipo de elemento puede ser de cualquier tipo.
Debido a que esta es una situación común, para interfaces que restringen posiciones, se puede omitir la interfaz cerrada{} y podemos simplemente escribir (sintaxis de azúcar de genéricos en lenguaje Go y escritura simplificada de restricciones de tipo):
[S ~[]E, E interface{}]
Dado que las interfaces vacías son comunes en las listas de parámetros de tipo así como en el código Go normal, Go 1.18 introduce un nuevo identificador predeclarado any como alias para los tipos de interfaz vacías. Así, obtenemos este código idiomático:
[S ~[]E, E any]
Con los parámetros de tipo, es necesario pasar parámetros de tipo, lo que puede generar un código detallado. Volvamos a nuestra función GMin general: