Inhaltsverzeichnis
Was sind Generika?
Generika in go # 🎜🎜#go hat in Version 1.8 nur Generika eingeführt. Wenn Ihre Go-Version niedriger als 1.8 ist, können Sie keine Generika verwenden. Der Code in diesem Artikel verwendet Version 1.9. In Version 1.8 wurden viele Änderungen vorgenommen, um Generika zu unterstützen.
类型集合、接口
基础接口类型
接口组合
通用接口
类型推导
泛型的使用
内置容器类型
通用的结构体
通用的函数
总结
Heim Backend-Entwicklung Golang Vertieftes Verständnis von Generika in Golang (Generika)

Vertieftes Verständnis von Generika in Golang (Generika)

Apr 11, 2023 pm 07:20 PM
go 后端

Dieser Artikel vermittelt Ihnen ein tiefgreifendes Verständnis von Generika in Golang? Wie verwende ich Generika? Es hat einen gewissen Referenzwert. Freunde in Not können sich darauf beziehen. Ich hoffe, es wird Ihnen hilfreich sein.

Vertieftes Verständnis von Generika in Golang (Generika)

Was sind Generika?

Generika ist eine Programmiertechnologie. In einer stark typisierten Sprache ist es Ihnen gestattet, Code mit später angegebenen Typen zu schreiben und den entsprechenden Typ zum Zeitpunkt der Instanziierung anzugeben.

In Generics können Typparameter anstelle spezifischer Datentypen verwendet werden. Diese Typparameter können in einer Klasse, Methode oder Schnittstelle deklariert und in diesen Deklarationen verwendet werden. Code, der Generics verwendet, kann die tatsächlichen Typparameter zur Laufzeit angeben, wodurch der Code auf viele verschiedene Datentypen angewendet werden kann.

Generika können die Lesbarkeit, Wartbarkeit und Wiederverwendbarkeit von Code verbessern. Es reduziert die Redundanz Ihres Codes und bietet eine bessere Typsicherheit und Typprüfung zur Kompilierungszeit.

Lassen Sie uns anhand eines konkreten Beispiels erklären, warum Generika die Coderedundanz reduzieren können:

Stellen Sie eine Funktion bereit, die den Mindestwert von a, b zurückgibt, den wir benötigen Schreiben Sie eine Funktion für jeden spezifischen Datentyp „int, float...“ oder verwenden Sie interface{} „muss eine Typzusicherung für Parameter durchführen, was sich auf die Ausführungsleistung auswirkt und die übergebenen Parameter nicht einschränken kann“# 🎜🎜 #

func minInt(a, b int) int {
    if a > b {
        return b
    }
    return a
}

func minFloat(a, b float64) float64 {
    if a > b {
        return b
    }
    return a
}

func minItf(a, b interface{}) interface{} {
    switch a.(type) {
    case int:
        switch b.(type) {
        case int:
            if a.(int) > b.(int) {
                return b
            }
            return a
        }
    case float64:
        switch b.(type) {
        case float64:
            if a.(float64) > b.(float64) {
                return b
            }
            return a
        }
    }
    return nil
}
Nach dem Login kopieren
Anhand der obigen Methode können wir erkennen, dass minInt und minFloat bis auf die unterschiedlichen Parametertypen und Rückgabeergebnisse den gleichen Code haben. Gibt es eine Möglichkeit, den Typ zu bestimmen, der beim Aufruf der Funktion übergeben wird, ohne einen bestimmten Typ anzugeben? Hier wird ein Konzept namens Generics eingeführt, das einfach als breiter Typ oder nicht spezifizierter spezifischer Typ verstanden werden kann. Durch die Einführung von Generika müssen wir keine spezifischen Datentypen mehr angeben. Die Min-Funktion kann wie folgt verwendet werden:

// T 为类型参数, 在调用时确定参数的具体值, 可以为 int, 也可以为 float64;它与 a, b 一样也是参数, 需要调用时传入具体的值;不同的是,T 为类型参数,值为具体的类型, a,b 为函数参数,值为具体类型对应的值
func minIntAndFloat64[T int | float64](a, b T) T { 
    if a < b {
        return a
    }
    return b
}

