最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Golang泛型與類型約束的用法詳解

 更新時間:2025年04月24日 08:41:29   作者:自由de單車  
這篇文章主要介紹了Golang泛型與類型約束的用法,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教

一、環(huán)境

Go 1.20.2

二、沒有泛型的Go

假設(shè)現(xiàn)在我們需要寫一個函數(shù),實現(xiàn):

1)輸入一個切片參數(shù),切片類型可以是[]int[]float64,然后將所有元素相加的“和”返回

2)如果是int切片,返回int類型;如果是float64切片,返回float64類型

當然,最簡單的方法是寫兩個函數(shù)SumSliceInt(s []int)、SumSliceFloat64(s []float64)來分別支持不同類型的切片,但是這樣會導(dǎo)致大部分代碼重復(fù)冗余,不是很優(yōu)雅。那么有沒有辦法只寫一個函數(shù)呢?

我們知道,在Go中所有的類型都實現(xiàn)了interface{}接口,所以如果想讓一個變量支持多種數(shù)據(jù)類型,我們可以將這個變量聲明為interface{}類型,例如var slice interface{},然后使用類型斷言(.(type))來判斷這個變量的類型。

interface{} + 類型斷言:

// any是inerface{}的別名,兩者是完全相同的:type any = interface{}
func SumSlice(slice any) (any, error) {
	switch s := slice.(type) {
	case []int:
		sum := 0
		for _, v := range s {
			sum += v
		}
		return sum, nil
	case []float64:
		sum := float64(0)
		for _, v := range s {
			sum += v
		}
		return sum, nil
	default:
		return nil, fmt.Errorf("unsupported slice type: %T", slice)
	}
}

從上述代碼可見,雖然使用interface{}類型可以實現(xiàn)在同一個函數(shù)內(nèi)支持兩種不同切片類型,但是每個case塊內(nèi)的代碼仍然是高度相似和重復(fù)的,代碼冗余的問題沒有得到根本的解決。

三、泛型的優(yōu)點

幸運的是,在Go 1.18之后開始支持了泛型(Generics),我們可以使用泛型來解決這個問題:

func SumSlice[T interface{ int | float64 }](slice []T) T {
	var sum T = 0
	for _, v := range slice {
		sum += v
	}
	return sum
}

是不是簡潔了很多?而且,泛型相比interface{}還有以下優(yōu)勢:

  • 可復(fù)用性:提高了代碼的可復(fù)用性,減少代碼冗余。
  • 類型安全性:泛型在編譯時就會進行類型安全檢查,可以確保編譯出來的代碼就是類型安全的;而interface{}是在運行時才進行類型判斷,如果編寫的代碼在類型判斷上有bug或缺漏,就會導(dǎo)致Go在運行過程中報錯。
  • 性能:不同類型的數(shù)據(jù)在賦值給interface{}變量時,會有一個隱式的裝箱操作,從interface{}取數(shù)據(jù)時也會有一個隱式的拆箱操作,而泛型就不存在裝箱拆箱過程,沒有額外的性能開銷。

四、理解泛型

(一)泛型函數(shù)(Generic function)

1)定義

編寫一個函數(shù),輸入a、b兩個泛型參數(shù),返回它們的和:

// T的名字可以更改,改成K、V、MM之類的都可以,只是一般比較常用的是T
// 這是一個不完整的錯誤例子
func Sum(a, b T) T {
	return a + b
}

大寫字母T的名字叫類型形參(Type parameter),代表a、b參數(shù)是泛型,可以接受多種類型,但具體可以接受哪些類型呢?在上面的定義中并沒有給出這部分信息,要知道,并不是所有的類型都可以相加的,因此這里就引出了約束的概念,我們需要對T可以接受的類型范圍作出約束:

// 正確例子
func Sum[T interface{ int | float64 }](a, b T) T {
	return a + b
}

中括號[]之間的空間用于定義類型形參,支持定義一個或多個

  • T:類型形參的名字
  • interface{ int | float64 }:對T的類型約束(Type Constraint),必須是一個接口,約束T只可以是intfloat64

為了簡化寫法,類型約束中的interface{}某些情況下是可以省略的,所以可以簡寫成:

func Sum[T int | float64](a, b T) T {
	return a + b
}

interface{}不能省略的一些情況:

// 當接口中包含方法時,不能省略
func Contains[T interface{ Equal() bool }](num T) {
}

可以定義多個類型形參:

func Add[T int, E float64](a T, b E) E {
	return E(a) + b
}

2)調(diào)用

以上面的Sum泛型函數(shù)為例,完整的調(diào)用寫法為:

Sum[int](1, 2)
Sum[float64](1.1, 2.2)

