欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

一文帶你了解Golang中的泛型

 更新時間:2023年07月28日 10:07:13   作者:Ted劉  
泛型是一種可以編寫?yīng)毩⒂谑褂玫奶囟愋偷拇a的方法,可以通過編寫函數(shù)或類型來使用一組類型中的任何一個,下面就來和大家聊聊Golang中泛型的使用吧

自從2022年 Golang 1.18 發(fā)布至今已有一年多了,在1.18版本中增加了非常重磅的一個功能,那就是泛型!Golang官方也對泛型格外重視:
“Generics are the biggest change we’ve made to Go since the first open source release”(泛型是自第一個開源版本以來我們對 Go 所做的最大改變)

然而由于平時工作項目所用Go版本較為古老,一直沒有大范圍應(yīng)用泛型這個特性,雖然之前剛發(fā)布時就了解學(xué)習過,但是也有些模糊了,這里還是希望能夠記錄一下Golang泛型,以備后續(xù)參考,我講基于Golang官方文檔、博客、youtube演講等來進行學(xué)習。

什么是泛型

泛型是一種可以編寫?yīng)毩⒂谑褂玫奶囟愋偷拇a的方法,可以通過編寫函數(shù)或類型來使用一組類型中的任何一個。泛型為Golang增添了三個重要功能:

  • 函數(shù)和類型的類型參數(shù)
  • 將接口類型定義為類型集,包括沒有方法的類型。也就是我們可以定義類型集和方法集
  • 類型推斷,允許函數(shù)調(diào)用時省略類型參數(shù)

我們從類型參數(shù)開始逐步了解

類型參數(shù) Type parameters

類型參數(shù)讓我們可以參數(shù)化函數(shù)或者具有類型的類型,與普通的參數(shù)列表類似,類型參數(shù)使用方括號來表示

函數(shù)中使用類型參數(shù)

這里有一個常見的取最小值函數(shù),我們經(jīng)常會在代碼中寫(新版的Golang官方庫已經(jīng)支持了max以及min):

func Min(x, y float64) float64 {
    if x < y {
        return x
    }
    return y
}

我們可以通過類型參數(shù)來替換 float64 類型來使這個函數(shù)更加通用,讓這個函數(shù)不僅僅適用于 float64 類型,可以這樣來做:

import "golang.org/x/exp/constraints"
func Min[T constraints.Ordered](x, y T) T {
    if x < y {
        return x
    }
    return y
}
m := Min[int](2, 3)

在這里我們使用了類型參數(shù) T 來替換 float64 類型使得函數(shù)通用,由于 T 是一個新的類型,所以我們要在 [] 中聲明它。

函數(shù)定義好后,與普通的函數(shù)調(diào)用類似,我們需要傳入函數(shù)的實參以及創(chuàng)建一個接收值來接受函數(shù)實際返回的結(jié)果,不同的是,我們需要在 [] 傳入具體的類型值,向函數(shù)提供類型參數(shù) int 成為實例化。

實例化將會分兩步進行:

  • 編譯器將整個泛型函數(shù)或類型中的所有類型實參進行替換
  • 編譯器驗證每個類型參數(shù)是否滿足了各自的約束

如果編譯器在第二步執(zhí)行失敗,實例化就會失敗且程序會fail。

我們也可以直接傳入類型參數(shù)來實例化函數(shù),而不需要傳入具體實參進行實際調(diào)用,實例化過后我們就可以像普通函數(shù)調(diào)用一樣來調(diào)用這個實例化過后的函數(shù)了:

fmin := Min[float64]
m := fmin(2.71, 3.14)

類型中使用類型參數(shù)

前面是一個函數(shù)中使用類型參數(shù)的例子,還有一個在類型中使用類型參數(shù)的例子:

type Tree[T interface{}] struct {
    left, right *Tree[T]
    value       T
}
func (t *Tree[T]) Lookup(x T) *Tree[T] { ... }
var stringTree Tree[string]