minIntAndFloat64[int](1, 2) // 实例化/调用时指定具体的类型
Nach dem Login kopieren

Generika in go # 🎜🎜#go hat in Version 1.8 nur Generika eingeführt. Wenn Ihre Go-Version niedriger als 1.8 ist, können Sie keine Generika verwenden. Der Code in diesem Artikel verwendet Version 1.9. In Version 1.8 wurden viele Änderungen vorgenommen, um Generika zu unterstützen.

Typparameter in Funktions- und Typdeklarationen eingeführt
  • Typsammlungen können über Schnittstellen definiert werden, einschließlich Typen ohne Methoden
  • #🎜🎜 #Typ Ableitung. In einigen Szenarien können Sie den Wert des Typparameters nicht angeben, wenn Sie die Funktion aufrufen Parameter, Typargumente, Instanziierung
  • Schauen wir uns zunächst eine normale add-Funktion an. add ist der Funktionsname, x, y ist der formale Parameter und (x, y int) ist die Parameterliste. Wenn ein Funktionsaufruf erfolgt, sind add(2, 3) 2, 3 tatsächliche Parameter.
Vertieftes Verständnis von Generika in Golang (Generika)In Analogie zu Generika benötigen wir einen Typparameter. Wenn ein Funktionsaufruf erfolgt, wird der entsprechende tatsächliche Typparameter übergeben. Eine Funktion mit Typparametern wird als generische Funktion bezeichnet. [T int |. int64] ist eine Typparameterliste, T ist ein Typparameter und int64 ist eine Typsammlung/Typeinschränkung . Wenn ein Funktionsaufruf add[int](2,3) erfolgt, ist int der Typ-Aktualparameter. Dieser Aufruf wird auch als Instanziierung bezeichnet, das heißt, der Typ-Aktualparameter wird bestimmt.

Vertieftes Verständnis von Generika in Golang (Generika)

Bei der Deklaration der Struktur können Sie auch Typparameter angeben. MyStruct[T] ist eine generische Struktur, die Methoden für generische Strukturen definieren kann.

add 函数。add 为函数名, x, y 为形参, (x,y int)为参数列表。发生函数调用时, add(2, 3) 2, 3 为实参。

Vertieftes Verständnis von Generika in Golang (Generika)类比到泛型中, 我们需要一个类型参数, 当发生函数调用时传入对应的类型实参, 带有类型参数的函数叫做泛型函数。[T int | int64] 为类型参数列表, T 为类型参数, int | int64 为类型集合/类型约束。当发生函数调用时 add[int](2,3),int 即为类型实参, 这一调用我们也叫做实例化, 即确定类型实参。

Vertieftes Verständnis von Generika in Golang (Generika)

在结构体声明时, 也可以指定类型参数。MyStruct[T] 是一个泛型结构体, 可以为泛型结构体定义方法。

Vertieftes Verständnis von Generika in Golang (Generika)

类型集合、接口

在基础类型中, uint8 表示 0~255 的集合。那么对于类型参数, 也需要像基础类型一样, 定义类型的集合。在上面的例子中 int | string就是类型的集合。那么如何对类型的集合进行复用呢?这里就使用了接口来进行定义。下面就是一个类型集合的定义。因此, 我们可以定义一个泛型函数 add[T Signed](x, y T) T

Vertieftes Verständnis von Generika in Golang (Generika)

在 go 1.8 之前, 接口的定义是方法的集合, 即实现了接口对应的方法, 就可以转换为对应的接口。在下面的例子中, MyInt 类型实现了 Add 方法, 因此可以转换为 MyInterface

type MyInterface interface {
    Add(x, y int) int
}

type MyInt int

func (mi myInt) Add(x, y int) int {
    return x + y
}

func main() {
    var mi MyInterface = myInt(1)
    fmt.Println(mi.Add(1, 2))
}
Nach dem Login kopieren

如果我们换个角度来思考一下, MyInterface 可以看作一个类型集合, 即包含了所有实现 addVertieftes Verständnis von Generika in Golang (Generika)

