1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        你想知道的 Go 泛型都在這里

        共 6092字,需瀏覽 13分鐘

         ·

        2021-09-27 10:27

        泛型現(xiàn)在進(jìn)展如何?這個(gè)友好而實(shí)用的教程將解釋泛型函數(shù)和類型是什么,為什么我們需要它們,它們?cè)?Go 中如何工作,以及我們可以在哪里使用它們。這是非常簡(jiǎn)單有趣的,讓我們開始吧!

        John Arundel 是一位 Go 語言的老師兼顧問,也是《For the Love of Go》一書的作者。這是一套關(guān)于現(xiàn)代軟件工程在 Go 語言中實(shí)踐的電子書,完全面向初學(xué)者。 


         《For the Love of Go》是一系列有趣并且容易理解的電子書,專門介紹軟件工程在 Go 語言中的實(shí)踐。

        什么是泛型

        大家都知道, Go 是一種 強(qiáng)類型 語言,這意味著程序中的每個(gè)變量和值都有特定的類型,如 intstring 。當(dāng)我們編寫函數(shù)時(shí),我們需要在所謂的 函數(shù)簽名 中指定它們的形參類型,像這樣:

        func PrintString(s string) {

        這里,形參 s 的類型是 string 。我們可以想象編寫這個(gè)函數(shù)接受 int 、 float64 、任意結(jié)構(gòu)類型等形參的版本。但是當(dāng)需要處理的不僅僅是這些明確類型時(shí),多少是不太方便的,盡管我們有時(shí)可以使用 接口 來解決這個(gè)問題(例如 map[string]interface 教程 中所描述),但這種方法也有很多局限性。

        Go 泛型函數(shù)

        相反,現(xiàn)在我們可以聲明一個(gè) 泛型函數(shù) PrintAnything,它接受一個(gè)表示任意類型的 any 參數(shù)(我們稱它為T ),并使用它做一些事情。

        這是它看起來的樣子:

        func PrintAnything[T any](thing T) {

        很簡(jiǎn)單對(duì)吧?這里的 any 表示T 可以是任何類型。

        我們?cè)趺礃诱{(diào)用這個(gè)函數(shù)?這也同樣很簡(jiǎn)單:

        PrintAnything("Hello!")

        注意:我在這里描述的對(duì) Go 泛型的支持還沒有發(fā)布,但它 正在實(shí)現(xiàn)中 ,很快就會(huì)發(fā)布。現(xiàn)在你可以在 支持泛型的 Go Playground 中使用它,或者在你的項(xiàng)目中使用實(shí)驗(yàn)性的 go2go 工具 來嘗試獲得 Go 泛型支持。

        約束

        要實(shí)現(xiàn) PrintAnything 函數(shù)其實(shí)非常容易,因?yàn)?fmt 庫(kù)就可以打印任何東西。假設(shè)我們想實(shí)現(xiàn)我們自己版本的 strings.Join 函數(shù),它接受一個(gè) T 類型的切片,并返回一個(gè)將它們連接在一起的字符串。讓我們來試一試:

        // 我有一種不好的預(yù)感 func Join[T any](things []T) (result string) { for _, v := range things { result += v.String() } return result }我們已經(jīng)創(chuàng)建了一個(gè)泛型函數(shù) Join() ,它接受一個(gè)任意類型 T 的切片參數(shù)。很好,但是現(xiàn)在我們遇到了一個(gè)問題:

        output := Join([]string{"a""b""c"})
        // v.String 沒有被定義(綁定的類型 T 沒有 String 方法)

        也就是說在 Join() 函數(shù)中,我們想對(duì)每個(gè)切片元素 v 調(diào)用 .String()方法 ,將其轉(zhuǎn)換為 string 。但是 Go 需要能夠提前檢查 T 類型是否有 String()方法,然而它并不知道 T 是什么,所以它不能直接調(diào)用!

        我們需要做的是稍微地約束下 T 類型。實(shí)際上我們只對(duì)具有 String() 方法的類型感興趣,而不是直接接受任何類型的 T 。任何具有這種方法的類型才能作為 Join() 函數(shù)的輸入,那么我們?nèi)绾斡?Go 表達(dá)這個(gè)約束呢?我們可以使用一個(gè) 接口 :

        type Stringer interface {
            String() string
        }

        當(dāng)給定類型實(shí)現(xiàn)了 String() 方法,現(xiàn)在我們就可以把這個(gè)約束應(yīng)用到泛型函數(shù)的類型上:

        func Join[T Stringer] ...

        因?yàn)?code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">Stringer保證了任何類型T的值都有 String() 方法,Go 現(xiàn)在很樂意讓我們?cè)诤瘮?shù)內(nèi)部調(diào)用它。但是,如果你嘗試使用某個(gè)未實(shí)現(xiàn) Stringer 類型的切片(例如 int )來調(diào)用 Join() 方法時(shí) ,Go 將會(huì)抱怨:

        result := Join([]int{1, 2, 3})
        // int 未實(shí)現(xiàn) Stringer 接口(未找到 String 方法)

        可比較的約束

        基于方法集的約束(如 Stringer)是有用的,但如果我們想對(duì)我們的泛型輸入做一些不涉及方法調(diào)用的事情呢?

        例如,假設(shè)我們想編寫一個(gè) Equal 函數(shù),它接受兩個(gè) T類型的形參,如果它們相等則返回 true ,否則返回 false 。讓我們?cè)囈辉嚕?/p>

        // 這將不會(huì)有效
        func Equal[T any](a, b T) bool {
            return a == b
        }

        fmt.Println(Equal(1, 1))
        // 不能比較 a == b (類型 T 沒有定義操作符 == )

        這與在 Join() 中使用 String() 方法遇到的問題相同,但由于我們現(xiàn)在沒有直接調(diào)用方法,所以不能使用基于方法集的約束。相反,我們需要將T 約束為可使用 ==!= 操作符,這被稱為 可比較 類型。幸運(yùn)的是,有一種直接的方式來指定這種類型:使用內(nèi)置的 comparable 約束,而不是 any 。

        func Equal[T comparable] ...

        constraints 包

        增加點(diǎn)難度,假設(shè)我們想用 T的值做一些事情,既不比較它們也不調(diào)用它們的方法。例如,假設(shè)我們想為泛型 T 類型編寫一個(gè) Max() 函數(shù),它接受 T 的一個(gè)切片,并返回切片元素中的最大值。我們可以嘗試這樣做:

        // Nope.
        func Max[T any](input []T) (max T) {
            for _, v := range input {
                if v > max {
                    max = v
                }
            }
            return max
        }

        我對(duì)此不太樂觀,但讓我們看看會(huì)發(fā)生什么:

        fmt.Println(Max([]int{1, 2, 3}))
        // 不能比較 v > max ( T 類型沒有定義操作符 > )

        同樣,Go 不能提前驗(yàn)證 T類型可以使用 > 操作符(也就是說,T 是 有序的 )。我們?nèi)绾谓鉀Q這個(gè)問題?我們可以簡(jiǎn)單地在約束中列出所有可能允許的類型,像這樣(稱為 列表類型 ):

        type Ordered interface {
            type int, int8, int16, int32, int64,
                uint, uint8, uint16, uint32, uint64, uintptr,
                float32, float64,
                string
        }

        幸運(yùn)的是,在標(biāo)準(zhǔn)庫(kù)的 constraints 包中已經(jīng)為我們定義了一些實(shí)用的約束條件,所以我們只需要?jiǎng)觿?dòng)鍵盤就可以導(dǎo)入并像這樣來使用:

        func Max[T constraints.Ordered] ...問題解決了!

        泛型類型

        到目前為止,一切都很酷。我們知道如何編寫可以接受任何類型參數(shù)的函數(shù)。但是如果我們想要?jiǎng)?chuàng)建一個(gè)可以包含任何類型的類型呢?例如,一個(gè) “任意類型的切片” 。這其實(shí)也很簡(jiǎn)單:

        type Bunch[T any] []T

        這里指對(duì)于任何給定的T類型 , Bunch[T]T類型的切片。例如, Bunch[int]int 的切片。我們可以用常規(guī)的方法來創(chuàng)建該類型的值:

        x := Bunch[int]{1, 2, 3}

        正如你所期望的,我們可以編寫接受泛型類型的泛型函數(shù):

        func PrintBunch[T any](b Bunch[T]) {

        方法也同樣可以:

        func (b Bunch[T]) Print() {

        我們也可以對(duì)泛型類型施加約束:

        type StringableBunch[T Stringer] []T

        視頻:Code Club: Generics

        泛型 Golang playground Go 團(tuán)隊(duì)提供了一個(gè)支持泛型的 Go Playground 版本,你可以在上面使用當(dāng)前泛型提案的實(shí)現(xiàn)(例如嘗試本教程中的代碼示例)。

        泛型 Golang Playground

        它的工作方式與我們所了解和喜愛的普通 Go Playground 完全相同,只是它支持本文描述的泛型語法。由于在 Playground 中不可能運(yùn)行所有的 Go 代碼(例如網(wǎng)絡(luò)調(diào)用或者訪問文件系統(tǒng)的代碼),你可以嘗試使用 go2go 工具,它可以將使用泛型的代碼翻譯成當(dāng)前 Go 版本能編譯的代碼。

        Q&A

        Go 泛型提案是什么

        你可以在這里閱讀完整的設(shè)計(jì)文檔草稿:

        類型參數(shù) - 設(shè)計(jì)草稿

        Golang 會(huì)支持泛型嗎

        是的。正如本教程的概述,在 Go 中目前對(duì)于支持泛型的提案已經(jīng)在 2020 年 6 月一篇博客文章:泛型的下一階段 中宣布了。并且這篇 Github issue (關(guān)于新增上文所描述形式的泛型)也已經(jīng)被接受了。

        Go 博客 表示,在 Go 1.18 的測(cè)試版本可能會(huì)包含對(duì)泛型的支持,該測(cè)試版本將于 2021 年 12 月發(fā)布。

        在此之前,你可以使用 泛型 Playground 來試驗(yàn)它,并嘗試運(yùn)行此文的示例。

        泛型 vs 接口:這是泛型的另一種選擇嗎

        正如我在 map[string]interface 教程 中提到的,我們可以通過 接口 來編寫 Go 代碼處理任何類型的值,而不需要使用泛型函數(shù)或類型。但是,如果你想編寫實(shí)現(xiàn)任意類型的集合之類的庫(kù),那么使用泛型類型要比使用接口簡(jiǎn)單得多,也方便得多。

        any 因何而來

        當(dāng)定義泛型函數(shù)或類型時(shí),輸入類型必須有一個(gè)約束。類型約束可以是接口(如 Stringer )、列表類型(如 constraints.ordered)或關(guān)鍵字 comparable。但如果你真的不想要約束,也就是說,像字面意義上的 任何 T 類型 ?

        符合邏輯的方法是使用 interface{} (接口對(duì)類型的方法集沒有任何限制)來表達(dá)。由于這是一個(gè)常見的約束,所以預(yù)先聲明關(guān)鍵字 any 被提供來作為 interface{} 的別名。但是你只能在類型約束中使用這個(gè)關(guān)鍵字,所以 any 并不是等價(jià)于 interface{} 。

        我可以使用代碼生成器代替泛型嗎

        在 Go 的泛型出現(xiàn)之前,“代碼生成器” 方法是處理此類問題的另一種傳統(tǒng)方法。本質(zhì)上,針對(duì)每種你的庫(kù)中需要處理的特定類型,它都需要使用 go 生成器工具 產(chǎn)生新的 Go 代碼。

        這雖然可行,但使用起來很笨拙,它的靈活性受到限制,并且需要額外的構(gòu)建步驟。雖然代碼生成器在某些情況下仍然有用,但我們不再需要使用它來模擬 Go 中的泛型函數(shù)和類型。

        什么是合約

        早期的 設(shè)計(jì)草案 中泛型使用了與我們今天相似的語法,但是它使用了一個(gè)新的關(guān)鍵字 contract 來實(shí)現(xiàn)類型約束,而非現(xiàn)有的 interface 。由于種種原因,它不太受歡迎,現(xiàn)在已經(jīng)被廢棄了。

        Further reading 延伸閱讀

        • 一個(gè)增加泛型的提案(https://go.dev/blog/generics-proposal)
        • 泛型的下一階段(https://go.dev/blog/generics-next-step)
        • 為什么使用泛型?(https://go.dev/blog/why-generics)
        • Go 泛型:將設(shè)計(jì)草案應(yīng)用到真實(shí)的用例中(https://secrethub.io/blog/go-generics/)
        • 在 Go 中嘗試泛型(https://medium.com/swlh/experimenting-with-generics-in-go-39ffa155d6a1)

        原文地址:https://bitfieldconsulting.com/golang/generics

        原文作者:John Arundel

        本文永久鏈接:https://github.com/gocn/translator/blob/master/2021/w13_Generics_in_Go.md

        譯者:haoheipi

        校對(duì):



        想要了解更多資訊,還可以入群和大家一起暢聊哦~



        瀏覽 23
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            中国videosex高潮hd | 伊人干综合| 在线观看亚洲欧洲 | 久久精品国产99久久不卡 | 男人插女人下面视频 | 欧美一区二区三区成人久久片 | 午夜九九 | 91精品国自产欧美在线观看 | 国产精品久久久久久日日游香 | 影音先锋最新av资源 |