golang 1.18 泛型教程

原文:https://makeoptim.com/golang/generics-tutorial

前言

本教程介紹 Go 中泛型的基礎(chǔ)知識面哼。使用泛型棵磷,你可以聲明和使用編寫為與調(diào)用代碼提供的任何一組類型一起使用的函數(shù)或類型展蒂。

在本教程中,你將聲明兩個簡單的非泛型函數(shù)抖单,然后在單個泛型函數(shù)中實(shí)現(xiàn)相同的邏輯岔激。

你將逐步完成以下部分:

  • 為你的代碼創(chuàng)建一個文件夾勒极。
  • 添加非泛型函數(shù)。
  • 添加一個通用函數(shù)來處理多種類型虑鼎。
  • 調(diào)用泛型函數(shù)時刪除類型參數(shù)辱匿。
  • 聲明類型約束。

注意:有關(guān)其他教程炫彩,請參閱教程匾七。

注意:如果你愿意,可以使用 “Go dev 分支”模式下的 Go Playground 來編輯和運(yùn)行你的程序昨忆。

先決條件

  • Go 1.18 或更高版本的安裝。有關(guān)安裝說明奖磁,請參閱安裝 Go
  • 用于編輯代碼的工具独旷。你擁有的任何文本編輯器都可以正常工作寥裂。
  • 一個命令終端嵌洼。Go 在 Linux 和 Mac 上的任何終端以及 Windows 中的 PowerShell 或 cmd 上都能很好地工作。

為你的代碼創(chuàng)建一個文件夾

首先低飒,為你要編寫的代碼創(chuàng)建一個文件夾糕档。

  1. 打開命令提示符并切換到你的主目錄端仰。

在 Linux 或 Mac 上:

$ cd

在 Windows 上:

C:\> cd %HOMEPATH%

本教程的其余部分將顯示 $ 作為提示鹤竭。你使用的命令也可以在 Windows 上運(yùn)行。

  1. 在命令提示符下苍碟,為你的代碼創(chuàng)建一個名為 generics 的目錄。
$ mkdir generics
$ cd generics
  1. 創(chuàng)建一個模塊來保存你的代碼早芭。

運(yùn)行 go mod init 命令匀谣,為其提供新代碼的模塊路徑。

$ go mod init example/generics
go: creating new go.mod: module example/generics

注意:對于生產(chǎn)代碼霹疫,你需要指定一個更符合你自己需求的模塊路徑痕寓。有關(guān)更多信息任内,請務(wù)必查看管理依賴項(xiàng)摘盆。

接下來狈邑,你將添加一些簡單的代碼來處理 maps坦弟。

添加非泛型函數(shù)

在此步驟中,你將添加兩個函數(shù)官地,每個函數(shù)將 map 的值相加并返回總數(shù)酿傍。

你要聲明兩個函數(shù)而不是一個,因?yàn)槟阏谑褂脙煞N不同類型的映射:一種用于存儲 int64 值驱入,另一種用于存儲 float64 值赤炒。

編寫代碼

  1. 使用你的文本編輯器,在 generics 目錄中創(chuàng)建一個名為 main.go 的文件亏较。你將在此文件中編寫你的 Go 代碼莺褒。

  2. 進(jìn)入 main.go,在文件頂部雪情,粘貼以下包聲明遵岩。

package main

獨(dú)立程序(與庫相反)始終位于 packagemain

  1. 在包聲明下方巡通,粘貼以下兩個函數(shù)聲明尘执。
// SumInts adds together the values of m.
func SumInts(m map[string]int64) int64 {
    var s int64
    for _, v := range m {
        s += v
    }
    return s
}

// SumFloats adds together the values of m.
func SumFloats(m map[string]float64) float64 {
    var s float64
    for _, v := range m {
        s += v
    }
    return s
}

在此代碼中,你:

  • 聲明兩個函數(shù)以將地圖的值相加并返回總和宴凉。
  • SumFloats stringfloat64 值的 map誊锭。
  • SumInts stringint64 值的 map
  1. main.go 頂部的包聲明下方弥锄,粘貼以下 main 函數(shù)以初始化兩個 map丧靡,并在調(diào)用你在上一步中聲明的函數(shù)時將它們用作參數(shù)。
func main() {
    // Initialize a map for the integer values
    ints := map[string]int64{
        "first":  34,
        "second": 12,
    }

    // Initialize a map for the float values
    floats := map[string]float64{
        "first":  35.98,
        "second": 26.99,
    }

    fmt.Printf("Non-Generic Sums: %v and %v\n",
        SumInts(ints),
        SumFloats(floats))
}

