goland 泛型初嘗試

go的1.18版本在3月15這天正式發(fā)布了 release notes,雖然在bate版本就可以嘗試了,畢竟那時(shí)候還沒(méi)正式發(fā)布,也就沒(méi)去嘗試了,現(xiàn)在正式發(fā)布了,馬上就更了嘗試一下。

總算是千呼萬(wàn)喚始出來(lái),其實(shí)我對(duì)Go的泛型還是挺期待的,畢竟用 interface 能實(shí)現(xiàn)一些東西,但是還是不如原生支持來(lái)得好,用interface首先會(huì)因?yàn)轭?lèi)型轉(zhuǎn)換帶來(lái)一些性能損失,用interface做類(lèi)型強(qiáng)轉(zhuǎn),有時(shí)候會(huì)因?yàn)椴恍⌒膶?xiě)錯(cuò)了,導(dǎo)致一些運(yùn)行時(shí)錯(cuò)誤。要是支持泛型,這些問(wèn)題就會(huì)解決了,一些通用的庫(kù)就會(huì)更好用了。

當(dāng)然泛型也不是完美無(wú)缺的,泛型會(huì)導(dǎo)致語(yǔ)言變得復(fù)雜,大型項(xiàng)目的代碼可能會(huì)更難讀、難理解了,這也會(huì)導(dǎo)致編譯器實(shí)現(xiàn)起來(lái)更復(fù)雜。但是優(yōu)勢(shì)還是大于劣勢(shì)的

這里想罵一句,其他語(yǔ)言的泛型一般都是用 <> go 的泛型用 [] 搞不懂為啥不用 <> 非要獨(dú)樹(shù)一幟,打不過(guò)就加入,你說(shuō)用啥就用啥吧

先通過(guò)幾個(gè)簡(jiǎn)單的例子來(lái)練習(xí)一下

泛型函數(shù)

func main() {
    fmt.Println(Max[int](1, 2))
    fmt.Println(Max(1.5, 2.6))
    fmt.Println(Max("abc", "efg"))
}

func Max[T int | float64 | string](a, b T) T {
    if a > b {
        return a
    }
    return b
}

通過(guò)泛型,實(shí)現(xiàn)了一個(gè)獲取大值的函數(shù),如果沒(méi)有泛型,就要手寫(xiě)三個(gè)不同類(lèi)型的函數(shù)。
在使用泛型函數(shù)的時(shí)候,fmt.Println(Max[int](1, 2))fmt.Println(Max(1.5, 2.6)) 兩種寫(xiě)法都是可以的。可以指定類(lèi)型,也可以不指定,讓編譯器自己類(lèi)型推導(dǎo)

Max函數(shù)的參數(shù)可不是任意類(lèi)型的參數(shù)都可以傳入,只能傳入int float64 string 這三種類(lèi)型,因?yàn)槎x函數(shù)的時(shí)候,類(lèi)型參數(shù)就只有這三種。泛型不是任意類(lèi)型的,也是有類(lèi)型的,這就是泛型的類(lèi)型約束

泛型約束

func main() {
    x := uint32(10)
    y := uint32(20)
    fmt.Println(Max(x, y))
}

func Max[T int | float64 | string](a, b T) T {
    if a > b {
        return a
    }
    return b
}

上面這段代碼是無(wú)法通過(guò)編譯的,會(huì)有如下的報(bào)錯(cuò) uint32 does not implement int|float64|string
因?yàn)?Max 只允許 int float64 string 這三種

還有一種情況是無(wú)法通過(guò)編譯的

type Myint int

func main() {
    x := Myint (10)
    y := Myint (20)
    fmt.Println(Max(x, y))
}

func Max[T int | float64 | string](a, b T) T {
    if a > b {
        return a
    }
    return b
}

這個(gè)也是無(wú)法通過(guò)編譯的,雖然 Myintint 類(lèi)型的,但是依然無(wú)法通過(guò)編譯。這種情況是可以在定義時(shí),修改一下就行了

type Myint int

func main() {
    x := Myint (10)
    y := Myint (20)
    fmt.Println(Max(x, y))
}

func Max[T ~int | ~float64 | ~string](a, b T) T {
    if a > b {
        return a
    }
    return b
}

在定義函數(shù)時(shí),只要在類(lèi)型前加上 ~ 就可以了。

每次在定義泛型約束時(shí),都要寫(xiě)一堆類(lèi)型好麻煩啊,go 官方也想到了,就是定義約束類(lèi)型

type MyGenerics interface {
    ~int | ~float64 | ~string
}

func Max[T MyGenerics](a, b T) T {
    if a > b {
        return a
    }
    return b
}

