GO泛型相關

通過引入 類型形參類型實參 這兩個概念,我們讓一個函數獲得了處理多種不同類型數據的能力,這種編程方式被稱為 泛型編程。

2. Go的泛型

  • 類型形參 (Type parameter)
  • 類型實參(Type argument)
  • 類型形參列表( Type parameter list)
  • 類型約束(Type constraint)
  • 實例化(Instantiations)
  • 泛型類型(Generic type)
  • 泛型接收器(Generic receiver)
  • 泛型函數(Generic function)

基本格式:

type Slice[T int|float32|float64 ] []T
  • T 就是上面介紹過的類型形參(Type parameter),在定義Slice類型的時候 T 代表的具體類型并不確定,類似一個占位符
  • int|float32|float64 這部分被稱為類型約束(Type constraint),中間的 | 的意思是告訴編譯器,類型形參 T 只可以接收 int 或 float32 或 float64 這三種類型的實參
  • 中括號里的 T int|float32|float64 這一整串因為定義了所有的類型形參(在這個例子里只有一個類型形參T),所以我們稱其為 類型形參列表(type parameter list)
  • 這里新定義的類型名稱叫 Slice[T]

泛型類型不能直接拿來使用,必須傳入類型實參(Type argument) 將其確定為具體的類型之后才可使用。而傳入類型實參確定具體類型的操作被稱為 實例化(Instantiations)

// 聲明一個泛型
type slice[T int | float32 | float64] []Tfunc main() {// 這里傳入了類型實參int,泛型類型Slice[T]被實例化為具體的類型 Slice[int]var a slice[int] = []int{1, 2, 3}fmt.Println(a) //[1 2 3]// 傳入類型實參float32, 將泛型類型Slice[T]實例化為具體的類型 Slice[float32]var b slice[float32] = []float32{1.2, 123.123, 2123.1}fmt.Println(b) //[1.2 123.123 2123.1]
}

其他類型的泛型


//泛型map
type myMap[KEP int | string, VAL int | float32] map[KEP]VALfunc main() {table := myMap[string, int]{"xiaoming": 190,"xiaohong": 150,}fmt.Printf("%+v", table)  //map[xiaohong:150 xiaoming:190]
}
//泛型結構體
type myStruct[T int|string] struct {name stringdata T
}
// 一個泛型接口(關于泛型接口在后半部分會詳細講解)
type IPrintData[T int | float32 | string] interface {Print(data T)
}
// 一個泛型通道,可用類型實參 int 或 string 實例化
type MyChan[T int | string] chan T

3 類型形參的互相套用

// 泛型嵌套
type woStruct[T int | string, s []T] struct {Data   smaxval Tminval T
}func main() {var test woStruct[int, []int] = woStruct[int, []int]{[]int{1, 2, 3},12,31,}fmt.Printf("%+v", test)  //{Data:[1 2 3] maxval:12 minval:31}
}

任何泛型類型都必須傳入類型實參實例化才可以使用。上面的代碼中,我們為T傳入了實參 int,然后因為 S 的定義是 []T ,所以 S 的實參自然是 []int

因為 S 的定義是 []T ,所以 T 一定決定了的話 S 的實參就不能隨便亂傳了

幾種語法錯誤

  • 定義泛型類型的時候,基礎類型不能只有類型形參,如下:
  • 當類型約束的一些寫法會被編譯器誤認為是表達式時會報錯。如下:
go
復制代碼// 錯誤,類型形參不能單獨使用
type CommonType[T int|string|float32] T
go
復制代碼//? 錯誤。T *int會被編譯器誤認為是表達式 T乘以int,而不是int指針
type NewType[T *int] []T
// 上面代碼再編譯器眼中:它認為你要定義一個存放切片的數組,數組長度由 T 乘以 int 計算得到
type NewType [T * int][]T //? 錯誤。和上面一樣,這里不光*被會認為是乘號,| 還會被認為是按位或操作
type NewType2[T *int|*float64] []T //? 錯誤
type NewType2 [T (int)] []T