在此代碼中籽暇,你:

  • 初始化一個 float64 map 和一個 int64 map温治,每個都有兩個條目。
  • 調(diào)用你之前聲明的兩個函數(shù)來查找每個 map 值的總和戒悠。
  • 打印結(jié)果熬荆。
  1. main.go 頂部附近,就在包聲明的下方救崔,導(dǎo)入你需要支持你剛剛編寫的代碼的包惶看。

第一行代碼應(yīng)如下所示:

package main

import "fmt"
  1. 保存 main.go

運(yùn)行代碼

從包含 main.go 的目錄中的命令行六孵,運(yùn)行代碼纬黎。

$ go run .
Non-Generic Sums: 46 and 62.97

使用泛型,你可以在此處編寫一個函數(shù)而不是兩個劫窒。接下來本今,你將為包含整數(shù)或浮點(diǎn)值的映射添加一個通用函數(shù)。

添加通用函數(shù)來處理多種類型

在本節(jié)中,你將添加一個通用函數(shù)冠息,該函數(shù)可以接收包含整數(shù)或浮點(diǎn)值的映射挪凑,從而有效地將你剛剛編寫的兩個函數(shù)替換為一個函數(shù)。

要支持任一類型的值逛艰,該單個函數(shù)將需要一種方法來聲明它支持的類型躏碳。另一方面,調(diào)用代碼需要一種方法來指定它是使用整數(shù)映射還是浮點(diǎn)映射散怖。

為了支持這一點(diǎn)菇绵,你將編寫一個函數(shù),該函數(shù)在其普通函數(shù)參數(shù)之外還聲明類型參數(shù)镇眷。這些類型參數(shù)使函數(shù)具有通用性咬最,使其能夠處理不同類型的參數(shù)。你將使用類型參數(shù)和普通函數(shù)參數(shù)調(diào)用該函數(shù)欠动。

每個類型參數(shù)都有一個類型約束永乌,它充當(dāng)類型參數(shù)的一種元類型。每個類型約束指定調(diào)用代碼可用于相應(yīng)類型參數(shù)的允許類型參數(shù)具伍。

雖然類型參數(shù)的約束通常表示一組類型翅雏,但在編譯時類型參數(shù)代表單一類型——調(diào)用代碼作為類型參數(shù)提供的類型沿猜。如果類型參數(shù)的約束不允許類型參數(shù)的類型枚荣,則代碼將無法編譯。

請記住啼肩,類型參數(shù)必須支持泛型代碼對其執(zhí)行的所有操作。例如衙伶,如果你的函數(shù)代碼嘗試對其 string 約束包括數(shù)字類型的類型參數(shù)執(zhí)行操作(例如索引)祈坠,則代碼將無法編譯。

在你即將編寫的代碼中矢劲,你將使用允許整數(shù)或浮點(diǎn)類型的約束赦拘。

編寫代碼

  1. 在你之前添加的兩個函數(shù)下方,粘貼以下通用函數(shù)芬沉。
// SumIntsOrFloats sums the values of map m. It supports both int64 and float64
// as types for map values.
func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {
    var s V
    for _, v := range m {
        s += v
    }
    return s
}

在此代碼中躺同,你:

  • 聲明一個 SumIntsOrFloats 具有兩個類型參數(shù)(在方括號內(nèi))KV 的函數(shù),以及一個使用類型參數(shù)的參數(shù)丸逸,m 類型為 map[K]V蹋艺。該函數(shù)返回一個類型的值 V
  • K 類型參數(shù)指定類型約束 comparable黄刚。專門針對此類情況捎谨,comparableGo 中預(yù)先聲明了約束。它允許任何類型的值可以用作比較運(yùn)算符 == 和的操作數(shù) !=Go 要求 map keys 具有可比性涛救。所以聲明 K as comparable 是必要的畏邢,這樣你就可以 Kmap 變量中用作鍵。它還確保調(diào)用代碼對 map keys 使用允許的類型检吆。
  • V 類型參數(shù)指定一個約束舒萎,它是兩種類型的聯(lián)合:int64float64使用 | 指定兩種類型的聯(lián)合蹭沛,這意味著此約束允許任何一種類型臂寝。編譯器將允許任一類型作為調(diào)用代碼中的參數(shù)。
  • 指定 m 參數(shù)是 type map[K]V致板,其中 KV 是已經(jīng)為類型參數(shù)指定的類型交煞。請注意,我們知道 map[K]V 是有效的 map 類型斟或,因?yàn)?K 它是可比較的類型素征。如果我們沒有聲明 K 可比較,編譯器將拒絕對 map[K]V 的引用萝挤。

main.go 中御毅,在你已有的代碼下方,粘貼以下代碼怜珍。

fmt.Printf("Generic Sums: %v and %v\n",
    SumIntsOrFloats[string, int64](ints),
    SumIntsOrFloats[string, float64](floats))

