go基礎-泛型

来源:https://www.cnblogs.com/asdfzxv/archive/2023/11/10/17823836.html
-Advertisement-
Play Games

UTL_MATCH介紹: Oracle的UTL_MATCH包是一個提供字元串匹配和相似度計算功能的工具包。它包含了一系列函數,用於執行字元串比較、相似度計算和模式匹配等操作。 UTL_MATCH包中的函數可以用於以下任務: 字元串相似度計算:UTL_MATCH提供了多個函數來計算字元串之間的相似度, ...


概述

在強類型變成語言中,類型是確定不可變,如函數入參是確定類型、鏈表元素是確定類型,這極大限制了函數功能。也有些解決方案,為每種類型都實現一版函數,會導致大量重覆代碼;使用類型轉換,使用特殊形參(如Object、any),在函數內部判斷並轉換類型後再執行邏輯,導致大量類型轉換的代碼,結構混亂,Java 未支持泛型之前就使用這個套路。最優解決方案是泛型,即類型參數化,編寫函數暫時不確定類型,使用占位符代替,調用時候類型也是通過參數傳遞進去,替換占位符。主流強類型語言都支持泛型,Java、C++、C#等。

千呼萬喚始出來,1.18版本正式增加泛型支持,但是體感貌似一般,使用場景並沒有那麼廣泛,特別是泛型約束能力,相比Java、C++弱了很多。更多是對函數式編程的支持,如slice、map、func等,在面向對象編程支持不夠。不確定後期是否會繼續演變,Go對相容性保持還算可以,後續演變也不用過分擔憂。

基本使用

Go 語言不是主流的面向對象,泛型支持上也有所區別,如Java一切皆為對象,只要確定對象泛型即可統一的泛型模式。Go的每種類型都有區別,可簡單分為兩類,類型泛型:切片泛型、哈希泛型、結構體泛型、基礎類型泛型等;函數泛型:函數泛型、方法泛型等。另外泛型語法不是主流的<>尖括弧,而使用的[]中括弧,這不重要,思路都是相似的

類型泛型,風格基本一致,使用type定義別名,名稱後面緊跟泛型定義,然後是基礎類型。

type MyGen[T any] int

函數泛型,語法也比較類似,函數名稱後面緊跟泛型定義

func MyFun[T any](x T, n int) {
    ...
}

切片泛型

定義切片泛型。使用type定了名稱為genSlice的新類型,底層類型是個切片。緊跟在名稱後面的[T any]就是泛型定義,T表示泛型占位符,any表示泛型約束,可以看到切片的元素類型也使用了T占位符

type genSlice[T any] []T

any是標準庫提供的特殊類型,表示任意類型,其本質是個空介面 type any = interface{},類似Java中的Object類型。更多泛型約束信息獨立章節介紹

使用泛型切片,與普通切片幾乎一模一樣。唯一區別,創建時需要傳遞參數類型,這就是所謂的類型參數化。

arr := make(genSlice[int], 0)
arr2 := genSlice[float64]{1.1, 1.2, 2.1}
var arr3 = genSlice[string]{"a", "b", "cd"}

創建了三個切片,相比普通切片多了個參數,使用大括弧傳遞的類型參數,在底層會替換占位符T,三個切片的元素類型分別是intfloat64string。僅此而已,再無其他區別。訪問切片

arr = append(arr, 10)
arr = append(arr, 20)

for _, i := range arr {
    fmt.Printf("%T=%v\n", i, i)
}

輸出如下

int=10
int=20

哈希泛型

也就是map切片,map有兩個參數,可以定一個或兩個泛型,其他方面與切片泛型幾乎一樣

type genMap1[V any] map[string]V				// 一個泛型, key 是字元串, value是泛型, 創建時傳入
type genMap2[K string|int, V any] map[K]V		// 兩個泛型, key 是泛型且限定為字元串或整數, value是泛型且沒有限定

創建實例

m1 := genMap1[int]{"k1": 1}
m2 := genMap2[string, string]{"k1": "v1"}

管道泛型

泛型定義

type genChan[T any] chan T

創建實例

chan1 := make(genChan[int])
var chan2 genChan[string]		// nil, 引用類型需要初始化

基礎類型泛型