這樣就完成了一個(gè)泛型的約束類(lèi)型的定義

而且go的泛型約束也可以像 interface 那樣相互嵌套的

type Int interface {
    ~int | ~uint
}

type Float interface {
    ~float32 | ~float64
}

type Number interface {
    Int | Float
}

func Max2[T Number](a, b T) T {
    if a > b {
        return a
    }
    return b
}

Go內(nèi)置一個(gè) constraints包,定義好了一些常用的泛型約束

type Signed interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64
}

type Unsigned interface {
    ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}

type Integer interface {
    Signed | Unsigned
}

type Float interface {
    ~float32 | ~float64
}

type Complex interface {
    ~complex64 | ~complex128
}

type Ordered interface {
    Integer | Float | ~string
}

go 還定義了一個(gè) 類(lèi)型約束 any 看這個(gè)名字應(yīng)該就知道什么意思了,就是任意類(lèi)型,這個(gè)也沒(méi)啥神秘的,就是給 interface 整了一個(gè)別名

type any interface{}

go 還有一種約束類(lèi)型,函數(shù)類(lèi)型約束,也就是只有實(shí)現(xiàn)了 對(duì)應(yīng)函數(shù),才能作為泛型類(lèi)型

type MyToString interface {
    ToString() string
}

type Myint int

func (m Myint) ToString() string {
    return strconv.Itoa(int(m))
}

func MyString[T MyToString](value T) {
    fmt.Println(value.ToString())
}

func main() {
    var x Myint
    x = 100
    MyString[Myint](x)
}

在定義函數(shù) MyString 函數(shù)的時(shí)候,MyString[T MyToString](value T) 函數(shù)的泛型約束時(shí)一個(gè)接口,函數(shù) MyString 的類(lèi)型約束就是 實(shí)現(xiàn)了 MyToString 接口的類(lèi)型。所以傳入函數(shù)的 T 類(lèi)型的 value 一定會(huì)有 ToString 這個(gè)函數(shù)。

泛型結(jié)構(gòu)體

func main() {
    stack := NewStack[int](10)

    for i := 0; i < 10; i++ {
        stack.Push(i)
    }

    for i := 0; i < 10; i++ {
        fmt.Printf("%d ", stack.Pop())
    }
}

func NewStack[T any](size int) *Stack[T] {
    stack := &Stack[T]{
        arr: make([]T, size),
    }
    stack.size = size
    stack.index = -1
    return stack
}

type Stack[T any] struct {
    arr   []T
    size  int
    index int
}

func (s *Stack[T]) Full() bool {
    return s.index+1 == s.size
}

func (s *Stack[T]) Empty() bool {
    return s.index < 0
}

func (s *Stack[T]) Push(v T) bool {
    if s.Full() {
        return false
    }
    s.index++
    s.arr[s.index] = v
    return true
}

func (s *Stack[T]) Pop() T {
    var v T
    if s.Empty() {
        return v
    }
    v = s.arr[s.index]
    s.index--
    return v
}

用泛型寫(xiě)了一個(gè)簡(jiǎn)單的棧

type Stack[T any] struct {
    arr   []T
    size  int
    index int
}

在定義結(jié)構(gòu)體的同時(shí)指定了泛型類(lèi)型,這樣就定義了泛型的結(jié)構(gòu)體

目前結(jié)構(gòu)體的泛型還是有一些限制,比如結(jié)構(gòu)的 method 不能是泛型的

type Mystruct struct {
}

func (m Mystruct) One[T ~int | ~float64](value T) {
    fmt.Println(value)
}

這段代碼是編譯不過(guò)的,后續(xù)可能會(huì)完善這部分吧

end

go的泛型總算是正式發(fā)布了,趁周末學(xué)習(xí)了一波?,F(xiàn)在go泛型剛發(fā)布,工作中用上泛型可能還要等很長(zhǎng)時(shí)間,畢竟線(xiàn)上的東西穩(wěn)定最重要。我記得我剛?cè)肼毠镜臅r(shí)候,那時(shí)候公司用的go版本還是 1.4, 那時(shí)候go 已經(jīng)到了 1.13了。后來(lái)因?yàn)閐lv調(diào)試不兼容低版本的go,就逐步升上來(lái)了。相信go的泛型肯定是一個(gè)趨勢(shì),后續(xù)很多的內(nèi)置酷和第三方庫(kù)都會(huì)用上泛型了。
先學(xué)到這里吧,等有空繼續(xù)學(xué)習(xí)go泛型的更多知識(shí),比如底層是怎樣實(shí)現(xiàn)的,是java那樣類(lèi)型擦除還是C++那樣代碼展開(kāi)的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容