#🎜🎜#Typsammlung, Schnittstelle#🎜🎜##🎜🎜##🎜🎜#Im Basistyp repräsentiert uint8 0~255 Gather . Dann müssen Sie für Typparameter auch eine Sammlung von Typen definieren, genau wie den Basistyp. Im obigen Beispiel ist int | eine Sammlung von Typen. Wie kann man also eine Sammlung von Typen wiederverwenden? Zur Definition werden hier Schnittstellen verwendet. Im Folgenden finden Sie die Definition einer Typsammlung. Daher können wir eine generische Funktion definieren add[T Signed](x, y T) T#🎜🎜##🎜🎜#Vertieftes Verständnis von Generika in Golang (Generika)#🎜🎜##🎜🎜#Vor Go 1.8 wurde die Schnittstelle als Methode A definiert Die Sammlung, die die der Schnittstelle entsprechende Methode implementiert, kann in die entsprechende Schnittstelle konvertiert werden. Im folgenden Beispiel implementiert der Typ MyInt die Add-Methode und kann daher in MyInterface konvertiert werden. #🎜🎜#
func I[T MyInterface](x, y int, i T) int {
    return i.Add(x, y)
}
Nach dem Login kopieren
Nach dem Login kopieren
#🎜🎜#Wenn wir es aus einem anderen Blickwinkel betrachten, kann MyInterface als eine Typsammlung betrachtet werden, die alle Typen umfasst, die die Methode add implementieren . Dann kann MyInterface als Sammlung von Typen verwendet werden. Beispielsweise können wir eine generische Funktion wie folgt definieren. #🎜🎜#
func I[T MyInterface](x, y int, i T) int {
    return i.Add(x, y)
}
Nach dem Login kopieren
Nach dem Login kopieren

在泛型中, 我们的类型集合不仅仅是实现接口中定义方法的类型, 还需要包含基础的类型。因此, 我们可以对接口的定义进行延伸, 使其支持基础类型。为了保证向前兼容, 我们需要对接口类型进行分类:

基础接口类型

只包含方法的集合, 既可以当作类型集合, 又可以作为数据类型进行声明。如下面的 MyInterface。还有一个特殊的接口类型 interface{}, 它可以用来表示任意类型, 即所有的类型都实现了它的空方法。在 1.8 之后可以使用 any 进行声明。

type any = interface{}

type MyInterface interface {
    Add(x, y int) int
    String() string
    String() string  // 非法: String 不能重复声明
    _(x int)         // 非法: 必须要有一个非空的名字
}
Nach dem Login kopieren

接口组合

可以通过接口组合的形式声明新的接口, 从而尽可能的复用接口。从下面的例子可以看出, ReadWriterReaderWrite 的类型集合的交集。

type Reader interface {
        Read(p []byte) (n int, err error)
        Close() error
}

type Writer interface {
        Write(p []byte) (n int, err error)
        Close() error
}

// ReadWriter&#39;s methods are Read, Write, and Close.
type ReadWriter interface {
        Reader  // includes methods of Reader in ReadWriter&#39;s method set
        Writer  // includes methods of Writer in ReadWriter&#39;s method set
}
Nach dem Login kopieren

通用接口

上面说的接口都必须要实现具体的方法, 但是类型集合中无法包含基础的数据类型。如: int, float, string...。通过下面的定义, 可以用来表示包含基础数据类型的类型集合。在 golang.org/x/exp/constraints 中定义了基础数据类型的集合。我们可以看到 符号, 它表示包含潜在类型为 int | int8 | int16 | int32 | int64 的类型, | 表示取并集。Singed 就表示所有类型为 int 的类型集合。

// Signed is a constraint that permits any signed integer type.
// If future releases of Go add new predeclared signed integer types,
// this constraint will be modified to include them.
type Signed interface {
     ~int | ~int8 | ~int16 | ~int32 | ~int64
}

type myInt int // 潜在类型为 int

func add[T constraints.Integer](x, y T) T {
        return x + y
}

func main() {
        var x, y myInt = 1, 2
        fmt.Println(add[myInt](x, y))
}
Nach dem Login kopieren

下面来看一些特殊的定义

// 潜在类型为 int, 并且实现了 String 方法的类型
type E interface {
    ~int
    String() string
}

