• <menu id="sssag"></menu>
  • <menu id="sssag"></menu>
  • go泛型教程

    泛型

    原文
    在線閱讀

    導讀:

    • 約束
    • 使用方法
    • 實現原理
    • 跟其它語言的泛型進行對比
    • 用例子學泛型
    • issues

    泛型需滿足 go1.18+

    約束

    go使用interface作為約束,約束的意思是約束了這個泛型都具有哪些實際類型。所以可以理解為,go將interface的職責給擴展了,讓接口不僅僅作為接口 --- 解耦的,抽象化的結構體,還具有了約束,對于類型的約束作用。

    type st interface{
      int | string
    }
    

    這里 st約束擁有int和string,請注意這里的st是約束,不是泛型類型

    go內置了很多約束,比如說 any 和 comparable ,意思是任何類型和可以比較的類型。以后應該會有一個新的內置約束的包叫做package constraints 例如any comparable ,Ordered 等等約束都會內置到標準庫中

    約束不僅僅可以單獨寫出來,還可以內置于函數內部。

    func Age[T int| string,B float64| string](i T,j B){}
    

    這種形式下,T 和 B 的約束就是僅限此函數使用

    下面我們看一種形式,這種情況下約束的不僅僅是string和int,而是包含了底層是他們的所有數據,比如說 type DD int 也符合這個約束,請記住只能使用在底層類型上,如果使用~DD是不被允許的

    type st interface{
    	~string | ~int
    }
    

    于此同時,約束也不僅僅是基礎類型,約束的內容是方法也是可以的

    
    func ConcatTo[S Stringer, P Plusser](s []S, p []P) []string {
    	r := make([]string, len(s))
    	for i, v := range s {
    		r[i] = p[i].Plus(v.String())
    	}
    	return r
    }
    
    type Plusser interface {
    	Plus(string) string
    }
    type Stringer interface {
    	String() string
    }
    
    

    所有說這里就可以看出來,在引入泛型之后,go的interface的功能擴充了。

    約束跟接口是一樣的也是可以嵌套的

    type ComparableHasher interface {
    	comparable
    	Hash() uintptr
    }
    
    // or
    
    type ImpossibleConstraint interface {
    	comparable
    	[]int
    }
    

    這里的意義就是 and的意思 就是說這個約束是可以比較的還是必須得支持hash()uintptr

    下面這種方法也是可以的

    type NumericAbs[T any] interface {
    	~int | ~int8 | ~int16 | ~int32 | ~int64 |
    		~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
    		~float32 | ~float64 |
    		~complex64 | ~complex128
    	Abs() T
    }
    

    上面的類型意思是滿足數字類型,下面的意思是滿足這個方法,所以最終實現這個約束,就是一個對象是數字類型,并且實現了這個接口

    那么這里有一個疑問,給約束嵌入泛型,應該如何操作

    type EmbeddedParameter[T any] interface {
    	T 
    }
    
    

    cannot embed a type parameter 這種方法,go不允許這么做。因為T已經是泛型了,而約束里的類型應該是實際類型,所以T不能這么用,

    不過如果約束里面是方法就可以這么做,這是因為T 這里只是方法的一個參數,比如說

    type EmbeddedParameter[T any] interface {
    	~int | ~uint 
    	me() T 
    

    如果想使用這種約束,可以這么使用

    func Abs[T EmbeddedParameter[T]](t T)T{}
    

    解釋一下,中括號里面泛型的兩個T表達的意思是不一樣的,后面的T表達的是 約束里的泛型,表示 any,前面的T表示的是滿足后面的這個約束的類型T,但是這里注意,后面T雖然之前定義的時候是any但是這里被改變了,改變為了必須滿足約束 EmbeddedParameter的類型,如果說的通俗點,從any變成了,滿足 int | uint and 實現 me()T方法 后文會有代碼進行解釋。

    當然了,后面的T沒有也行,如果沒有后面的T就是相當于不改變后面的T的約束類型了

    type Differ[T1 any] interface {
    	Diff(T1) int
    }
    
    func IsClose[T2 Differ](a, b T2) bool {
    	return a.Diff(b) < 2
    }
    
    

    當結構體中使用泛型的時候,泛型可以直接作為嵌入使用

    type Lockable[T any] struct {
    	T
    	mu sync.Mutex
    }
    

    請注意,type a[T any] interface 這種寫法有可能在go1.18還不支持

    當使用了泛型之后,是無法使用斷言的,這是非法的,那么如果一定要在運行時的時候去判斷類型怎么辦呢?答案就是轉變成interface{}即可,因為我們知道任何對象都已經實現了空接口,那么就可以被空接口去轉化

    func GeneralAbsDifference[T Numeric](a, b T) T {
    	switch (interface{})(a).(type) {
    	case int, int8, int16, int32, int64,
    		uint, uint8, uint16, uint32, uint64, uintptr,
    		float32, float64:
    		return OrderedAbsDifference(a, b) 
    	case complex64, complex128:
    		return ComplexAbsDifference(a, b) 
    	}
    }
    

    下面看一下別名的真實類型是泛型的情況

    type A[T any] []T
    
    type AliasA = A // 錯誤 ?
    
    type AliasA = A[int] // 正確
    

    其中錯誤的問題是 別名不能直接使用泛型類型 cannot use generic type A[T any] without instantiation,它需要泛型的實例化

    使用方法

    下面展示一下go泛型的基本使用方法

    package main
    
    import "fmt"
    
    func main() {
    	fmt.Printf("%v",Age[int](12))
    }
    
    func Age[T any](t T) T{
    	return t
    }
    

    這是函數使用泛型的寫法,當函數使用泛型的時候,需要在變量前使用中括號標注泛型的具體約束,然后后面才能使用這個泛型類型,使用泛型函數的時候,中括號是可以省略的Age(12) 系統會自動推算泛型的具體實現。順便說一下,泛型類型使用%v作為占位符,泛型類型無法進行斷言,這一點跟interface{}不同。

    當然了,我么也可以不用any,自定義一個約束

    package main
    
    import "fmt"
    
    func main() {
    	Age[int](12)
    }
    type st interface{
      int | string
    }
    func Age[T st](t T) {
    	fmt.Println((t))
    }
    

    看完了在函數內的泛型,我們在看一下在方法中如何使用泛型

    package main
    
    import "fmt"
    
    func main() {
    	new(Age[int]).Post(12)
    
    	var dd DD[int]
    	dd.TT(12)
    }
    
    type Age[T any] struct {
    	I T
    }
    
    func (a *Age[T]) Post(t T) {
    	fmt.Println(a.I, t)
    }
    
    type DD[T any] []T
    
    func(dd *DD[T])TT(t T){
    	fmt.Println(t,len(*dd))
    }
    

    在 age 結構體聲明的時候,聲明了一個泛型 T ,在struct體內就可以使用這個T,值得注意的是,這個結構體方法內部僅可以使用定義在這個結構體對象上的泛型

    下面是一個錯誤案例

    func (a *Age[T])Post[B any](t T,b B) {
    	fmt.Println(a.I, t)
    } 
    

    syntax error: method must have no type parameters

    接下來我們看一下,如何使用 type a[T any] interface{} 有類型也有方法的泛型結構

    package main
    
    import "fmt"
    
    func main() {
    	var d DDD
    	var i DDD
    	d = 1
    	i = 2
    	io := AbsDifference[DDD](d, i)
    	fmt.Println(io)
    }
    
    type DDD int
    
    func (ddd DDD) Abs() DDD {
    	return ddd + ddd
    }
    
    type NumericAbs[T any] interface {
    	~int | ~int8 | ~int16 | ~int32 | ~int64 |
    		~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
    		~float32 | ~float64 |
    		~complex64 | ~complex128
    	Abs() T
    }
    
    // AbsDifference computes the absolute value of the difference of
    // a and b, where the absolute value is determined by the Abs method.
    func AbsDifference[T NumericAbs[T]](a, b T) T {
    	d := a - b
    	return d.Abs()
    }
    

    實現原理

    泛型的第一種方法是在編譯這個泛型時,使用一個字典,里面包含了這個泛型函數的全部類型信息,然后當運行時,使用函數實例化的時候從這個字典中取出信息進行實例化即可,這種方法會導致執行性能下降,一個實例化類型int, x=y可能通過寄存器復制就可以了,但是泛型必須通過內存了(因為需要map進行賦值),不過好處就是不浪費空間

    還有一種方法就是把這個泛型的所有類型全部提前生成,這種方法也有一個巨大的缺點就是代碼量直線上升,如果是一個包的情況下還能根據具體的函數調用去實現該實現的類型,如果是包輸出的的情況下,那么就得不得不生成所有的類型。

    所以將兩者結合在一起或許是最好的選擇。

    這種方法是這樣的,如果類型的內存分配器/垃圾回收器呈現的方式一致的情況下,只給它生成一份代碼,然后給它一個字典來區分不同的具體行為,可以最大限度的平衡速度和體積

    跟其它語言的泛型進行對比

    • c語言:本身不具有泛型,需要程序員去實現一個泛型,實現復雜,但是不增加語言的復雜度(換言之只增加了程序員的)
    • c++和rust:跟go基本保持一種方式,就是增加編譯器的工作量
    • Java:將泛型裝箱為object,在裝箱和拆箱擦除類型的過程中,程序執行效率會變低

    用例子學泛型

    理論學習完了,不使用例子進行復習的話會忘的很快的。跟著我看幾個例子吧

    例子一: 函數泛型 map-filter-reduce

    package main
    
    import (
    	"fmt"
    )
    
    func main() {
    	vM := Map[int]([]int{1, 2, 3, 4, 5}, func(i int) int {
    
    		return i + i
    	})
    	fmt.Printf("map的結果是:%v", vM)
    	vF := Filter[int]([]int{1, 2, 3, 4, 5}, func(t int) bool {
    		if t > 2 {
    			return true
    		}
    		return false
    	})
    	fmt.Printf("filter的結果是:%v", vF)
    	vR := Reduce[Value, *Result]([]Value{
    		{name: "tt", year: 1},
    		{name: "bb", year: 2},
    		{name: "7i", year: 3},
    		{name: "8i", year: 4},
    		{name: "u4i", year: 5},
    		{name: "uei", year: 6},
    		{name: "uwi", year: 7},
    		{name: "uti", year: 8},
    	}, &Result{}, func(r *Result, v Value) *Result {
    		r.value = r.value + v.year
    		return r
    	})
    	fmt.Println("reduce的結果是:", vR.value)
    
    }
    
    // Map:類似于洗菜,進去的菜和出來的菜不一樣了所以需要兩種種類
    func Map[T1, T2 any](arr []T1, f func(T1) T2) []T2 {
    	result := make([]T2, len(arr))
    	for k, v := range arr {
    		result[k] = f(v)
    	}
    	return result
    }
    
    // Filter:類似于摘菜,進去的菜和出來的菜是一種,不過量減少了
    func Filter[T any](arr []T, f func(T) bool) []T {
    	var result []T
    	for _, v := range arr {
    		if f(v) {
    			result = append(result, v)
    		}
    	}
    	return result
    }
    
    // Reduce:類似于做菜,將菜做成一道料理,所以需要兩種類型
    func Reduce[T1, T2 any](arr []T1, zero T2, f func(T2, T1) T2) T2 {
    	result := zero
    	for _, v := range arr {
    		result = f(result, v)
    	}
    	return result
    }
    
    type Value struct {
    	name string
    	year int
    }
    type Result struct {
    	value int
    }
    

    map的結果是:[2 4 6 8 10] filter的結果是:[3 4 5] reduce的結果是: 36

    例子二: 方法上的泛型 sets

    package main
    
    import (
    	"fmt"
    )
    
    func main() {
    
    	// 這里 Sets的具體類型和Make的具體類型都是int,所以可以正常賦值
    	var s Sets[int] = Make[int]()
    	//
    	s.Add(1)
    	s.Add(2)
    	fmt.Println(s)
    	fmt.Println(s.Contains(3))
    	fmt.Println(s.Len())
    	s.Iterate(func(i int) {
    		fmt.Println(i)
    	})
    	fmt.Println(s)
    	s.Delete(2)
    	fmt.Println(s)
    }
    
    // Sets 一個key  存儲對象
    type Sets[T comparable] map[T]struct{}
    
    // Make 實例化一個map
    func Make[D comparable]() Sets[D] {
    	// 泛型就像一個管道一樣,只要實例化的時候管子里的東西一致,那么就是一根管子
    	return make(Sets[D])
    }
    
    // Add 向這個sets添加內容
    func (s Sets[T]) Add(t T) {
    	s[t] = struct{}{}
    }
    
    // delete ,從這個sets中刪除內容
    func (s Sets[T]) Delete(t T) {
    	delete(s, t)
    }
    
    //  Contains 播報t是否屬于這個sets
    func (s Sets[T]) Contains(t T) bool {
    	_, ok := s[t]
    	return ok
    }
    
    //Len sets擁有的長度
    
    func (s Sets[T]) Len() int {
    	return len(s)
    }
    
    // Iterate 迭代器,并且給予每個元素功能
    
    func (s Sets[T]) Iterate(f func(T)) {
    	for k := range s {
    		f(k)
    	}
    }
    

    map[1:{} 2:{}] false 2 1 2 map[1:{} 2:{}] map[1:{}]

    例子三: 外部定義的約束 實現一個sort接口類型

    package main
    
    import "fmt"
    
    func main() {
    	fmt.Println("Hello, 世界")
    }
    // ~ 代表只要底層滿足這些類型也可以算滿足約束
    type Ordered interface {
    	~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uintptr |
    		~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 |
    		~float32 | ~float64 | ~string
    }
    type orderedSlice[T Ordered] []T
    
    func (s orderedSlice[T]) Len() int           { return len(s) }
    func (s orderedSlice[T]) Less(i, j int) bool { return s[i] < s[j] }
    func (s orderedSlice[T]) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
    func OrderedSlice[T Ordered](s []T) {
    	sort.Sort(orderedSlice[T](s))
    }
    

    issues

    問題一: 關于泛型中的零值

    在go里面對泛型的零值并沒有一個所謂的泛型零值可以使用,需要根據不同的實踐去實現,比如

    package main
    
    import "fmt"
    
    func main() {
    	
    }
    
    type Aget[T any] struct {
    	t *T
    }
    // 根據實際判斷,如果a的t不等于nil再返回,如果是nil就返回一個T類型的nil(意思就是只聲明)
    func (a *Aget[T]) Approach() T {
    	if a.t != nil { 
    		return *a.t
    	}
    	var r T
    	return r
    }
    
    

    實際上目前(1.18還沒發布),還沒一個確切的泛型的零值,那么我們要做的只能是按照實際來具體分析,按照提案,以后有可能使用return ... return _ return return nil return T{} 這些都是可能的結果,我個人比較喜歡 return T{} 來表示泛型的零值,或許在go1.19或者go2的時候能實現,拭目以待吧。

    問題二: 無法識別使用了底層數據的其它類型

    type Float interface {
    	~float32 | ~float64
    }
    
    func NewtonSqrt[T Float](v T) T {
    	var iterations int
    	switch (interface{})(v).(type) {
    	case float32:
    		iterations = 4
    	case float64:
    		iterations = 5
    	default:
    		panic(fmt.Sprintf("unexpected type %T", v))
    	}
    	// Code omitted.
    }
    
    type MyFloat float32
    
    var G = NewtonSqrt(MyFloat(64))
    

    這里約束 Float擁有的約束類型是 ~float32float64當在switch中定義了float32和flaot64時,無法識別下面的新類型 MyFloat即使它的底層時 float32 ,go的提議是以后在switch中使用 case ~float32: 來解決這個問題,目前尚未解決這個問題

    問題三: 即便約束一致,類型也是不同的

    func Copy[T1, T2 any](dst []T1, src []T2) int {
    	for i, x := range src {
    		if i > len(dst) {
    			return i
    		}
    		dst[i] = T1(x) // x 是 T2類型 不能直接轉化為 T1類型
    	}
    	return len(src)
    }
    
    

    T1,和T2 雖然都是any的約束,但是啊,它不是一個類型??!

    Copy[int,string]() // 這種情況下,你能說可以直接轉化嗎???
    

    這種代碼可以更改一下

    dst[i]= (interface{})(x).(T1)
    

    確認是一種類型以后才能轉化

    參考資料

    posted @ 2022-03-07 20:10  科科人神  閱讀(117)  評論(0編輯  收藏  舉報
    国产在线码观看超清无码视频,人妻精品动漫H无码,十大看黄台高清视频,国产在线无码视频一区二区三区,国产男女乱婬真视频免费,免费看女人的隐私超爽,狠狠色狠狠色综合久久蜜芽