這是一個通用的二叉樹類型定義,我們再次采用了類型參數(shù) T 來作為一個通用性的數(shù)據(jù)類型,這里定義了類型 Tree[T] 同時定義了它所具有的方法 Lookup(x T) *Tree[T]

最后一行通過 var 對變量 stringTree 做了一次實例化,傳入?yún)?shù)類型為 string

類型集

func min(x,y float64)float64
func Gmin[T constraints.Ordered](x, y T) T

普通函數(shù) min() 每個參數(shù)值都有一個類型,例如min函數(shù)中,限定了 x,y 及 返回值只有在 float64 類型時才有效;而函數(shù) Gmin() 類型參數(shù)列表中每個類型參數(shù)都有一個類型,由于類型參數(shù)本身就是一種類型,因此類型參數(shù)的類型定義了類型集,這種元類型告訴了我們那些類型對該參數(shù)類型有效,因此這個元類型實際定義了類型集,我們可以稱之為類型約束。

Gmin() 中,類型越是是從約束包中導(dǎo)入的,這個包也是 Golang 標準庫中新增的包。這個Ordered約束描述了具有可排序值的所有類型的集合,或者換句話說,約束了能夠使用 < 運算符(或 <= 、 > 等)進行比較的類型范圍。所以只有具有可排序值的類型才能傳遞給GMin,在GMin函數(shù)體中,該類型參數(shù)的值可以用于與 < 等運算符進行比較。

接口類型定義為類型集 Type sets define by interface

在Golang中的類型約束必須是接口,接口類型可以用作值類型,也可以用作元類型。接口定義了方法,所以我們可以選擇需要存在某些方法的類型約束。但是在例子中的constraints.Ordered也是一個接口類型,并且 < 運算符也并不是一個方法,那它是如何工作的呢?

在Golang的規(guī)范中,如果我們有一個接口定義了一組方法,帶有方法 a b c,那我們就可以說接口定義了一個帶有方法 a b c 的方法集,實現(xiàn)了這些方法的每個類型也就實現(xiàn)了該接口。如圖所示,類型 P Q R 都實現(xiàn)了接口

我們可以換個角度來看待這個規(guī)范,那就是接口定義了一組類型,這些類型都要具有接口中的方法。從這個角度來看,每個接口方法集都可以延伸出無限的類型,作為接口類型集元素的任何類型都實現(xiàn)該接口。要檢查某一類型是否實現(xiàn)了接口,僅需檢查該類型是否是該類型集的元素。

通過視角的轉(zhuǎn)換,就我們的目的而言,類型集視圖比方法集視圖有一個優(yōu)勢:我們可以顯式地將類型添加到集合中,從而以新的方式控制類型集。

Golang通過對接口類型語法的擴展來實現(xiàn)這一點。在下面的示例中,我們有一個具有int\string\bool三種類型的接口示例,并且該接口定義了這三種類型的集合

實際聲明的constraints.Ordered

package constraints
type Ordered interface {
    Integer|Float|~string
}

這表明,Ordered接口是所有 int\float\string 類型的集合,豎線"|"表示這些類型的聯(lián)合(類型集)。Ordered接口沒有定義任何方法。"~"表示我們接受任意的基礎(chǔ)類型為string的類型集,包括類型string本身以及使用定義聲明的所有類型,例如type MyString string

我們?nèi)绻朐诮涌谥幸?guī)定方法,那么仍然是向后兼容的,在1.18版本后,接口可以像之前一樣包含方法或嵌入接口,同時我們也可以嵌入非接口類型、聯(lián)合和底層類型集。

用作約束的接口可以指定名稱(例如Order),也可以是內(nèi)聯(lián)在類型參數(shù)列表中的接口,例如:

[S interface{~[]E}, E interface{}]

這里類型參數(shù)列表中有兩個類型參數(shù),分別是 S 和 E,S定義的是一個切片類型的類型參數(shù),其元素類型可以是任何類型,由于 interface{} 在約束時可以省略封裝,因此可以簡單寫為:

[S ~[]E, E interface{}]
// Go1.18中引入的any即為interface{}: interface{} => any
[S ~[]E, E any]

