最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

一文帶你了解Golang中的泛型

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

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

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

什么是泛型

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

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

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

類型參數(shù) Type parameters

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

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

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

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

我們可以通過類型參數(shù)來替換 float64 類型來使這個(gè)函數(shù)更加通用,讓這個(gè)函數(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 是一個(gè)新的類型,所以我們要在 [] 中聲明它。

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

實(shí)例化將會(huì)分兩步進(jìn)行:

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

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

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

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

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

前面是一個(gè)函數(shù)中使用類型參數(shù)的例子,還有一個(gè)在類型中使用類型參數(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]

這是一個(gè)通用的二叉樹類型定義,我們?cè)俅尾捎昧祟愋蛥?shù) T 來作為一個(gè)通用性的數(shù)據(jù)類型,這里定義了類型 Tree[T] 同時(shí)定義了它所具有的方法 Lookup(x T) *Tree[T]。

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

類型集

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

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

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

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

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

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

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

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

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

實(shí)際聲明的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版本后,接口可以像之前一樣包含方法或嵌入接口,同時(shí)我們也可以嵌入非接口類型、聯(lián)合和底層類型集。

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

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

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

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

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

類型推斷 type inference

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

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

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

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

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

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

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

約束類型推斷

從一個(gè)整數(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
}

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

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

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

這時(shí)候我們想要去放大 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
}

看起來沒有什么問題,但是編譯就會(huì)報(bào)錯(cuò)。

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

// 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ù)唯一的變化為,在實(shí)際調(diào)用時(shí)傳遞 S 而不是 []E,同樣返回值會(huì)返回 type Point。

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

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

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

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

官方建議的泛型使用時(shí)機(jī)

適用場(chǎng)景:

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

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

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

非適用場(chǎng)景:

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

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

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

相關(guān)文章

  • Go語言開發(fā)k8s之ConfigMap操作解析

    Go語言開發(fā)k8s之ConfigMap操作解析

    這篇文章主要為大家介紹了Go語言開發(fā)k8s之ConfigMap操作示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-06-06
  • Go語言計(jì)算兩個(gè)經(jīng)度和緯度之間距離的方法

    Go語言計(jì)算兩個(gè)經(jīng)度和緯度之間距離的方法

    這篇文章主要介紹了Go語言計(jì)算兩個(gè)經(jīng)度和緯度之間距離的方法,涉及Go語言相關(guān)數(shù)學(xué)函數(shù)的使用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-02-02
  • 詳解Golang中的各種時(shí)間操作

    詳解Golang中的各種時(shí)間操作

    這篇文章主要介紹了詳解Golang中的各種時(shí)間操作,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-10-10
  • Go語言切片擴(kuò)容原理和過程

    Go語言切片擴(kuò)容原理和過程

    切片(Slice)在 Go 語言中,有一個(gè)很常用的數(shù)據(jù)結(jié)構(gòu),切片是一個(gè)擁有相同類型元素的可變長(zhǎng)度的序列,它是基于數(shù)組類型做的一層封裝,它非常靈活,支持自動(dòng)擴(kuò)容,并發(fā)不安全,本文給大家介紹了Go 語言切片如何擴(kuò)容,需要的朋友可以參考下
    2025-02-02
  • go實(shí)現(xiàn)redigo的簡(jiǎn)單操作

    go實(shí)現(xiàn)redigo的簡(jiǎn)單操作

    golang操作redis主要有兩個(gè)庫,go-redis和redigo,今天我們就一起來介紹一下redigo的實(shí)現(xiàn)方法,需要的朋友可以參考下
    2018-07-07
  • Golang實(shí)現(xiàn)Json分級(jí)解析及數(shù)字解析實(shí)踐詳解

    Golang實(shí)現(xiàn)Json分級(jí)解析及數(shù)字解析實(shí)踐詳解

    你是否遇到過在無法準(zhǔn)確確定json層級(jí)關(guān)系的情況下對(duì)json進(jìn)行解析的需求呢?本文就來和大家介紹一次解析不確定的json對(duì)象的經(jīng)歷,以及遇到的問題和解決方法
    2023-02-02
  • Go底層之string和[]byte相互轉(zhuǎn)換原理分析

    Go底層之string和[]byte相互轉(zhuǎn)換原理分析

    這篇文章主要介紹了Go底層之string和[]byte相互轉(zhuǎn)換原理,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2025-06-06
  • Go語言interface詳解

    Go語言interface詳解

    這篇文章主要介紹了Go語言interface詳解,本文講解了什么是interface、interface類型、interface值、空interface、interface函數(shù)參數(shù)等內(nèi)容,需要的朋友可以參考下
    2014-10-10
  • Go開發(fā)神器Air熱加載的安裝使用探究

    Go開發(fā)神器Air熱加載的安裝使用探究

    這篇文章主要介紹了Go開發(fā)神器Air熱加載使用實(shí)例探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2024-01-01
  • 使用go在mangodb中進(jìn)行CRUD操作

    使用go在mangodb中進(jìn)行CRUD操作

    這篇文章主要介紹了使用go在mangodb中進(jìn)行CRUD操作,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2019-10-10

最新評(píng)論

卢龙县| 龙里县| 微博| 贵溪市| 三门县| 越西县| 德江县| 夏津县| 五常市| 石河子市| 满城县| 鹤庆县| 晋江市| 尼木县| 济阳县| 砚山县| 禹城市| 广河县| 峨眉山市| 应用必备| 公安县| 布拖县| 惠来县| 西丰县| 南昌市| 龙州县| 荥经县| 赫章县| 大关县| 峨眉山市| 金堂县| 周至县| 湘阴县| 南宁市| 来凤县| 屏南县| 榆社县| 合川市| 无棣县| 吉木乃县| 哈巴河县|