type mInt int // 属于 E 的类型集合
func (m mInt) String() string {
    return fmt.Sprintf("%v", m)
}

// 潜在类型必须是自己真实的类型
type F interface {
    ~int
    // ~mInt  invalid use of ~ (underlying type of mInt is int)
    // ~error illegal: error is an interface
}

// 基础接口可以作为形参和类型参数类型, 通用类型只能作为类型参数类型, E 只能出现在类型参数中 [T E]
var x E                    // illegal: cannot use type E outside a type constraint: interface contains type constraints
var x interface{} = E(nil) // illegal: cannot use interface E in conversion (contains specific type constraints or is comparable)
Nach dem Login kopieren

类型推导

由于泛型使用了类型参数, 因此在实例化泛型时我们需要指定类型实参。 看下面的 case, 我们在调用函数的时候并没有指定类型实参, 这里是编译器进行了类型推导, 推导出类型实参, 不需要显性的传入。

func add[T constraints.Integer](x, y T) T {
    return x + y
}

func main() {
    fmt.Println(add(1, 1)) // add[int](1,1)
}
Nach dem Login kopieren

有时候, 编译器无法推导出具体类型。则需要指定类型, 或者更换写法, 也许可以推断出具体类型。

// 将切片中的值扩大
func Scale[E constraints.Integer](s []E, c E) []E {
    r := make([]E, len(s))
    for i, v := range s {
        r[i] = v * c
    }
    return r
}

func ScaleAndPrint(p Point) {
    r := Scale(p, 2)
    r.string() // 非法, Scale 返回的是 []int32
}

type Point []int32

func (p Point) string() {
    fmt.Println(p)
}

// 方法更新,这样传入的是 Point 返回的也是 Point
func Scale[T ~[]E, E constraints.Integer](s T, c E) T {
    r := make([]E, len(s))
    for i, v := range s {
        r[i] = v * c
    }
    return r
}
Nach dem Login kopieren

泛型的使用

go 是在 1.8 版本中开始引入泛型的。下面主要介绍一下什么时候使用泛型:

内置容器类型

在 go 中, 提供以下容器类型:map, slice, channel。当我们用到容器类型时, 且逻辑与容器具体的类型无关, 这个时候可以考虑泛型。这样我们可以在调用时指定具体的类型实参, 从而避免了类型断言。例如,下面的例子, 返回 map 中的 key。

// comparable 是一个内置类型, 只能用于对类型参数的约束。在 map 中, key 必须是可比较类型。
func GetKeys[K comparable, V any](m map[K]V) []K {
    res := make([]K, 0, len(m))
    for k := range m {
        res = append(res, k)
    }
    return res
}
Nach dem Login kopieren

通用的结构体

对于一些通用的结构体, 我们应该使用泛型。例如, 栈、队列、树结构。这些都是比较通用的结构体, 且逻辑都与具体的类型无关, 因此需要使用泛型。下面是一个栈的例子:

type Stack[T any] []T

func (s *Stack[T]) Push(item T) {
    *s = append(*s, item)
}

func (s *Stack[T]) Pop() T {
    if len(*s) == 0 {
        panic("can not pop item in emply stack")
    }
    lastIndex := len(*s) - 1
    item := (*s)[lastIndex]
    *s = (*s)[:lastIndex]
    return item
}

func main() {
    var s Stack[int]
    s.Push(9)
    fmt.Println(s.Pop())
    s.Push(9)
    s.Push(8)
    fmt.Println(s.Pop(), s.Pop())
}
Nach dem Login kopieren

通用的函数

有些类型会实现相同的方法, 但是对于这些类型的处理逻辑又与具体类型的实现无关。例如: 两个数比大小, 只要实现 Ordered 接口即可进行大小比较:

func Min[T constraints.Ordered](x, y T) T {
    if x < y {
        return x
    }

    return y
}

func main() {
    fmt.Println(Min(5, 6))
    fmt.Println(Min(6.6, 9.9))
}
Nach dem Login kopieren

总结

