? ? Go語言在v1.18版本添加。其中泛即通用的意思。
12.1 泛型函數(shù)
? ? 如果沒有泛型,同一種類型需要使用重載功能,但Go又不支持重載功能。如果寫一個簡單的加法計算函數(shù),我們只能定義出如下所示的代碼:
func AddInt(a, b int) int {
return a + b
}
func AddFloat(a, b float64) float64 {
return a + b
}
func JoinString(a, b string) string {
return a + b
}
? ? 從上面代碼可以看出,每個函數(shù)的參數(shù)個數(shù)都一樣,僅僅參數(shù)的數(shù)據(jù)類型不一樣,如果還有類似的功能,僅僅是參數(shù)數(shù)據(jù)類型不一致,就還得增加函數(shù),從而導致代碼存在大量的重復。那么有沒有一種,可以將參數(shù)的數(shù)據(jù)類型再抽象一次來解決這樣的問題,我們來看看以下代碼
func Add(a,b T) T{
return a+b
}
? ? 上面代碼中的T表示類型形參,即參數(shù)的數(shù)據(jù)類型是可變的,a,b T則表明a和b的類型要一致。即在運行時,T最終一定會確定是某一種數(shù)據(jù)類型。Go語言針對這一問題的解決方案是泛型。其語法格式如下所示:
func name[T,P](parameters-1 T,parameters-2 P) P{
函數(shù)語句塊
}
? ? 示例代碼如下所示:
package main
import "fmt"
func Add[T int | float64 | string](a, b T) T {
return a + b
}
func main() {
fmt.Printf("int: %v,%[1]T\n", Add(4, 5))
fmt.Printf("float: %v,%[1]T\n", Add(4.7, 5.8))
fmt.Printf("string: %v,%[1]T\n", Add("Sur", "pass"))
}
? ? 運行結果如下所示:
int: 9,int
float: 10.5,float64
string: Surpass,string
? ? 通過以上代碼,可以看到代碼量大大減小,而且也非常簡潔。
- T: 稱為類型形參,僅僅是一個類型占位符
-
int | float64 | string: 稱為類型約束,其中
|表示或的意思 - T int | float64 | string:稱為類型參數(shù)列表,存在多個時,使用逗號進行分隔,例如 [T int | string , P string | bool]
- Add[T]:即泛型函數(shù)
- Add[int]: 即類型實參,傳入int給泛型函數(shù)的過程稱為泛型函數(shù)實例化
- Add[int](4,5) 可以簡寫為 Add(4,5),因為可以根據(jù)傳入的參數(shù)自動推斷出數(shù)據(jù)類型
- 定義泛型函數(shù)時,函數(shù)名后面緊跟著類型參數(shù)列表。因此匿名函數(shù)不可以定義為泛型函數(shù),但可通過使用定義的類型開通T
12.2 類型約束
? ? 類型約束是一個接口。為支持泛型,Go v1.18對接口進行了語法擴展。用在泛型中,接口含義就是符合這些特征的類型集合。在Go語言中,內置了兩個約束:
- any: 表示任意類型
- comparable: 表示類型的值可以使用
==和!=進行比較
[T int ] 等價于[T interface{int}] // 表示T只能是int類型
type Constraint interface{
int | string
}
[T int | string ] 、 [ T interface{int | string }] 和 [T Constraint] 三者等價,都表示類型只能是int或string類型
12.3 泛型類型
? ? 示例代碼如下所示:
package main
import "fmt"
// 接口從本質上講,就是一種類型約束,如果要使用接口類型,則要求其結構必須接口中所有的方法
type Runer interface {
Run()
}
// 定義一種新的Map類型,要求Key的數(shù)據(jù)類型為int或string,而value為Runner類型
// [K string | int, V Runer] 對自定義類型的參數(shù)進行約束
type MyMap[K string | int, V Runer] map[K]V
type MyString string // 類型新定義
func (myString MyString) Run() {
fmt.Printf("MyString:%#v - %[1]T\n", myString)
}
func main() {
// var m MyMap[string, MyString] = make(MyMap[string, MyString])
d := make(MyMap[string, MyString]) //相當于map[string]MyString{}
fmt.Printf("d: %#v - %[1]T\n", d)
d["name"] = "Surpass"
fmt.Printf("d: %#v - %[1]T\n", d)
d["name"].Run()
}
? ? 運行結果如下所示:
d: main.MyMap[string,main.MyString]{} - main.MyMap[string,main.MyString]
d: main.MyMap[string,main.MyString]{"name":"Surpass"} - main.MyMap[string,main.MyString]
MyString:"Surpass" - main.MyString
? ? 結合以上代碼,泛型是對函數(shù)的參數(shù)或返回值設置多個數(shù)據(jù)類型,比普通函數(shù)更靈活的設置參數(shù)類型和返回值類型,但卻又比不上空接口參數(shù)的開放自由。
? ? 既然泛型比不上空接口,那為什么還要引入泛型呢?我們知道空接口參數(shù)不受數(shù)據(jù)類型限制,但如果在調用過程中,函數(shù)傳入的參數(shù)是無法處理的數(shù)據(jù)類型,則很容易導致出現(xiàn)異常情況。而使用泛型時,既可以對函數(shù)的參數(shù)和返回值數(shù)據(jù)類型進行約束,也能保證傳入?yún)?shù)的多樣性。