Go泛型基礎使用
導讀 | 泛型,是Go語言多年來最令人興奮和根本性的變化之一。沒有泛型,很多人以此「鄙視」Go語言。當然,也有人覺得根本不需要泛型。有泛型,不代表你一定要用。平心而論,有些場景下,泛型還是很有必要和幫助的。 |
現在已經確認,Go1.18 正式包含泛型(Go1.17 已經可以試用,只是預設不支援,見之前的文章:揚眉吐氣:剛剛,Go 已經預設支援泛型了)。
不過,不少人對泛型還是迷迷糊糊的。本文就嘗試用簡單的術語解釋泛型相關的內容。
Go 是一門強型別語言,意味著程式中的每個變數和值都有某種特定的型別,例如int、string 等。在函式簽名中,我們需要對引數和返回值指定型別,如下所示:
func Add(a, b int) int
引數 a 和 b 的型別是 int,返回值型別也是 int,結果是 a 和 b 的和。
如果現在需要一個對兩個 float64 求和的函式,怎麼辦?
大機率會出現類似這樣的函式:
func AddFloat(a, b float64) float64
如果有更多其他的型別(比如字串相加),可能需要寫更多的對應版本函式,很不方便,也很繁瑣,一堆複製貼上的程式碼。
如果有了泛型,上面的問題怎麼解決呢?只需要一個函式就搞定:
func Add[T any](a, b T) T
是不是很簡單?不過看著有點暈?稍微解釋下:
- Add 後面的 [T any],T 表示型別的標識,any 表示 T 可以是任意型別
- a、b 和返回值的型別 T 和前面的 T 是同一個型別
- 為什麼用 [],而不是其他語言中的 <>,官方有過解釋,大概就是 <> 會有歧義。曾經計劃使用 (),因為太容易混淆,最後使用了 []。
這樣就表示,a、b 和返回值可以是任意型別,但它們的型別是同一個。那具體是什麼型別如何確定呢?根據呼叫時的實際引數決定。因此,我們現在可以這麼使用:
Add(1, 2) Add(2.1, 3.2)
不過,這時候程式碼會報錯。你可以本地用 Go1.17 啟用泛型的方式試驗,也可以使用 gotip 版本,亦或直接訪問這裡試驗:
package main import ( "fmt" ) func Add[T any](a, b T) T { return a + b } func main() { fmt.Println(Add(1, 2)) fmt.Println(Add(2.1, 3.2)) }
執行會報錯:
type checking failed for main prog.go2:8:9: invalid operation: operator + not defined for a (variable of type parameter type T)
為什麼?請看下文。
很顯然,並非所有型別都支援加法操作。因此我們需要給出約束,指定可以進行加法操作的型別。
上面程式碼中,我們對型別 T 使用的是 any,相當於沒有進行任何約束。現在我們給一個約束:
type Addable interface { type int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr, float32, float64, complex64, complex128, string }
這是新語法,叫做型別列表(type list)。
首先,Addable 重用了介面語法,即 interface 關鍵字,表示約束,具體約束的型別透過 type 指定,多個用逗號分隔。
現在 Add 函式中 T 的約束從 any 改為 Addable:
func Add[T Addable](a, b T "T Addable") T { return a + b }
現在再次執行:,發現正常了。而且還支援字串、複數等:
Add("polaris", "xu")
可見,約束可以是任意介面型別。(any 相當於空介面)
還有另外一種場景:可比較。比如 map 中的 key 要求是可比較的。比如下面的程式碼:
func findFunc[T any](a []T, v T "T any") int { for i, e := range a { if e == v { return i } } return -1 }
T 的約束是任意型別,而實際上並非所有型別都是可比較的。怎麼辦?我們當然可以向上面 Addable 一樣定義一個約束,但為了方便,Go 內建提供了一個 comparable 約束,表示可比較的。參考下面程式碼:
package main func findFunc[T comparable](a []T, v T "T comparable") int { for i, e := range a { if e == v { return i } } return -1 } func main() { print(findFunc([]int{1, 2, 3, 4, 5, 6}, 5)) }
寫泛型程式碼時,約束挺常見。再看一個例子,從切片中找出最大值:
func Max[T any](input []T "T any") (max T) { for _, v := range input { if v > max { max = v } } return }
但執行會報錯:
fmt.Println(Max([]int{1, 4, 2, 10})) // cannot compare v > max (operator > not defined for T)
這時,我們自然想到使用上面 Add 函式類似的辦法,自定義一個約束:Ordered,把可能的型別都列上。
type Ordered interface {
type int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr, float32, float64, string
}
因為這樣的需求挺常見的,為了方面,官方提供了一個新包:constraints,預定義了一些約束,具體檢視:。
有了它,不需要自定義這個 Ordered 約束,而是使用 constraints 包中的,即:
func Max[T constraints.Ordered](input []T "T constraints.Ordered") (max T)
上面,我們介紹了泛型函式:即函式可以接受任意型別。注意和 interface{} 這樣的任意型別區分開,泛型中的型別,在函式內部並不需要做任何型別斷言和反射的工作,在編譯期就可以確定具體的型別。
我們知道,Go 支援自定義型別,比如標準庫 sort 包中的 IntSlice:
type IntSlice []int
此外,還有 StringSlice、Float64Slice 等,一堆重複程式碼。如果我們能夠定義泛型型別,就不需要定義這麼多不同的型別了。比如:
type Slice[T any] []T
能看懂吧。
在使用時,針對 int 型別,就是這樣:
x := Slice[int]{1, 2, 3}
如果作為函式引數,這麼使用:
func PrintSlice[T any](b Slice[T] "T any")
如果為這個型別定義方法,則是這樣:
func (b Slice[T]) Print()
也就是說,Slice[T] 作為整體存在。
當然,泛型型別也可以做型別約束,而不是 any 型別:
type Slice[T comparable] []T
透過本文的講解,相信你對 Go 泛型有了一個基本的掌握。
Go1.18 會包含不少泛型相關的標準庫,包括對現有標準庫的泛型支援,這是目前 Go 官方的重要工作。
原文來自:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69955379/viewspace-2837746/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Java基礎 —— 泛型Java泛型
- Java基礎-泛型Java泛型
- java 基礎 泛型Java泛型
- TypeScript基礎--泛型TypeScript泛型
- Go 官方出品泛型教程:如何開始使用泛型Go泛型
- Go 泛型Go泛型
- Go泛型解密:從基礎到實戰的全方位解析Go泛型解密
- Java基礎之泛型方法Java泛型
- java基礎複習-----泛型Java泛型
- Java基礎-泛型詳解Java泛型
- 【Java反射】Java 泛型基礎Java反射泛型
- Go 泛型之泛型約束Go泛型
- go泛型教程Go泛型
- Java基礎之淺談泛型Java泛型
- Java基礎——深入理解泛型Java泛型
- C#基礎:泛型委託C#泛型
- corejava基礎知識(3)-泛型Java泛型
- JAVA基礎之九-泛型(通用型別)Java泛型型別
- Java 基礎 一文搞懂泛型Java泛型
- java入門基礎學習----泛型Java泛型
- 泛型最佳實踐:Go泛型設計者教你如何用泛型泛型Go
- Java基礎知識掃盲(四)——泛型Java泛型
- Java & Go 泛型對比JavaGo泛型
- Go 1.17 泛型嚐鮮Go泛型
- 基礎篇:深入解析JAVA泛型和Type型別體系Java泛型型別
- go 1.18 泛型初體驗Go泛型
- go需要泛型的場景Go泛型
- Go 需要泛型的場景Go泛型
- 使用 Go 泛型的函數語言程式設計Go泛型函數程式設計
- go語言資料型別-基礎型別Go資料型別
- python使用泛型Python泛型
- Go 基礎之基本資料型別Go資料型別
- PHP->GO 基礎-資料型別PHPGo資料型別
- go 基礎總結 --- 資料型別Go資料型別
- 泛型類、泛型方法、型別萬用字元的使用泛型型別字元
- Go 1.18泛型的侷限性初探Go泛型
- Go Internals: Go 反射 vs Java 泛型 vs cpp 模板Go反射Java泛型
- Go 1.18 泛型全面講解:一篇講清泛型的全部Go泛型