相比容器類型有一些區別。基礎類型不能只有類型形參,如下缺乏原始類型。

type CommonType[T int|string|float32] T		// err

正確定義,給出原始類型int

type CommonType[T int | string | float32] int

由於原始類型是int,導致泛型約束無效,原始類型限定更強

var v1 CommonType[int] = 10				// ok
var v2 CommonType[string] = "test"		// err

函數泛型

定義泛型函數

func add[T int|string](x, y T) T {
	return x+y
}

調用泛型函數,一樣的套路,調用時多個傳遞一個參數,表示類型

n := add[int](1, 2)
fmt.Println(n)

註意,匿名函數不支持泛型,匿名函數不能定義類型參數,以下案例編譯不通過

func[T int | float32](a, b T) T {		// err
        return a+b
}

閉包函數則可以使用泛型,閉包函數是對外部類型的應用,而不是類型參數。

func MyFunc[T int | float32]() func(a, b T) T {
	return func(a, b T) T {
		return a + b
	}
}

結構體泛型

定義泛型結構體

type genStruct[T int | string] struct {
    Name string
    Data T
}

創建實例

p1 := genStruct[int]{"xx", 10}
p2 := new(genStruct[string])

註意,匿名結構體目前還不支持泛型,以下代碼編譯不通過。

struct[T int|string] {
    caseName string
    got      T
    want     T
}[int]{
    caseName: "test OK",
    got:      100,
    want:     100,
}

需要特別註意的是結構體方法,泛型結構體並不代表方法泛型。判斷一個方法是否支持泛型,要看是否有定義類型參數,這與Java特性一樣。如下setName就是普通方法,因為並沒有定義泛型參數。名稱前面的(p *person[T]) 是類型綁定,而不是類型參數。setName雖然是普通方法,但是內部可以使用結構體中定義的泛型

type person[T string | int] struct {
	name T
}

func (p *person[T]) setName(name T) T {		// 普通方法
	p.name = name
	return p.name
}

截止目前go 還不支持泛型方法(1.9版本),如果後續支持,定義方式應該如下,增加了類型參數E

func (p *person[T]) setName[E string](name T, surname E) T {
    ...
}

泛型也支持相互套用

type WowStruct[T int | float32, S []T] struct {
    Data     S		// S是T類型切片
    MaxValue T
    MinValue T
}

看起來有點複雜,只要記住一點:任何泛型類型都必須傳入類型實參實例化才可以使用。

var ws WowStruct[int, []int]

介面泛型

介面泛型最為特殊,因為方法不支持泛型,介面定義卻支持泛型,看似好像兩者衝突了。其實用了個雞賊的方式實現,繞開了問題根本,本質是類型轉換。介面泛型定義如下,看起來沒有特別之處

type DataProcessor[T any] interface {
	Process(oriData T) (newData T)
	Save(data T) error
}

實現泛型介面

type CSVProcessor struct {
}

func (c CSVProcessor) Process(oriData string) (newData string) {
	return oriData
}

func (c CSVProcessor) Save(oriData string) error {
	return nil
}

註意:與實現非泛型介面有區別,結構體的方法簽名與泛型介面內方法簽名不一樣,按照規範兩者沒有實現關係。討巧就在這裡,給泛型傳入類型參數,然後類型轉換。

func MyFun(E DataProcessor[string]) {					// 形參是泛型介面
	println(E.Process("name"))
}

var processor DataProcessor[string] = CSVProcessor{}	// 類型轉換
MyFun(processor)

包裝泛型

以下方式也都是錯誤定義

// 錯誤, 它認為你要定義一個存放切片的數組,數組長度由 T 乘以 int 計算得到
type NewType [T * int][]T 

//✗ 錯誤。和上面一樣,這裡不光*被會認為是乘號,| 還會被認為是按位或操作
type NewType2[T *int|*float64] []T 

//✗ 錯誤
type NewType2 [T (int)] []T

解決方法也都一樣,使用介面包裝。在“介面類型約束”章節詳細說明

type NewType interface {
    *int | *float64
}

泛型約束

