泛型編程在許多編程語言中都是一項非常強大的特性,它可以使程序更加通用、具有更高的重用性。然而,Go語言在很長一段時間內一直沒有提供泛型功能。在過去的一些版本中,Go語言開發者試圖引入泛型,但最終都因為各種原因被取消或擱置了。直到Go 1.18版本,終于引入了泛型功能。在本文中,將會介紹這項新特性及其使用方法。
泛型是一種編程語言的特性,它可以將類型參數化,并以類型參數形式傳遞到不同的算法和數據結構中。泛型使得程序可以更加通用、安全且具有更高的重用性。不同的類型參數可以通過參數化類型類型來表示。例如,在Java中,可以使用ArrayList<Integer>來表示包含整數的動態數組,其中Integer是類型參數的類型。
在Go語言中,泛型的類型參數可以是任何類型,包括基本類型、引用類型、結構體和接口等。這些類型參數可以用在函數、方法、結構體、接口、通道和映射等語法結構中。
當談到泛型編程時,我們需要了解兩個重要的概念:類型形參和類型實參。
使用類型形參和類型實參的一個典型例子是在泛型函數中定義類型形參,然后調用該函數時提供類型實參的類型。例如:
package mainimport "fmt"http:// 定義泛型函數func PrintType[T any](x T) { fmt.Printf("Type: %T/n", x)}func main() { // 調用泛型函數,類型實參為 int PrintType[int](42) // 調用泛型函數,類型實參為 string PrintType[string]("hello")}輸出結果:Type: intType: string
在上面的示例中,我們定義了一個名為 PrintType 的泛型函數,并使用 [T any] 聲明了一個類型形參。然后,在調用該函數時,我們使用類型實參來具體化類型形參,例如使用 int 和 string。這樣,在函數內部,我們就可以使用具體的類型信息來打印數據的類型。
類型形參和類型實參的使用為我們提供了更大的靈活性和通用性,使得我們可以編寫可處理多種類型的泛型代碼。
通過上面的代碼,我們對Go的泛型編程有了最初步也是最重要的認識——類型形參 和類型實參。而Go 1.18也是通過這種方式實現的泛型,但是單純的形參實參是遠遠不能實現泛型編程的,所以Go還引入了非常多全新的概念:
type MySlice[T int|float32|float64 ] []Tvar mySlice MySlice[int]上面這段代碼定義了一個具有類型約束的泛型類型MySlice,T為類型參,必須是int、float32或float64之一,表示只能用這個明確的類型代替T。MySlice[T]表示一個元素類型為T切片類型。T 就是類型形參(Type parameter),類似一個占位符int|float32|float64 就是類型約束(Type constraint),中間的 | 就是或的意思,表示類型形參 T 只接收 int 或 float32 或 float64 這三種類型的實參中括號里的 T int|float32|float64 這一整串因為定義了所有的類型形參(在這個例子里只有一個類型形參T),所以我們稱其為 類型形參列表(Type parameter list)在使用MySlice時,如MySlice[int]表示元素類型為int切片類型,int 就是類型實參(Type argument)上面只是個最簡單的例子,實際上類型形參的數量可以遠遠不止一個,如下:// CostMap類型定義了兩個類型形參 KEY 和 VALUE。分別為兩個形參指定了不同的類型約束// 這個泛型類型的名字叫:CostMap[KEY, VALUE]type CostMap[KEY int | string, VALUE float32 | float64] map[KEY]VALUE // 用類型實參 string 和 flaot64 替換了類型形參 KEY 、 VALUE,// 泛型類型被實例化為具體的類型:CostMap[string, float64]var a CostMap[string, float64] = map[string]float64{ "dept1_cost": 8913.34, "dept2_cost": 4295.64,}
用上面的例子重新復習下各種概念:
用如下一張圖就能簡單說清楚:
圖片
在Go語言中,泛型的實現方式是使用類型參數化函數和類型參數化結構體。類型參數化函數是一種函數,接受類型參數作為輸入,并根據這些類型參數返回不同的結果。類型參數化結構體是一種結構體,其中一些或全部成員字段由類型參數確定。
以下是一個用于從切片中查找元素并返回其索引的類型參數化函數的代碼示例:
func Find[T comparable](slice []T, value T) int { for i, v := range slice { if v == value { return i } } return -1}
這個函數接收一個任意類型的切片和一個具有相同類型的值,并返回第一次出現該值的索引。類型參數T必須是“comparable”類型,也就是說,它必須是可比較的類型,這是Go泛型的一個限制。
以下是一個用于實現一個類型安全的棧的類型參數化結構體代碼示例:
type Stack[T any] struct { data []T}func (s *Stack[T]) Push(v T) { s.data = append(s.data, v)}func (s *Stack[T]) Pop() (t T, err error) { if len(s.data) == 0 { return t, errors.New("stack is empty") } res := s.data[len(s.data)-1] s.data = s.data[:len(s.data)-1] return res, nil}func main() { var stack Stack[int] stack.Push(1) stack.Push(2) stack.Push(3) item, err := stack.Pop()if err != nil { fmt.Println("Error:", err) } else { fmt.Println("Pop item:", item) } item, err = stack.Pop()if err != nil { fmt.Println("Error:", err) } else { fmt.Println("Pop item:", item) }}
這個結構體表示棧,其中T是元素類型,并且在Push和Pop函數中使用。注意,這里的類型參數T沒有任何限制,因此可以傳遞任何類型。var stack Stack[int] 在初始化實例時,就把類型設置好了。
以上是一些示例代碼,展示了Go泛型的使用。在復雜的程序中,泛型的使用可以使代碼更加通用、易于閱讀、安全且具有更高的重用性。
Go語言的泛型實現與其他編程語言(如Java、C++、C#等)的泛型實現有一些不同的地方。以下是它們在一些方面的對比:
總的來說,Go泛型的實現方式比較簡單、靈活,但在性能方面有些損失。但同時,Go語言也在持續地改進其泛型實現,以提高其性能,并加入更多的功能特性。
以下代碼是Go中用泛型實現Set無序集合,包含了添加,刪除,是否存在,轉成列表等方法。
type Set[T comparable] struct { m map[T]struct{}}func (s *Set[T]) Add(t T) { s.m[t] = struct{}{}}func (s *Set[T]) Remove(t T) { delete(s.m, t)}func (s *Set[T]) Exist(t T) bool { _, ok := s.m[t] return ok}func (s *Set[T]) List() []T { t := make([]T, len(s.m)) var i int for k := range s.m { t[i] = k i++ } return t}func (s *Set[T]) ForEach(f func(T)) { for k, _ := range s.m { f(k) }}
Go泛型的出現,使得我們可以更加通用、安全且具有更高的重用性。它的出現具有以下優勢:
在Golang中,泛型功能的引入提高了Go的通用性、可讀性和安全性。使用類型參數化的方式,我們可以編寫出可以處理任何類型的代碼。盡管Go泛型的實現方式略有不同于其他語言,但仍然可以為程序員提供實用的工具和功能,使代碼更加通用、安全、易讀和易于維護。
本文鏈接:http://www.tebozhan.com/showinfo-26-10439-0.htmlGo語言進化之路:泛型的崛起與復用的新篇章
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com