為了避免這種誤解,解決辦法就是給類型約束包上 interface{} 或加上逗號消除歧義(關于接口具體的用法會在后半篇提及)

go
復制代碼type NewType[T interface{*int}] []T
type NewType2[T interface{*int|*float64}] []T // 如果類型約束中只有一個類型,可以添加個逗號消除歧義
type NewType3[T *int,] []T//? 錯誤。如果類型約束不止一個類型,加逗號是不行的
type NewType4[T *int|*float32,] []T

因為上面逗號的用法限制比較大,這里推薦統一用 interface{} 解決問題

匿名結構體不支持泛型

4. 泛型receiver


type mySlice[T int | string | float32] []T//泛型方法
func (m mySlice[T]) sum() T {var sum Tfor _, v := range m {sum += v}return sum
}func main() {var t mySlice[int] = []int{1, 2, 3, 4, 5}fmt.Println(t.sum()) //15var f mySlice[float32] = []float32{1.2, 3.4, 5.6}fmt.Println(f.sum())  //10.200001}
  • 首先看receiver (s MySlice[T]) ,所以我們直接把類型名稱 MySlice[T] 寫入了receiver中
  • 然后方法的返回參數我們使用了類型形參 T ****(實際上如果有需要的話,方法的接收參數也可以實用類型形參)
  • 在方法的定義中,我們也可以使用類型形參 T (在這個例子里,我們通過 var sum T 定義了一個新的變量 sum )

動態判斷變量的類型

泛型不像接口一樣可以通過類型斷言來判斷其類型

但可以通過反射來實現動態判斷其類型

func (receiver Queue[T]) Put(value T) {// Printf() 可輸出變量value的類型(底層就是通過反射實現的)fmt.Printf("%T", value) // 通過反射可以動態獲得變量value的類型從而分情況處理v := reflect.ValueOf(value)switch v.Kind() {case reflect.Int:// do somethingcase reflect.String:// do something}// ...
}

泛型函數

func main() {//在調用函數的時候聲明類型fmt.Println(add[float64](float64(6.123), float64(8.12312)))//自動類型推斷fmt.Println(add(19, 123)) //142
}
//定義一個函數泛型
func add[T int | float32 | float64](a T, b T) T {return a + b
}

匿名函數不能自己定義類型形參:

但是匿名函數可以使用別處定義好的類型實參,如:

go
復制代碼func MyFunc[T int | float32 | float64](a, b T) {// 匿名函數可使用已經定義好的類型形參fn2 := func(i T, j T) T {return i*2 - j*2}fn2(a, b)
}


?

既然函數都支持泛型了,那你應該自然會想到,方法支不支持泛型?很不幸,目前Go的方法并不支持泛型

6. 變得復雜的接口

type IntUintFloat interface {int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | float32 | float64
}type Slice[T IntUintFloat] []T

這段代碼把類型約束給單獨拿出來,寫入了接口類型 IntUintFloat 當中。需要指定類型約束的時候直接使用接口 IntUintFloat 即可。

不過這樣的代碼依舊不好維

6.1 ~ : 指定底層類型

上面定義的 Slie[T] 雖然可以達到目的,但是有一個缺點:

go
復制代碼var s1 Slice[int] // 正確 type MyInt int
var s2 Slice[MyInt] // ? 錯誤。MyInt類型底層類型是int但并不是int類型,不符合 Slice[T] 的類型約束

這里發生錯誤的原因是,泛型類型 Slice[T] 允許的是 int 作為類型實參,而不是 MyInt (雖然 MyInt 類型底層類型是 int ,但它依舊不是 int 類型)。

為了從根本上解決這個問題,Go新增了一個符號 ~ ,在類型約束中使用類似 ~int 這種寫法的話,就代表著不光是 int ,所有以 int 為底層類型的類型也都可用于實例化。

限制:使用 ~ 時有一定的限制:

  1. ~后面的類型不能為接口
  2. ~后面的類型必須為基本類型
type MyInt inttype _ interface {~[]byte  // 正確~MyInt   // 錯誤,~后的類型必須為基本類型~error   // 錯誤,~后的類型不能為接口
}

6.2 從方法集(Method set)到類型集(Type set)

當滿足以下條件時,我們可以說 類型 T 實現了接口 I ( type T implements interface I)

  • T 不是接口時:類型 T 是接口 I 代表的類型集中的一個成員 (T is an element of the type set of I)
  • T 是接口時: T 接口代表的類型集是 I 代表的類型集的子集(Type set of T is a subset of the type set of I)

6.2.2 類型的并集

并集我們已經很熟悉了,之前一直使用的 | 符號就是求類型的并集( union )

6.2.3 類型的交集

接口可以不止書寫一行,如果一個接口有多行類型定義,那么取它們之間的 交集

go
復制代碼type AllInt interface {~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint32
}type Uint interface {~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
}type A interface { // 接口A代表的類型集是 AllInt 和 Uint 的交集AllIntUint
}type B interface { // 接口B代表的類型集是 AllInt 和 ~int 的交集AllInt~int
}

上面這個例子中

  • 接口 A 代表的是 AllInt 與 Uint 的 交集,即 ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
  • 接口 B 代表的則是 AllInt 和 ~int 的交集,即 ~int

除了上面的交集,下面也是一種交集:

go
復制代碼type C interface {~intint
}

很顯然,~int 和 int 的交集只有int一種類型,所以接口C代表的類型集中只有int一種類型
?

6.2.4 空集

當多個類型的交集如下面 Bad 這樣為空的時候, Bad 這個接口代表的類型集為一個空集

go
復制代碼type Bad interface {intfloat32 
} // 類型 int 和 float32 沒有相交的類型,所以接口 Bad 代表的類型集為空


?

6.2.5 空接口和 any

上面說了空集,接下來說一個特殊的類型集——空接口 interface{} 。因為,Go1.18開始接口的定義發生了改變,所以 interface{} 的定義也發生了一些變更:

空接口代表了所有類型的集合

// 空接口代表所有類型的集合。寫入類型約束意味著所有類型都可拿來做類型實參
type Slice[T interface{}] []T

因為空接口是一個包含了所有類型的類型集,所以我們經常會用到它。于是,Go1.18開始提供了一個和空接口 interface{} 等價的新關鍵詞 any ,用來使代碼更簡單:

type Slice[T any] []T // 代碼等價于 type Slice[T interface{}] []T


?

6.2.6 comparable(可比較) 和 可排序(ordered)

Go直接內置了一個叫 comparable 的接口,它代表了所有可用 != 以及 == 對比的類型

6.3.1 基本接口(Basic interface)

接口定義中如果只有方法的話,那么這種接口被稱為基本接口(Basic interface)。這種接口就是Go1.18之前的接口,用法也基本和Go1.18之前保持一致

6.3.2 一般接口(General interface)

如果接口內不光只有方法,還有類型的話,這種接口被稱為 一般接口(General interface) ,如下例子都是一般接口:

go
復制代碼type Uint interface { // 接口 Uint 中有類型,所以是一般接口~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
}type ReadWriter interface {  // ReadWriter 接口既有方法也有類型,所以是一般接口~string | ~[]runeRead(p []byte) (n int, err error)Write(p []byte) (n int, err error)
}

一般接口類型不能用來定義變量,只能用于泛型的類型約束中。所以以下的用法是錯誤的:

go
復制代碼type Uint interface {~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
}var uintInf Uint // 錯誤。Uint是一般接口,只能用于類型約束,不得用于變量定義

這一限制保證了一般接口的使用被限定在了泛型之中,不會影響到Go1.18之前的代碼,同時也極大減少了書寫代碼時的心智負擔


?


?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/715561.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/715561.shtml
英文地址,請注明出處:http://en.pswp.cn/news/715561.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Pake 輕松構建輕量級多端桌面應用

Pake 利用 Rust 輕松構建輕量級多端桌面應用,支持 Mac / Windows / Linux。 小白用戶:可以使用 「常用包下載」 方式來體驗 Pake 的能力,也可試試 Action 方式。 開發用戶:可以使用 「命令行一鍵打包」,對 Mac 比較友…

Matlab 機器人工具箱 動力學

文章目錄 R.dynR.fdynR.accelR.rneR.gravloadR.inertiaR.coriolisR.payload官網:Robotics Toolbox - Peter Corke R.dyn 查看動力學參數 mdl_puma560; p560.dyn;%查看puma560機械臂所有連桿的動力學參數 p560.dyn(2);%查看puma560機械臂第二連桿的動力學參數 p560.links(2)…

react父子組件傳參demo

父組件代碼 /* eslint-disable next/next/no-img-element */ "use client"; import React, { useEffect, useState } from "react"; import WxTip from ../components/WxTipconst Download () > {const [showTip, setshowTip] useState<boolean…

javaweb day9 day10

昨天序號標錯了 vue的組件庫Elent 快速入門 寫法 常見組件 復制粘貼 打包部署

高斯消元法解線性方程組

高斯消元法 基本性質&#xff1a; 把某一行乘一個非 0 0 0的數 (方程的兩邊同時乘上一個非 0 0 0數不改變方程的解) 交換某兩行 (交換兩個方程的位置) 把某行的若干倍加到另一行上去 &#xff08;把一個方程的若干倍加到另一個方程上去&#xff09; 算法步驟 枚舉每一列c …

洛谷p1225 c++(使用高精度)

題解: 一開始我這個代碼想到的是使用遞歸來求解 int digui(int n){int sum=0;if(n==1)sum=1;if(n==2)sum=2;if(n==1||n==2)return sum;if(n>2){return sum+=digui(n-1)+digui(n-2);} } 但是后面發現明顯超時,我試圖用記憶化搜索來搶救一下,所以就有了下面代碼 int di…

圖論 - DFS深度優先遍歷、BFS廣度優先遍歷、拓撲排序

文章目錄 前言Part 1&#xff1a;DFS&#xff08;深度優先遍歷&#xff09;一、排列數字1.題目描述輸入格式輸出格式數據范圍輸入樣例輸出樣例 2.算法 二、n皇后問題1.問題描述輸入格式輸出格式數據范圍輸入樣例輸出樣例 2.算法 三、樹的重心1.問題描述輸入格式輸出格式數據范圍…

計算機二級Python刷題筆記------基本操作題23、33、35、37(考察字符串)

文章目錄 第二十三題&#xff08;字符串替換&#xff1a;replace(old,new)&#xff09;第三十三題&#xff08;字符串遍歷&#xff09;第三十五題&#xff08;字符串與列表&#xff09;第三十七題&#xff08;拼接字符串&#xff09; 第二十三題&#xff08;字符串替換&#xf…

第19章-IPv6基礎

1. IPv4的缺陷 2. IPv6的優勢 3. 地址格式 3.1 格式 3.2 長度 4. 地址書寫壓縮 4.1 段內前導0壓縮 4.2 全0段壓縮 4.3 例子1 4.4 例子 5. 網段劃分 5.1 前綴 5.2 接口標識符 5.3 前綴長度 5.4 地址規模分類 6. 地址分類 6.1 單播地址 6.2 組播地址 6.3 任播地址 6.4 例子 …

Redis學習------實戰篇----2024/02/29----緩存穿透,雪崩,擊穿

1.緩存穿透 Overridepublic Result queryById(Long id) {//1.從redis中查詢緩存String key CACHE_SHOP_KEY id;String shopJson stringRedisTemplate.opsForValue().get(key);//2.判斷是否存在//3.存在則直接返回if (StrUtil.isNotBlank(shopJson)){Shop shop JSONUtil.toB…

每日一題 2867統計樹中的合法路徑

2867. 統計樹中的合法路徑數目 題目描述&#xff1a; 給你一棵 n 個節點的無向樹&#xff0c;節點編號為 1 到 n 。給你一個整數 n 和一個長度為 n - 1 的二維整數數組 edges &#xff0c;其中 edges[i] [ui, vi] 表示節點 ui 和 vi 在樹中有一條邊。 請你返回樹中的 合法路…

Nginx 反向代理入門教程

Nginx 反向代理入門教程 一、什么是反向代理 反向代理&#xff08;Reverse Proxy&#xff09;方式是指以代理服務器來接受Internet上的連接請求&#xff0c;然后將請求轉發給內部網絡上的服務器&#xff1b;并將從服務器上得到的結果返回給Internet上請求連接的客戶端&#x…

Vue 2.0 與 Vue 3.0 的主要差異

Vue 2.0 與 Vue 3.0 的主要差異 在前端框架的世界中&#xff0c;Vue.js 已經成為了一股不可忽視的力量。自從 Vue.js 首次亮相以來&#xff0c;它便以其輕量級、靈活性和易用性贏得了開發者的喜愛。然而&#xff0c;隨著技術的不斷進步和開發者需求的不斷變化&#xff0c;Vue.…

Android AppCompatActivity 方法詳解

在 Android 開發中&#xff0c;AppCompatActivity 是一個常用的類&#xff0c;它提供了對新版 Android 特性在舊版 Android 上的兼容支持。作為 Android 支持庫的一部分&#xff0c;它通常被用作活動&#xff08;Activity&#xff09;的基類。下面我們將介紹 AppCompatActivity…

Vins-Moon配準運行

Vins-Moon運行 源碼地址電腦配置環境配置編譯適配Kitti數據集運行結果Euroc數據集kitti數據集 evo評估&#xff08;KITTI數據&#xff09;輸出軌跡(tum格式)結果 源碼地址 源碼鏈接&#xff1a;https://github.com/HKUST-Aerial-Robotics/VINS-Mono.git 電腦配置 Ubuntu 18.…

破解SQL Server迷局,徹底解決“管道的另一端無任何進程錯誤233”

問題描述&#xff1a;在使用 SQL Server 2014的時候&#xff0c;想用 SQL Server 身份方式登錄 SQL Servcer Manager&#xff0c;結果報錯&#xff1a; 此錯誤消息&#xff1a;表示SQL Server未偵聽共享內存或命名管道協議。 問題原因&#xff1a;此問題的原因有多種可能 管道…

人才測評系統在企業中的作用有哪些?

一個企業除了產出價值給社會&#xff0c;它還有自己的工作架構體系&#xff0c;無論的工作時間制度上&#xff0c;還是工資組成方向&#xff0c;這樣公司才能正常運轉&#xff0c;那么人才測評系統可以在企業中充當一個什么角色呢&#xff1f;又或者說它起著什么作用呢&#xf…

【數據結構】棧和隊列(概念選擇題)

1.概念選擇題 1.一個棧的初始狀態為空。現將元素1、2、3、4、5、A、B、C、D、E依次入棧&#xff0c;然后再依次出棧&#xff0c;則元素出 棧的順序是&#xff08; &#xff09;。 A 12345ABCDE B EDCBA54321 C ABCDE12345 D 54321EDCBA2.若進棧序列為 1,2,3,4 &#xff0c;進棧…

走進SQL審計視圖——《OceanBase診斷系列》之二

1. 前言 在SQL性能診斷上&#xff0c;OceanBase有一個非常實用的功能 —— SQL審計視圖(gv$sql_audit)。在OceanBase 4.0.0及更高版本中&#xff0c;該功能是 gv$ob_sql_audit。它可以使開發和運維人員更方便地排查在OceanBase上運行過的任意一條SQL&#xff0c;無論這些SQL是成…

字節前端實習一面

1.自我介紹 實習經歷介紹 2.選擇前端的原因 3.如何解決跨域 4.tailwind CSS 這個是我其中一個項目中使用的&#xff0c;但我當時只是當它工具使用的&#xff0c;直接問我實現原理和優勢等等。實現原理我沒回答好&#xff0c;但這個確實是一個好問題 代碼題&#xff1a; 1.let …