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ò)編譯的,雖然 Myint 是 int 類(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)的。