[]之間的內(nèi)容稱為類型實參(Type argument),是函數(shù)定義中的類型形參T的實際值,例如傳int過去,那么T的實際值就是int。

類型形參確定為具體類型的過程稱為實例化(Instantiations),可以簡單理解為將函數(shù)定義中的T替換為具體類型:

泛型函數(shù)實例化后,就可以像普通函數(shù)那樣調(diào)用了。

大多數(shù)時候,編譯器都可以自動推導(dǎo)出該具體類型,無需我們主動告知,這個功能叫函數(shù)實參類型推導(dǎo)(Function argument type inference)。所以可以簡寫成:

// 簡寫,跟調(diào)用普通函數(shù)一樣的寫法
Sum(1, 2)
Sum(1.1, 2.2)

需要注意的是,在調(diào)用這個函數(shù)時,a、b兩個參數(shù)的類型必須一致,要么兩個都是int,要么都是float64,不能一個是int一個是float64

Sum(1, 2.3) // 編譯會報錯

什么時候不能簡寫?

// 當類型形參T僅用在返回值,沒有用在函數(shù)參數(shù)列表時
func Foo[T int | float64]() T {
	return 1
}
Foo() // 報錯:cannot infer T
Foo[int]() // OK
Foo[float64]() // OK

(二)類型約束(Type constraint)

1)接口與約束

Go 使用interface定義類型約束。我們知道,在引入泛型之前,interface中只可以聲明一組未實現(xiàn)的方法,或者內(nèi)嵌其它interface,例如:

// 普通接口
type Driver interface {
	SetName(name string) (int, error)
	GetName() string
}

// 內(nèi)嵌接口
type ReaderStringer interface {
    io.Reader
    fmt.Stringer
}

接口里的所有方法稱之為方法集(Method set)。

引入泛型之后,interface里面可以聲明的元素豐富了很多,可以是任何 Go 類型,除了方法、接口以外,還可以是基本類型,甚至struct結(jié)構(gòu)體都可以,接口里的這些元素稱為類型集(Type set)

// 基本類型約束
type MyInt interface {
	int
}

// 結(jié)構(gòu)體類型約束
type Point interface {
	struct{ X, Y int }
}

// 內(nèi)嵌其它約束
type MyNumber interface {
	MyInt
}

// 聯(lián)合(Unions)類型約束,不同類型元素之間是“或”的關(guān)系
// 如果元素是一個接口,這個接口不能包含任何方法!
type MyFloat interface {
	float32 | float64
}

有了豐富的類型集支持,我們就可以更加方便的使用接口對類型形參T的類型作出約束,既可以約束為基本類型(intfloat32、string…),也可以約束它必須實現(xiàn)一組方法,靈活性大大增加。

因此前面的Sum函數(shù)還可以改寫成:

// 原始例子:
// func Sum[T int | float64](a, b T) T {
//	 return a + b
// }

type MyNumber interface {
	int | float64
}

func Sum[T MyNumber](a, b T) T {
	return a + b
}

2)結(jié)構(gòu)體類型約束

Go 還允許我們使用復(fù)合類型字面量來定義約束。例如,我們可以定義一個約束,類型元素是一個具有特定結(jié)構(gòu)的struct

type Point interface {
	struct{ X, Y int }
}

然而,需要注意的是,雖然我們可以編寫受此類結(jié)構(gòu)體類型約束的泛型函數(shù),但在當前版本的 Go 中,函數(shù)無法訪問結(jié)構(gòu)體的字段,例如:

func GetX[T Point](p T) int {
	return p.X  // p.X undefined (type T has no field or method X)
}

3)類型近似(Type approximations)

我們知道,在Go中可以創(chuàng)建新的類型,例如:

type MyString string

MyString是一個新的類型,底層類型是string。

在類型約束中,有時候我們可能并不關(guān)心上層類型,只要底層類型符合要求就可以,這時候就可以使用類型近似符號:~。

// 創(chuàng)建新類型
type MyString string

// 定義類型約束
type AnyStr interface {
	~string
}

// 定義泛型函數(shù)
func Foo[T AnyStr](param T) T {
	return param
}

func main() {
	var p1 string = "aaa"
	var p2 MyString = "bbb"
	Foo(p1)
	Foo(p2) // 雖然p2是MyString類型,但也可以通過泛型函數(shù)的類型約束檢查
}

需要注意的是,類型近似中的類型,必須是底層類型,而且不能是接口類型:

type MyInt int

type I0 interface {
	~MyInt // 錯誤! MyInt不是底層類型, int才是
	~error // 錯誤! error是接口
}

