<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 6092字,需瀏覽 13分鐘

           ·

          2021-09-27 10:27

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

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


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

          什么是泛型

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

          func PrintString(s string) {

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

          Go 泛型函數(shù)

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

          這是它看起來(lái)的樣子:

          func PrintAnything[T any](thing T) {

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

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

          PrintAnything("Hello!")

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

          約束

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

          // 我有一種不好的預(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è)任意類(lèi)型 T 的切片參數(shù)。很好,但是現(xiàn)在我們遇到了一個(gè)問(wèn)題:

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

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

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

          type Stringer interface {
              String() string
          }

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

          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保證了任何類(lèi)型T的值都有 String() 方法,Go 現(xiàn)在很樂(lè)意讓我們?cè)诤瘮?shù)內(nèi)部調(diào)用它。但是,如果你嘗試使用某個(gè)未實(shí)現(xiàn) Stringer 類(lèi)型的切片(例如 int )來(lái)調(diào)用 Join() 方法時(shí) ,Go 將會(huì)抱怨:

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

          可比較的約束

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

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

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

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

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

          func Equal[T comparable] ...

          constraints 包

          增加點(diǎn)難度,假設(shè)我們想用 T的值做一些事情,既不比較它們也不調(diào)用它們的方法。例如,假設(shè)我們想為泛型 T 類(lèi)型編寫(xiě)一個(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ì)此不太樂(lè)觀(guān),但讓我們看看會(huì)發(fā)生什么:

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

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

          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)鍵盤(pán)就可以導(dǎo)入并像這樣來(lái)使用:

          func Max[T constraints.Ordered] ...問(wèn)題解決了!

          泛型類(lèi)型

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

          type Bunch[T any] []T

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

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

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

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

          方法也同樣可以:

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

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

          type StringableBunch[T Stringer] []T

          視頻:Code Club: Generics

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

          泛型 Golang Playground

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

          Q&A

          Go 泛型提案是什么

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

          類(lèi)型參數(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 來(lái)試驗(yàn)它,并嘗試運(yùn)行此文的示例。

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

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

          any 因何而來(lái)

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

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

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

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

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

          什么是合約

          早期的 設(shè)計(jì)草案 中泛型使用了與我們今天相似的語(yǔ)法,但是它使用了一個(gè)新的關(guān)鍵字 contract 來(lái)實(shí)現(xiàn)類(lèi)型約束,而非現(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)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  操逼片儿 | 人人草人人草人人草 | 国产婷婷内射一级二 | 久久国产精品精品国产色综合 | [无码破解]AV破解版HD在线观看 |