作為類型集的接口是一種新機制,是Go中類型約束的關(guān)鍵。

類型推斷 type inference

函數(shù)參數(shù)類型推斷

有了類型參數(shù),就需要傳遞類型參數(shù),這可能會導(dǎo)致代碼冗長

func GMin[T constraints.Ordered](x, y T) T { ... }
var a, b, m float64
m = GMin[float64](a, b) // explicit type argument

這里我們指定了實例化的參數(shù)類型 float64,實際上,編譯器可以從實參推斷出類型參數(shù),這樣可以使代碼更為簡潔清晰:

var a, b, m float64
m = GMin(a, b) // no type argument

這種從函數(shù)參數(shù)的類型推斷出參數(shù)類型的推斷稱為函數(shù)參數(shù)類型推斷。如果推斷成功,函數(shù)就和普通函數(shù)一樣正常使用,如果不成功,編譯器會報錯,我們還是需要指定類型。

函數(shù)參數(shù)類型推斷僅適用于在函數(shù)參數(shù)中使用的類型參數(shù),不適用于在函數(shù)結(jié)果或僅在函數(shù)體中使用的類型參數(shù)。

約束類型推斷

從一個整數(shù)切片的縮放示例看起:

// Scale returns a copy of s with each element multiplied by c.
// This implementation has a problem, as we will see.
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
}

這個函數(shù)適用于所有整數(shù)類型的切片,看起來和之前的沒有什么不同,但是存在一個問題,讓我們繼續(xù)揭開這個問題:

假設(shè)我們有一個 Point 類型,它是一個 int32 類型元素的切片,同時他具有自己的一個方法 String():

type Point []int32
func (p Point) String() string {
    // Details not important.
}

這時候我們想要去放大 Point 為2倍,我們可以調(diào)用 Scale 函數(shù):

// ScaleAndPrint doubles a Point and prints it.
func ScaleAndPrint(p Point) {
    r := Scale(p, 2)
    fmt.Println(r.String()) // DOES NOT COMPILE
}

看起來沒有什么問題,但是編譯就會報錯。

問題在于,調(diào)用 Scale() 函數(shù)后,會返回一個 []E 類型的值,由于傳入的實參類型為 int32,因此函數(shù)的實際返回值為 []int32 而不是 Point,而 []int32 類型不具有其他方法。為了解決這個問題,我們要這樣做:

// Scale returns a copy of s with each element multiplied by c.
func Scale[S ~[]E, E constraints.Integer](s S, c E) S {
    r := make(S, len(s))
    for i, v := range s {
        r[i] = v * c
    }
    return r
}

通過引入新的參數(shù)類型 S 及約束,使得返回值類型為 S 而不是 []E,函數(shù)唯一的變化為,在實際調(diào)用時傳遞 S 而不是 []E,同樣返回值會返回 type Point。

這里也就引出了約束類型推斷的概念,也就是說,我們無需通過指定約束類型來實例化調(diào)用函數(shù),類似于:

r := Scale[Point, int32](p, 2)

編譯器可以推斷出類型參數(shù) S 是 Point。但是該函數(shù)還有一個類型參數(shù)E,實參為 2,2是一個無類型的常量,因此, 編譯器推斷出 E 的類型實參是切片的元素類型過程就是我們所說的約束類型推斷

通常情況當一個約束使用某種類型的形式時 ,其中該類型是使用其他類型參數(shù)編寫的,會用到這種應(yīng)用場景

官方建議的泛型使用時機

適用場景:

適用于任何元素類型的切片、映射和通道的函數(shù)

通過通用數(shù)據(jù)結(jié)構(gòu)用于通用數(shù)據(jù)(非接口類型,省去斷言)

當不同類型實現(xiàn)通用方法并且不同類型的方法實現(xiàn)看起來都一樣時

非適用場景:

僅在類型參數(shù)上調(diào)用方法時(例如io.reader)

當每種類型的公共方法的實現(xiàn)不同時 當對不同參數(shù)具有不同操作時

