在本節(jié)中,您將添加通用函數(shù)調(diào)用的修改版本,進行小的更改以簡化調(diào)用代碼。您將刪除在這種情況下不需要的類型參數(shù)。
網(wǎng)站建設(shè)哪家好,找成都創(chuàng)新互聯(lián)公司!專注于網(wǎng)頁設(shè)計、網(wǎng)站建設(shè)、微信開發(fā)、微信小程序、集團企業(yè)網(wǎng)站建設(shè)等服務(wù)項目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了黃梅免費建站歡迎大家使用!
當 Go 編譯器可以推斷您要使用的類型時,您可以在調(diào)用代碼中省略類型參數(shù)。編譯器從函數(shù)參數(shù)的類型推斷類型參數(shù)。
請注意,這并不總是可能的。例如,如果您需要調(diào)用沒有參數(shù)的泛型函數(shù),則需要在函數(shù)調(diào)用中包含類型參數(shù)。
在 main.go 中,在您已有的代碼下方,粘貼以下代碼。
在此代碼中:
(1)調(diào)用泛型函數(shù),省略類型參數(shù)。
從包含 main.go 的目錄中的命令行,運行代碼。
接下來,您將通過將整數(shù)和浮點數(shù)的并集捕獲到您可以重用的類型約束(例如從其他代碼中)來進一步簡化函數(shù)。
正如您將在本節(jié)中看到的,約束接口也可以引用特定類型。
1、編寫代碼
在此代碼中:
b.在您已有的函數(shù)下方,粘貼以下通用 SumNumbers函數(shù)。
在此代碼中:
c.在 main.go 中,在您已有的代碼下方,粘貼以下代碼。
在此代碼中:
(1)調(diào)用SumNumbers打印每個map的總和。
與上一節(jié)一樣,在調(diào)用泛型函數(shù)時省略了類型參數(shù)(方括號中的類型名稱)。Go 編譯器可以從其他參數(shù)推斷類型參數(shù)。
從包含 main.go 的目錄中的命令行,運行代碼。
做得很好!您剛剛學習了 Go 中的泛型。
這幾天golang社區(qū)對泛型的討論非常多的,一片熱火朝天的景象。對我們廣大gopher來說總歸是好事。
泛型很有可能會顛覆我們之前的很多設(shè)計,帶著這種疑問和沖動,我準備嘗試用golang泛型實現(xiàn)幾個orm的常見功能。
本文并沒完全實現(xiàn)通用的orm,只是探討其實現(xiàn)的一種方式提供各位讀者做借鑒。
雖然golang有了泛型,但是目前在標準庫sql底層還沒有改造,目前還有很多地方需要用到reflect。
調(diào)用方式
這個部分跟傳統(tǒng)的orm使用上沒有太大區(qū)別,沒辦法不使用反射的情況下,泛型的方式可能變得有點繁瑣。
調(diào)用方式
和創(chuàng)建table類似,寫入數(shù)據(jù)好像比沒有之前的orm有優(yōu)勢。
讀取數(shù)據(jù)是非常高頻的操作,所以我們稍作封裝。
調(diào)用方式
稍微比原先的orm方式有了多一點想象空間,比如 在[T any]做更明確的約束,比如要求實現(xiàn)Filter定制方法。
鑒于本人能力還認證有限,目前還沒有發(fā)現(xiàn)泛型對orm劇烈的改進和突破的可能。未來如果go對底層sql做出改動,或者實現(xiàn)諸如Rust那種Enum方式,可能會帶來更多的驚喜。
Golang團隊認為在類型系統(tǒng)和運行時的復雜性花費太大,還沒找到可以和這個復雜性相抵的良好設(shè)計。內(nèi)置的map和slice其實都有泛型的味道,加上可以用interface{}來構(gòu)造容器,可以達到泛型的效果。所以目前為止還沒有直接的支持泛型。
Go 由于不支持泛型而臭名昭著,但最近,泛型已接近成為現(xiàn)實。Go 團隊實施了一個看起來比較穩(wěn)定的設(shè)計草案,并且正以源到源翻譯器原型的形式獲得關(guān)注。本文講述的是泛型的最新設(shè)計,以及如何自己嘗試泛型。
例子
FIFO Stack
假設(shè)你要創(chuàng)建一個先進先出堆棧。沒有泛型,你可能會這樣實現(xiàn):
type?Stack?[]interface{}func?(s?Stack)?Peek()?interface{}?{
return?s[len(s)-1]
}
func?(s?*Stack)?Pop()?{
*s?=?(*s)[:
len(*s)-1]
}
func?(s?*Stack)?Push(value?interface{})?{
*s?=?
append(*s,?value)
}
但是,這里存在一個問題:每當你 Peek 項時,都必須使用類型斷言將其從 interface{} 轉(zhuǎn)換為你需要的類型。如果你的堆棧是 *MyObject 的堆棧,則意味著很多 s.Peek().(*MyObject)這樣的代碼。這不僅讓人眼花繚亂,而且還可能引發(fā)錯誤。比如忘記 * 怎么辦?或者如果您輸入錯誤的類型怎么辦?s.Push(MyObject{})` 可以順利編譯,而且你可能不會發(fā)現(xiàn)到自己的錯誤,直到它影響到你的整個服務(wù)為止。
通常,使用 interface{} 是相對危險的。使用更多受限制的類型總是更安全,因為可以在編譯時而不是運行時發(fā)現(xiàn)問題。
泛型通過允許類型具有類型參數(shù)來解決此問題:
type?Stack(type?T)?[]Tfunc?(s?Stack(T))?Peek()?T?{
return?s[len(s)-1]
}
func?(s?*Stack(T))?Pop()?{
*s?=?(*s)[:
len(*s)-1]
}
func?(s?*Stack(T))?Push(value?T)?{
*s?=?
append(*s,?value)
}
這會向 Stack 添加一個類型參數(shù),從而完全不需要 interface{}?,F(xiàn)在,當你使用 Peek() 時,返回的值已經(jīng)是原始類型,并且沒有機會返回錯誤的值類型。這種方式更安全,更容易使用。(譯注:就是看起來更丑陋,^-^)
此外,泛型代碼通常更易于編譯器優(yōu)化,從而獲得更好的性能(以二進制大小為代價)。如果我們對上面的非泛型代碼和泛型代碼進行基準測試,我們可以看到區(qū)別:
type?MyObject?struct?{
X?
int
}
var?sink?MyObjectfunc?BenchmarkGo1(b?*testing.B)?{
for?i?:=?0;?i??b.N;?i++?{
var?s?Stack
s.Push(MyObject{})
s.Push(MyObject{})
s.Pop()
sink?=?s.Peek().(MyObject)
}
}
func?BenchmarkGo2(b?*testing.B)?{
for?i?:=?0;?i??b.N;?i++?{
var?s?Stack(MyObject)
s.Push(MyObject{})
s.Push(MyObject{})
s.Pop()
sink?=?s.Peek()
}
}
結(jié)果:
BenchmarkGo1BenchmarkGo1-16?????12837528?????????87.0?ns/op???????48?B/op????????2?allocs/opBenchmarkGo2BenchmarkGo2-16?????28406479?????????41.9?ns/op???????24?B/op????????2?allocs/op
在這種情況下,我們分配更少的內(nèi)存,同時泛型的速度是非泛型的兩倍。
合約(Contracts)
上面的堆棧示例適用于任何類型。但是,在許多情況下,你需要編寫僅適用于具有某些特征的類型的代碼。例如,你可能希望堆棧要求類型實現(xiàn) String() 函數(shù)