(三)泛型類型(Generic type)

1)泛型切片

假設(shè)現(xiàn)在有一個IntSlice類型:

type IntSlice []int

var s1 IntSlice = []int{1, 2, 3} // 正常
var s2 IntSlice = []string{"a", "b", "c"} // 報錯,因為IntSlice底層類型是[]int,字符串無法賦值

很顯然,因為類型不一致,s2是無法賦值的,如果想要支持其它類型,需要定義新類型:

type StringSlice []string
type Float32Slice []float32
type Float64Slice []float64
// ...

但是這樣做的問題也顯而易見,它們結(jié)構(gòu)都是一樣的,只是元素類型不同就需要重新定義這么多新類型,導(dǎo)致代碼復(fù)雜度增加。

這時候就可以用泛型類型來解決這個問題:

// 只需定義一種新類型,就可以同時支持[]int/[]string/[]float32多種切片類型
// 新類型的名字叫 MySlice[T]
type MySlice[T int|string|float32] []T

類型定義中帶 類型形參 的類型,稱之為泛型類型(Generic type)

泛型切片的初始化:

var s1 MySlice[int] = MySlice[int]{1, 2, 3}
var s2 MySlice[string] = MySlice[string]{"a", "b", "c"}
s3 := MySlice[string]{"a", "b", "c"} // 簡寫

其它一些例子:

// 泛型Map
type MyMap[K int | string, V any] map[K]V

var m1 MyMap[string, int] = MyMap[string, int]{"a": 1, "b": 2} // 完整寫法
m2 := MyMap[int, string]{1: "a", 2: "b"} // 簡寫

// 泛型通道
type MyChan[T int | float32] chan T

var c1 MyChan[int] = make(MyChan[int]) // 完整寫法
c2 := make(MyChan[float32]) // 簡寫

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

假設(shè)現(xiàn)在要創(chuàng)建一個struct結(jié)構(gòu)體,里面含有一個data泛型屬性,類型是一個intfloat64的切片:

type List[T int | float64] struct {
	data []T
}

給這個結(jié)構(gòu)體增加一個Sum方法,用于對切片求和:

func (l *List[T]) Sum() T {
	var sum T
	for _, v := range l.data {
		sum += v
	}
	return sum
}

實例化結(jié)構(gòu)體,并調(diào)用Sum方法:

// var list *List[int] = &List[int]{data: []int{1, 2, 3}} // 完整寫法
list := &List[int]{data: []int{1, 2, 3}}
sum := list.Sum()
fmt.Println(sum) // 輸出:6

3)泛型接口

泛型也可以用在接口上:

type Human[T float32] interface {
	GetWeight() T
}

假設(shè)現(xiàn)在有兩個結(jié)構(gòu)體,它們都有GetWeight()方法,哪個結(jié)構(gòu)體實現(xiàn)了上面Human[T]接口?

// 結(jié)構(gòu)體1
type Person1 struct {
	Name string
}
func (p Person1) GetWeight() float32 {
	return 66.6
}

// 結(jié)構(gòu)體2
type Person2 struct {
	Name string
}
func (p Person2) GetWeight() int {
	return 66
}

注意觀察兩個GetWeight()方法的返回值類型,因為我們在Human[T]接口中約束了T的類型只能是float32,而只有Person1結(jié)構(gòu)體的返回值類型符合約束,所以實際上只有Person1結(jié)構(gòu)體實現(xiàn)了Human[T]接口。

p1 := Person1{Name: "Tim"}
var iface1 Human[float32] = p1 // 正常,因為Person1實現(xiàn)了接口,所以可以賦值成功

p2 := Person2{Name: "Tim"}
var iface2 Human[float32] = p2 // 報錯,因為Person2沒有實現(xiàn)接口

(五)一些錯誤示例

下面列出一些錯誤使用泛型的例子。

1)聯(lián)合約束中的類型元素限制

聯(lián)合約束中的類型元素不能是包含方法的接口:

// 錯誤
type ReaderStringer interface {
	io.Reader | fmt.Stringer // 錯誤,io.Reader和fmt.Stringer是包含方法的接口
}

// 正確
type MyInt interface {
	int
}
type MyFloat interface {
	float32
}
type MyNumber interface {
	MyInt | MyFloat // 正確,MyInt和MyFloat接口里面沒有包含方法
}

聯(lián)合約束中的類型元素不能含有comparable接口:

type Number interface {
	comparable | int // 含有comparable,報錯
}

2)一般接口只能用于泛型的類型約束