到此這篇關(guān)于一文帶你了解Golang中的泛型的文章就介紹到這了,更多相關(guān)Golang泛型內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Golang語言的多種變量聲明方式與使用場景詳解

    Golang語言的多種變量聲明方式與使用場景詳解

    Golang當中的變量類型和C/C++比較接近,一般用的比較多的也就是int,float和字符串,下面這篇文章主要給大家介紹了關(guān)于Golang語言的多種變量聲明方式與使用場景的相關(guān)資料,需要的朋友可以參考下
    2022-02-02
  • 簡單四步快速集成go環(huán)境變量

    簡單四步快速集成go環(huán)境變量

    這篇文章主要為大家介紹了快速集成go環(huán)境變量的簡單四個步驟詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-10-10
  • Go語言并發(fā)之context標準庫的使用詳解

    Go語言并發(fā)之context標準庫的使用詳解

    Context的出現(xiàn)是為了解決在大型應(yīng)用程序中的并發(fā)環(huán)境下,協(xié)調(diào)和管理多個goroutine之間的通信、超時和取消操作的問題,本文就來和大家簡單聊聊它的具體用法,希望對大家有所幫助
    2023-06-06
  • Golang實現(xiàn)定時任務(wù)的幾種方法小結(jié)

    Golang實現(xiàn)定時任務(wù)的幾種方法小結(jié)

    在 Golang 開發(fā)中,定時任務(wù)是常見的需求,本文將介紹幾種在 Golang 中實現(xiàn)定時任務(wù)的方法,包括 time 包的定時器、ticker,以及第三方庫 cron,并通過示例代碼展示它們的使用方式,需要的朋友可以參考下
    2024-01-01
  • 一篇文章說清楚?go?get?使用私有庫的方法

    一篇文章說清楚?go?get?使用私有庫的方法

    這篇文章主要介紹了go?get?如何使用私有庫,本文會明確指出Git?、golang的配置項,附送TortoiseGit?+?Git混合配置,需要的朋友可以參考下
    2022-09-09
  • RabbitMQ延時消息隊列在golang中的使用詳解

    RabbitMQ延時消息隊列在golang中的使用詳解

    延時隊列常使用在某些業(yè)務(wù)場景,使用延時隊列可以簡化系統(tǒng)的設(shè)計和開發(fā)、提高系統(tǒng)的可靠性和可用性、提高系統(tǒng)的性能,下面我們就來看看如何在golang中使用RabbitMQ的延時消息隊列吧
    2023-11-11
  • 淺談GoLang幾種讀文件方式的比較

    淺談GoLang幾種讀文件方式的比較

    這篇文章主要介紹了淺談GoLang幾種讀文件方式的比較,一般來說常用的有三種。使用Read加上buffer,使用bufio庫和ioutil 庫,非常具有實用價值,需要的朋友可以參考下
    2019-01-01
  • Golang實現(xiàn)根據(jù)某個特定字段對結(jié)構(gòu)體的順序進行排序

    Golang實現(xiàn)根據(jù)某個特定字段對結(jié)構(gòu)體的順序進行排序

    這篇文章主要為大家詳細介紹了Golang如何實現(xiàn)根據(jù)某個特定字段對結(jié)構(gòu)體的順序進行排序,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學(xué)習一下
    2024-03-03
  • Golang設(shè)計模式之原型模式詳細講解

    Golang設(shè)計模式之原型模式詳細講解

    如果一個類的有非常多的屬性,層級還很深。每次構(gòu)造起來,不管是直接構(gòu)造還是用建造者模式,都要對太多屬性進行復(fù)制,那么有沒有一種好的方式讓我們創(chuàng)建太的時候使用體驗更好一點呢? 今天的文章里就給大家介紹一種設(shè)計模式,來解決這個問題
    2023-01-01
  • Go到底能不能實現(xiàn)安全的雙檢鎖(推薦)

    Go到底能不能實現(xiàn)安全的雙檢鎖(推薦)

    這篇文章主要介紹了Go到底能不能實現(xiàn)安全的雙檢鎖,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-05-05

最新評論