除了類型參數化,泛型有個重要作用:類型約束。簡單理解就是限定泛型占位符的範圍,比如類型參數只能是數組、字元串、數組、介面、函數等。Java的泛型約束可支持上邊界、下邊界、或類型等;Go泛型約束弱一些,沒有上邊界、下邊界。主要原因還是Go的特殊面向對象,沒有Java完整的類型系統。

基本類型約束

泛型約束為int類型

func MyFunc[T int]() T {
	...
}

這種單個基礎數據類型約束沒有意義,直接使用基礎類型效果一樣。更多時候會約束一個範圍,如下約束範圍是所有的整數類型。

func MyFunc[T int|int8|int16|int32|int64|float32|float64]() T {
	...
}

介面類型約束

介面是泛型約束中使用最廣泛的類型,這與Java一致。面向介面編程,面向介面約束。

在基本類型約束中有個案例

func MyFunc[T int|int8|int16|int32|int64|float32|float64]() T {
	...
}

這種寫法繁瑣且不方便復用,如果其他函數有相同的約束需求,需要再寫一遍,為此Go 提供特殊的介面定義,專門用於泛型約束。

// 整型
type Int interface {
    int|int8|int16|int32|int64|float32|float64
}

// 無符號整型
type Uint interface {
    uint | uint8 | uint16 | uint32
}

// 浮點
type Float interface {
    float32 | float64
}

介面約束之間也繼續組合,約束為所有數值類型

type Number interface {
    Int | Uint | Float
}

使用介面約束,與使用其他約束一樣

func MyFunc[T Number]() T {
	...
}

上面介面定義各類型使用|符號分割,是並集關係,也支持交集關係

// 並集
type AllInt interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint32
}

// 並集
type Uint interface {
    ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
}

// 交集, 介面A代表的類型集是 AllInt 和 Uint 的交集
type A interface {
    AllInt
    Uint
}

// 交集, 介面B代表的類型集是 AllInt 和 ~int 的交集
type B interface { 	
    AllInt
    ~int
}

空集合, intfloat32 沒有相交的類型,所以介面 Bad 代表的類型集為空,沒有任何類型可以匹配,這與any空介面剛好相反,後者可表示任意類型。

type Bad interface {
    int
    float32 
} 

註意:並集和交集只能用於基本類型,如下定義異常

type A interface{
    funcA()
}

type B interface{
    funcB()
}

type C interface{
    A | B			// err
}

穿透別名,項目中經常使用type定義別名,泛型約束無法穿透別名,如下案例編譯不通過

type MyInt int

func MyFunc[T MyInt]() T {
	...
}

MyFunc[int]()		// err,int != MyInt

Go專門為此提供了特殊語法,在類型前面增加~符號,可穿透別名約束到底層類型。

type MyInt interface {
	~int | ~int8 | ~int16 | ~int32 | ~int64
}

func MyFunc[T MyInt]() T {
	...
}

MyFunc[int]()		// ok
MyFunc[MyInt]()		// ok

使用~符號穿透別名約束有一些限制條件:~後面的類型不能為介面,~後面的類型必須為基本類型。

type MyInt int

type _ interface {
    ~int		// ok
    ~[]byte  	// ok
    ~MyInt   	// err,~後的類型必須為基本類型
    ~error   	// err,~後的類型不能為介面
}

介面新語法有更深層次的意義,影響深遠,在1.18版本之前介面是一堆函數聲明的集合,稱為方法集(method set)。如下ReadWriter 定義了一個介面 ,包含了 Read() 和 Write() 兩個方法。所有同時定義了這兩種方法的類型被視為實現了這一介面。

type ReadWriter interface {
    Read(p []byte) (n int, err error)
    Write(p []byte) (n int, err error)
}

但是如果換一個角度來重新思考,會發現還能這樣理解:可以把  ReaderWriter 介面看成代表一個“類型的集合”,所有實現了  Read()  Writer() 這兩個方法的類型都在介面代表的類型集合當中。從 1.18 開始介面的定義就從 **方法集(method set)** 變為了 **類型集(type set)**

以此配合介面增加了新技能,介面不僅可以定義方法聲明,也定義底層類型。來個複雜點的案例,介面類型 ReadWriter 代表了一個類型集合,所有以 string[]rune 為底層類型,並且實現了 Read()Write() 這兩個方法的類型都在 ReadWriter 代表的類型集當中