先解釋下相關(guān)概念,引入泛型后,Go的接口分為兩種類型:

  • 基本接口(Basic interface)
  • 只包含方法的接口,稱為基本接口,其實就是引入泛型之前的那種傳統(tǒng)接口。
  • 一般接口(General interface)
  • 由于引入泛型后,接口可以定義的元素大大豐富,如果一個接口里含有除了方法以外的元素,那么這個接口就稱為一般接口。

一般接口只能用于泛型的類型約束,不能用于變量、函數(shù)參數(shù)、返回值的類型聲明,而基本接口則沒有此限制:

type NoMethods interface {
	int
}

// 錯誤,不能用于函數(shù)參數(shù)列表、返回值
func Foo(param NoMethods) NoMethods {
	return param
}

// 錯誤,不能用來聲明變量的類型
var param NoMethods

// 正確
func Foo[T NoMethods](param T) T {
	return param
}

總結(jié)

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Go秒爬博客園100頁新聞

    Go秒爬博客園100頁新聞

    利用go語言的協(xié)程并發(fā)優(yōu)勢爬取網(wǎng)頁速度相當之快,博客園100頁新聞標題只需一秒即可全部爬取,跟著小編一起去看看如何實現(xiàn)的,希望大家可以從中受益
    2018-09-09
  • go語言if/else語句簡單用法示例

    go語言if/else語句簡單用法示例

    這篇文章主要介紹了go語言if/else語句用法,結(jié)合實例形式分析了go語言if else語句的判定與流程控制技巧,需要的朋友可以參考下
    2016-05-05
  • GoLang之使用Context控制請求超時的實現(xiàn)

    GoLang之使用Context控制請求超時的實現(xiàn)

    這篇文章主要介紹了GoLang之使用Context控制請求超時的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-04-04
  • Go 自定義package包設(shè)置與導(dǎo)入操作

    Go 自定義package包設(shè)置與導(dǎo)入操作

    這篇文章主要介紹了Go 自定義package包設(shè)置與導(dǎo)入操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-05-05
  • golang gc的內(nèi)部優(yōu)化詳細介紹

    golang gc的內(nèi)部優(yōu)化詳細介紹

    Go編譯器在垃圾回收(GC)的掃描標記階段,對存儲無指針鍵值對的map進行了優(yōu)化,即在GC掃描時不深入掃描map內(nèi)部數(shù)據(jù),只檢查map本身是否需要回收,這一優(yōu)化顯著提升了GC掃描的速度,從而減少了GC對程序性能的影響
    2024-10-10
  • Golang實現(xiàn)反向代理的示例代碼

    Golang實現(xiàn)反向代理的示例代碼

    這篇文章主要為大家學習介紹了如何利用Golang實現(xiàn)反向代理,文中的示例代碼講解詳細,具有一定的學習價值,感興趣的小伙伴可以跟隨小編一起了解一下
    2023-07-07
  • GoLang逃逸分析講解

    GoLang逃逸分析講解

    我們都知道go語言中內(nèi)存管理工作都是由Go在底層完成的,這樣我們可以不用過多的關(guān)注底層的內(nèi)存問題。本文主要總結(jié)一下?Golang內(nèi)存逃逸分析,需要的朋友可以參考以下內(nèi)容,希望對大家有幫助
    2022-12-12
  • Go語言CSP并發(fā)模型goroutine及channel底層實現(xiàn)原理

    Go語言CSP并發(fā)模型goroutine及channel底層實現(xiàn)原理

    這篇文章主要為大家介紹了Go語言CSP并發(fā)模型goroutine?channel底層實現(xiàn)原理,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-05-05
  • go語言同步教程之條件變量

    go語言同步教程之條件變量

    這篇文章主要給大家介紹了關(guān)于go語言同步教程之條件變量的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2018-07-07
  • Go channel實現(xiàn)原理分析

    Go channel實現(xiàn)原理分析

    Channel是go語言內(nèi)置的一個非常重要的特性,也是go并發(fā)編程的兩大基石之一,下面這篇文章主要給大家介紹了關(guān)于Go中channel的相關(guān)資料,需要的朋友可以參考下
    2023-04-04

最新評論

阿克苏市| 汕头市| 凤凰县| 庆安县| 武宁县| 伊吾县| 永和县| 四会市| 贵南县| 石门县| 基隆市| 谢通门县| 三门县| 吴桥县| 钦州市| 辽阳县| 武胜县| 化隆| 安龙县| 河北区| 元氏县| 上犹县| 苏州市| 柘城县| 汉寿县| 芦溪县| 镇赉县| 堆龙德庆县| 兴安县| 天峻县| 京山县| 边坝县| 清远市| 顺平县| 循化| 思茅市| 民县| 太白县| 梨树县| 云阳县| 漾濞|