go 在引入泛型算是一次较大的改动。我们只有弄清楚类型参数、类型约束、类型集合、基础接口、通用接口、泛型函数、泛型类型、泛型接口等概念, 才能不会困惑。核心改动点还是引入了类型参数, 使用接口来定义类型集合。

当然,也不能为了使用泛型而使用泛型。还是要具体的 case 具体来分析。 简单的指导原则就是, 当你发现你的代码除了类型不同外, 其余代码逻辑都相同; 或者你写了许多重复代码, 仅仅是为了支持不同类型; 那么你可以考虑使用泛型。

推荐学习:Golang教程

Das obige ist der detaillierte Inhalt vonVertieftes Verständnis von Generika in Golang (Generika). Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn

Heiße KI -Werkzeuge

Undresser.AI Undress

Undresser.AI Undress

KI-gestützte App zum Erstellen realistischer Aktfotos

AI Clothes Remover

AI Clothes Remover

Online-KI-Tool zum Entfernen von Kleidung aus Fotos.

Undress AI Tool

Undress AI Tool

Ausziehbilder kostenlos

Clothoff.io

Clothoff.io

KI-Kleiderentferner

AI Hentai Generator

AI Hentai Generator

Erstellen Sie kostenlos Ai Hentai.

Heißer Artikel

R.E.P.O. Energiekristalle erklärten und was sie tun (gelber Kristall)
4 Wochen vor By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. Beste grafische Einstellungen
4 Wochen vor By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. So reparieren Sie Audio, wenn Sie niemanden hören können
4 Wochen vor By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. Chat -Befehle und wie man sie benutzt
4 Wochen vor By 尊渡假赌尊渡假赌尊渡假赌

Heiße Werkzeuge

Notepad++7.3.1

Notepad++7.3.1

Einfach zu bedienender und kostenloser Code-Editor

SublimeText3 chinesische Version

SublimeText3 chinesische Version

Chinesische Version, sehr einfach zu bedienen

Senden Sie Studio 13.0.1

Senden Sie Studio 13.0.1

Leistungsstarke integrierte PHP-Entwicklungsumgebung

Dreamweaver CS6

Dreamweaver CS6

Visuelle Webentwicklungstools

SublimeText3 Mac-Version

SublimeText3 Mac-Version

Codebearbeitungssoftware auf Gottesniveau (SublimeText3)

Vertiefendes Verständnis des Golang-Funktionslebenszyklus und des Variablenumfangs Vertiefendes Verständnis des Golang-Funktionslebenszyklus und des Variablenumfangs Apr 19, 2024 am 11:42 AM

In Go umfasst der Funktionslebenszyklus Definition, Laden, Verknüpfen, Initialisieren, Aufrufen und Zurückgeben; der Variablenbereich ist in Funktionsebene und Blockebene unterteilt. Variablen innerhalb einer Funktion sind intern sichtbar, während Variablen innerhalb eines Blocks nur innerhalb des Blocks sichtbar sind .

Wie kann ich Zeitstempel mithilfe regulärer Ausdrücke in Go abgleichen? Wie kann ich Zeitstempel mithilfe regulärer Ausdrücke in Go abgleichen? Jun 02, 2024 am 09:00 AM

In Go können Sie reguläre Ausdrücke verwenden, um Zeitstempel abzugleichen: Kompilieren Sie eine Zeichenfolge mit regulären Ausdrücken, z. B. die, die zum Abgleich von ISO8601-Zeitstempeln verwendet wird: ^\d{4}-\d{2}-\d{2}T \d{ 2}:\d{2}:\d{2}(\.\d+)?(Z|[+-][0-9]{2}:[0-9]{2})$ . Verwenden Sie die Funktion regexp.MatchString, um zu überprüfen, ob eine Zeichenfolge mit einem regulären Ausdruck übereinstimmt.

Wie sende ich Go WebSocket-Nachrichten? Wie sende ich Go WebSocket-Nachrichten? Jun 03, 2024 pm 04:53 PM

In Go können WebSocket-Nachrichten mit dem Paket gorilla/websocket gesendet werden. Konkrete Schritte: Stellen Sie eine WebSocket-Verbindung her. Senden Sie eine Textnachricht: Rufen Sie WriteMessage(websocket.TextMessage,[]byte("message")) auf. Senden Sie eine binäre Nachricht: Rufen Sie WriteMessage(websocket.BinaryMessage,[]byte{1,2,3}) auf.