type ReadWriter interface {
    ~string | ~[]rune

    Read(p []byte) (n int, err error)
    Write(p []byte) (n int, err error)
}

從此介面可分為兩種類型:Basic interface基本介面,介面內僅包含方法聲明,就是Go1.18 之前的介面。General interface一般介面,介面內有定義原始類型,以及方法聲明。以下兩個介面都是一般介面,因為內部都有定義原生類型

type Uint interface { 		
    ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64	// 原始類型
}

type ReadWriter interface {  	
    ~string | ~[]rune								// 原始類型

    Read(p []byte) (n int, err error)
    Write(p []byte) (n int, err error)
}

容器類型約束

常用的約束

func MyFun[T any](arr []T) {
	for i := range arr {
		log.Println(i)
	}
}

以下兩種寫法和實現的功能其實是差不多的,實例化之後結構體相同

type WowStruct[T int|string] struct {
    Name string
    Data []T
}

type WowStruct2[T []int|[]string] struct {
    Name string
    Data T
}

但是像下麵這種情況的時候,使用前一種寫法會更好

type WowStruct3[T int | string] struct {
    Data     []T
    MaxValue T
    MinValue T
}

函數類型約束

也比較常規,就是使用函數簽名約束,更多使用介面約束

func MyFunc[T any](arg T) {
    ...
}

參考


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、QBarSeries簡介 1. 官方描述 https://doc.qt.io/qtforpython-6/PySide6/QtCharts/QBarSeries.html 【譯註:官方文檔內容過於簡潔,表明完全僅繼承了QAbstractBarSeries,且沒有擴展任何屬性、方法和信號。因此,直 ...
  • 刷 Leetcode 總能遇到關於二分的題目,但是之前也只是草草地瞭解一下,每次在使用的時候都需要找模板,要不然就需要對於邊界條件進行調試,著實是很麻煩!!! 二分介紹: 首先來簡單介紹一下二分:二分查找也稱折半查找(Binary Search),它是一種效率較高的查找方法。但是,折半查找要求 線性 ...
  • 從表格中選擇數據 要從MySQL中的表格中選擇數據,請使用"SELECT"語句: 示例選擇"customers"表格中的所有記錄,並顯示結果: import mysql.connector mydb = mysql.connector.connect( host="localhost", user= ...
  • 12.1、環境搭建 創建名為spring_mvc_interceptor的新module,過程參考9.1節和9.5節 12.1.1、頁面請求示例 <a th:href="@{/test/hello}">測試攔截器</a> 12.1.2、控制器方法示例 @RequestMapping("/test/h ...
  • 介紹AVIF圖片格式的特點和在Web端顯示AVIF格式圖片的兩種方案。 1 簡介 AVIF是一種基於AV1視頻編碼的新圖像格式,相對於JPEG、Wep等圖片格式壓縮率更高,並且畫面細節更好。AVIF通過使用更現代的壓縮演算法,在相同質量的前提下,AVIF文件大小是JPEG文件的35%左右。 AVIF支 ...
  • 作者:糊塗碼 鏈接:https://juejin.cn/post/7156428078061895710 前言 MP 從出現就一直有爭議 感覺一直 都存在兩種聲音 like: 很方便啊 通過函數自動拼接Sql 不需要去XML 再去使用標簽 之前一分鐘寫好的Sql 現在一秒鐘就能寫好 簡直不要太方便 ...
  • 目錄一、爬取目標1.1 效果截圖1.2 演示視頻1.3 軟體說明二、代碼講解2.1 爬蟲採集模塊2.2 軟體界面模塊2.3 日誌模塊三、獲取源碼及軟體 一、爬取目標 您好!我是@馬哥python說 ,一名10年程式猿。 我用python開發了一個爬蟲採集軟體,可自動抓取小紅書評論數據,並且含二級評論 ...
  • 概述 下麵我們將學習如何創建多個 Spring boot 微服務以及如何使用 RestTemplate 類在多個微服務之間進行同步通信。 微服務通信有兩種風格: 同步通訊 非同步通信 同步通訊 在同步通信的情況下,客戶端發送請求並等待服務的響應。這裡重要的一點是協議(HTTP/HTTPS)是同步的,客 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...