在此代碼中端蛆,你:

  • 調(diào)用你剛剛聲明的通用函數(shù),傳遞你創(chuàng)建的每個映射酥泛。
  • 指定類型參數(shù) - 方括號中的類型名稱 - 以明確應(yīng)該替換你正在調(diào)用的函數(shù)中的類型參數(shù)的類型今豆。
  • 正如你將在下一節(jié)中看到的,你通橙嵩可以在函數(shù)調(diào)用中省略類型參數(shù)呆躲。Go 通常可以從你的代碼中推斷出它們捶索。
  • 打印函數(shù)返回的總和插掂。

運(yùn)行代碼

從包含 main.go 的目錄中的命令行,運(yùn)行代碼腥例。

$ go run .
Non-Generic Sums: 46 and 62.97
Generic Sums: 46 and 62.97

為了運(yùn)行你的代碼辅甥,在每次調(diào)用中,編譯器將類型參數(shù)替換為該調(diào)用中指定的具體類型燎竖。

在調(diào)用你編寫的泛型函數(shù)時璃弄,你指定了類型參數(shù),告訴編譯器使用什么類型代替函數(shù)的類型參數(shù)底瓣。正如你將在下一節(jié)中看到的谢揪,在許多情況下你可以省略這些類型參數(shù)蕉陋,因?yàn)榫幾g器可以推斷它們。

調(diào)用泛型函數(shù)時刪除類型參數(shù)

在本節(jié)中拨扶,你將添加通用函數(shù)調(diào)用的修改版本凳鬓,進(jìn)行小的更改以簡化調(diào)用代碼。你將刪除在這種情況下不需要的類型參數(shù)患民。

當(dāng) Go 編譯器可以推斷你要使用的類型時缩举,你可以在調(diào)用代碼中省略類型參數(shù)。編譯器從函數(shù)參數(shù)的類型推斷類型參數(shù)匹颤。

請注意仅孩,這并不總是可能的。例如印蓖,如果你需要調(diào)用沒有參數(shù)的泛型函數(shù)辽慕,則需要在函數(shù)調(diào)用中包含類型參數(shù)

編寫代碼

main.go 中赦肃,在你已有的代碼下方溅蛉,粘貼以下代碼。

fmt.Printf("Generic Sums, type parameters inferred: %v and %v\n",
    SumIntsOrFloats(ints),
    SumIntsOrFloats(floats))

在此代碼中他宛,你:

  • 調(diào)用泛型函數(shù)船侧,省略類型參數(shù)。

運(yùn)行代碼

從包含 main.go 的目錄中的命令行厅各,運(yùn)行代碼镜撩。

$ go run .
Non-Generic Sums: 46 and 62.97
Generic Sums: 46 and 62.97
Generic Sums, type parameters inferred: 46 and 62.97

接下來,你將通過將整數(shù)和浮點(diǎn)數(shù)的并集捕獲到你可以重用的類型約束(例如從其他代碼中)來進(jìn)一步簡化函數(shù)队塘。

聲明類型約束

在最后一部分中袁梗,你將把之前定義的約束移到它自己的接口中,以便你可以在多個地方重用它憔古。以這種方式聲明約束有助于簡化代碼围段,例如當(dāng)約束更復(fù)雜時。

你將類型約束聲明為接口投放。約束允許任何類型實(shí)現(xiàn)接口。例如适贸,如果你聲明了具有三個方法的類型約束接口灸芳,然后在泛型函數(shù)中將其與類型參數(shù)一起使用,則用于調(diào)用該函數(shù)的類型參數(shù)必須具有所有這些方法拜姿。

正如你將在本節(jié)中看到的烙样,約束接口也可以引用特定類型

編寫代碼

  1. 就在上面 main蕊肥,緊跟在 import 語句之后谒获,粘貼以下代碼來聲明類型約束蛤肌。
type Number interface {
    int64 | float64
}

在此代碼中,你:

  • 聲明 Number 要用作類型約束的接口類型批狱。

  • 在接口內(nèi)部聲明一個并集 int64float64

本質(zhì)上裸准,你正在將聯(lián)合從函數(shù)聲明移動到新的類型約束中。這樣赔硫,當(dāng)你想將類型參數(shù)約束為 int64 或者 float64 時炒俱,你可以使用此 Number 類型約束而不是寫出 int64 | float64

  1. 在你已有的函數(shù)下方爪膊,粘貼以下通用 SumNumbers 函數(shù)权悟。
// SumNumbers sums the values of map m. It supports both integers
// and floats as map values.
func SumNumbers[K comparable, V Number](m map[K]V) V {
    var s V
    for _, v := range m {
        s += v
    }
    return s
}