Der Unterschied zwischen Golang und Go-Sprache Der Unterschied zwischen Golang und Go-Sprache May 31, 2024 pm 08:10 PM

Go und die Go-Sprache sind unterschiedliche Einheiten mit unterschiedlichen Eigenschaften. Go (auch bekannt als Golang) ist bekannt für seine Parallelität, schnelle Kompilierungsgeschwindigkeit, Speicherverwaltung und plattformübergreifende Vorteile. Zu den Nachteilen der Go-Sprache gehören ein weniger umfangreiches Ökosystem als andere Sprachen, eine strengere Syntax und das Fehlen dynamischer Typisierung.

Wie vermeidet man Speicherlecks bei der technischen Leistungsoptimierung von Golang? Wie vermeidet man Speicherlecks bei der technischen Leistungsoptimierung von Golang? Jun 04, 2024 pm 12:27 PM

Speicherlecks können dazu führen, dass der Speicher des Go-Programms kontinuierlich zunimmt, indem: Ressourcen geschlossen werden, die nicht mehr verwendet werden, wie z. B. Dateien, Netzwerkverbindungen und Datenbankverbindungen. Verwenden Sie schwache Referenzen, um Speicherlecks zu verhindern, und zielen Sie auf Objekte für die Garbage Collection ab, wenn sie nicht mehr stark referenziert sind. Bei Verwendung von Go-Coroutine wird der Speicher des Coroutine-Stapels beim Beenden automatisch freigegeben, um Speicherverluste zu vermeiden.

Wie kann ich die Golang-Funktionsdokumentation in der IDE anzeigen? Wie kann ich die Golang-Funktionsdokumentation in der IDE anzeigen? Apr 18, 2024 pm 03:06 PM

Go-Funktionsdokumentation mit der IDE anzeigen: Bewegen Sie den Cursor über den Funktionsnamen. Drücken Sie den Hotkey (GoLand: Strg+Q; VSCode: Nach der Installation von GoExtensionPack F1 und wählen Sie „Go:ShowDocumentation“).

Eine Anleitung zum Unit-Testen gleichzeitiger Go-Funktionen Eine Anleitung zum Unit-Testen gleichzeitiger Go-Funktionen May 03, 2024 am 10:54 AM

Das Testen gleichzeitiger Funktionen in Einheiten ist von entscheidender Bedeutung, da dies dazu beiträgt, ihr korrektes Verhalten in einer gleichzeitigen Umgebung sicherzustellen. Beim Testen gleichzeitiger Funktionen müssen grundlegende Prinzipien wie gegenseitiger Ausschluss, Synchronisation und Isolation berücksichtigt werden. Gleichzeitige Funktionen können Unit-Tests unterzogen werden, indem Rennbedingungen simuliert, getestet und Ergebnisse überprüft werden.

Wie verwende ich den Fehler-Wrapper von Golang? Wie verwende ich den Fehler-Wrapper von Golang? Jun 03, 2024 pm 04:08 PM

In Golang können Sie mit Fehler-Wrappern neue Fehler erstellen, indem Sie Kontextinformationen an den ursprünglichen Fehler anhängen. Dies kann verwendet werden, um die von verschiedenen Bibliotheken oder Komponenten ausgelösten Fehlertypen zu vereinheitlichen und so das Debuggen und die Fehlerbehandlung zu vereinfachen. Die Schritte lauten wie folgt: Verwenden Sie die Funktion „errors.Wrap“, um die ursprünglichen Fehler in neue Fehler umzuwandeln. Der neue Fehler enthält Kontextinformationen zum ursprünglichen Fehler. Verwenden Sie fmt.Printf, um umschlossene Fehler auszugeben und so mehr Kontext und Umsetzbarkeit bereitzustellen. Wenn Sie verschiedene Fehlertypen behandeln, verwenden Sie die Funktion „errors.Wrap“, um die Fehlertypen zu vereinheitlichen.

See all articles