在此代碼中,你:

  • 聲明一個與你之前聲明的泛型函數(shù)具有相同邏輯的泛型函數(shù)推盛,但使用新的接口類型而不是聯(lián)合作為類型約束峦阁。和以前一樣,你使用類型參數(shù)作為參數(shù)和返回類型耘成。
  1. main.go 中榔昔,在你已有的代碼下方,粘貼以下代碼凿跳。
fmt.Printf("Generic Sums with Constraint: %v and %v\n",
    SumNumbers(ints),
    SumNumbers(floats))

在此代碼中件豌,你:

  • 調(diào)用 SumNumbers 每個 map,打印每個 map 的總和控嗜。

與上一節(jié)一樣茧彤,在調(diào)用泛型函數(shù)時省略了類型參數(shù)(方括號中的類型名稱)。Go 編譯器可以從其他參數(shù)推斷類型參數(shù)疆栏。

運(yùn)行代碼

從包含 main.go 的目錄中的命令行曾掂,運(yùn)行代碼。

$ go run .
Non-Generic Sums: 46 and 62.97
Generic Sums: 46 and 62.97
Generic Sums, type parameters inferred: 46 and 62.97
Generic Sums with Constraint: 46 and 62.97

結(jié)論

做得很好壁顶!你剛剛向自己介紹了 Go 中的泛型珠洗。

建議的下一個主題:

完整的代碼

你可以在 Go playground 上運(yùn)行這個程序许蓖。在 playground 上,只需單擊“運(yùn)行”按鈕调衰。

package main

import "fmt"

type Number interface {
    int64 | float64
}

func main() {
    // Initialize a map for the integer values
    ints := map[string]int64{
        "first": 34,
        "second": 12,
    }

    // Initialize a map for the float values
    floats := map[string]float64{
        "first": 35.98,
        "second": 26.99,
    }

    fmt.Printf("Non-Generic Sums: %v and %v\n",
        SumInts(ints),
        SumFloats(floats))

    fmt.Printf("Generic Sums: %v and %v\n",
        SumIntsOrFloats[string, int64](ints),
        SumIntsOrFloats[string, float64](floats))

    fmt.Printf("Generic Sums, type parameters inferred: %v and %v\n",
        SumIntsOrFloats(ints),
        SumIntsOrFloats(floats))

    fmt.Printf("Generic Sums with Constraint: %v and %v\n",
        SumNumbers(ints),
        SumNumbers(floats))
}

// SumInts adds together the values of m.
func SumInts(m map[string]int64) int64 {
    var s int64
    for _, v := range m {
        s += v
    }
    return s
}

// SumFloats adds together the values of m.
func SumFloats(m map[string]float64) float64 {
    var s float64
    for _, v := range m {
        s += v
    }
    return s
}

// SumIntsOrFloats sums the values of map m. It supports both floats and integers
// as map values.
func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {
    var s V
    for _, v := range m {
        s += v
    }
    return s
}

// SumNumbers sums the values of map m. Its supports both integers
// and floats as map values.
func SumNumbers[K comparable, V Number](m map[K]V) V {
    var s V
    for _, v := range m {
        s += v
    }
    return s
}

參考鏈接

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末膊爪,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子嚎莉,更是在濱河造成了極大的恐慌米酬,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件趋箩,死亡現(xiàn)場離奇詭異赃额,居然都是意外死亡加派,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進(jìn)店門跳芳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來芍锦,“玉大人,你說我怎么就攤上這事筛严∽淼” “怎么了?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵桨啃,是天一觀的道長车胡。 經(jīng)常有香客問我,道長照瘾,這世上最難降的妖魔是什么匈棘? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮析命,結(jié)果婚禮上主卫,老公的妹妹穿的比我還像新娘。我一直安慰自己鹃愤,他們只是感情好簇搅,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著软吐,像睡著了一般瘩将。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上凹耙,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天姿现,我揣著相機(jī)與錄音,去河邊找鬼肖抱。 笑死备典,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的意述。 我是一名探鬼主播提佣,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼荤崇!你這毒婦竟也來了镐依?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤天试,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后然低,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體喜每,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡务唐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了带兜。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片枫笛。...
    茶點(diǎn)故事閱讀 38,716評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖刚照,靈堂內(nèi)的尸體忽然破棺而出刑巧,到底是詐尸還是另有隱情,我是刑警寧澤无畔,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布啊楚,位于F島的核電站,受9級特大地震影響浑彰,放射性物質(zhì)發(fā)生泄漏恭理。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一郭变、第九天 我趴在偏房一處隱蔽的房頂上張望颜价。 院中可真熱鬧,春花似錦诉濒、人聲如沸周伦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽专挪。三九已至,卻和暖如春茄猫,著一層夾襖步出監(jiān)牢的瞬間狈蚤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工划纽, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留脆侮,地道東北人。 一個月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓勇劣,卻偏偏與公主長得像靖避,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子比默,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評論 2 350

推薦閱讀更多精彩內(nèi)容