亚洲国产日韩欧美一区二区三区,精品亚洲国产成人av在线,国产99视频精品免视看7,99国产精品久久久久久久成人热,欧美日韩亚洲国产综合乱

Table of Contents
What is generics
Generics in go
Formal parameters, actual parameters, type parameters, type actual parameters, instantiation
Type collection, interface
基礎(chǔ)接口類型
接口組合
通用接口
類型推導(dǎo)
泛型的使用
內(nèi)置容器類型
通用的結(jié)構(gòu)體
通用的函數(shù)
總結(jié)
Home Backend Development Golang In-depth understanding of generics in golang (Generic)

In-depth understanding of generics in golang (Generic)

Apr 11, 2023 pm 07:20 PM
go rear end

This article brings you an in-depth understanding of generics in golang? How to use generics? It has certain reference value. Friends in need can refer to it. I hope it will be helpful to you.

In-depth understanding of generics in golang (Generic)

What is generics

Generics (Generic) is a programming technology. In a strongly typed language, you are allowed to write code using types that are specified later, and specify the corresponding type at instantiation time.

In generics, type parameters can be used instead of specific data types. These type parameters can be declared in a class, method, or interface, and can be used in these declarations. Code that uses generics can specify the actual type parameters at runtime, which allows the code to be applied to many different types of data.

Generics can improve the readability, maintainability and reusability of code. It reduces the redundancy of your code and provides better type safety and compile-time type checking.

Let’s use a specific example to explain why generics can reduce code redundancy:

Provide a function that returns the minimum value of a and b. We need each Write a function for a specific data type "int, float..."; or use interface{} "It requires type assertion for parameters, which has an impact on running performance, and cannot constrain the parameters passed in"

func minInt(a, b int) int {
    if a > b {
        return b
    }
    return a
}

func minFloat(a, b float64) float64 {
    if a > b {
        return b
    }
    return a
}

func minItf(a, b interface{}) interface{} {
    switch a.(type) {
    case int:
        switch b.(type) {
        case int:
            if a.(int) > b.(int) {
                return b
            }
            return a
        }
    case float64:
        switch b.(type) {
        case float64:
            if a.(float64) > b.(float64) {
                return b
            }
            return a
        }
    }
    return nil
}

We can see from the above method that except for the different types of parameters and return results, minInt and minFloat have the same code. Is there a way to determine the type passed in when the function is called without specifying a specific type? Here, a concept called generics is introduced, which can be simply understood as a broad type or an unspecified specific type. By introducing generics, we no longer need to specify specific data types. The min function can be used in the following way:

// T 為類型參數(shù), 在調(diào)用時(shí)確定參數(shù)的具體值, 可以為 int, 也可以為 float64;它與 a, b 一樣也是參數(shù), 需要調(diào)用時(shí)傳入具體的值;不同的是,T 為類型參數(shù),值為具體的類型, a,b 為函數(shù)參數(shù),值為具體類型對(duì)應(yīng)的值
func minIntAndFloat64[T int | float64](a, b T) T { 
    if a < b {
        return a
    }
    return b
}

minIntAndFloat64[int](1, 2) // 實(shí)例化/調(diào)用時(shí)指定具體的類型

Generics in go

go only introduced generics in version 1.8. If your go version is lower than 1.8, you cannot use generics. The code in this article uses version 1.9. In version 1.8, a lot of changes were made to support generics.

  • Type parameters are introduced in function and type declarations
  • A collection of types can be defined through interfaces, including types without methods
  • Type derivation, in some scenarios Type parameters will be deduced, and you can call the function without specifying the value of the type parameter

Formal parameters, actual parameters, type parameters, type actual parameters, instantiation

Let’s look at a common add function first. add is the function name, x, y is the formal parameter, (x, y int) is the parameter list. When a function call occurs, add(2, 3) 2, 3 are actual parameters.

In-depth understanding of generics in golang (Generic) By analogy to generics, we need a type parameter. When a function call occurs, the corresponding type parameter is passed in. A function with type parameters is called a generic function. [T int | int64] is the type parameter list, T is the type parameter, int | int64 is the type set/type constraint. When a function call occurs add[int](2,3), int is the type actual parameter. This call is also called instantiation, that is, the type actual parameter is determined.

In-depth understanding of generics in golang (Generic)

Type parameters can also be specified when declaring the structure. MyStruct[T] is a generic structure, and methods can be defined for generic structures.

In-depth understanding of generics in golang (Generic)

Type collection, interface

In the basic type, uint8 represents a collection of 0~255. Then for type parameters, you also need to define a collection of types just like the basic type. In the above example int | string is a collection of types. So how to reuse a collection of types? Interfaces are used here for definition. The following is the definition of a type collection. Therefore, we can define a generic function add[T Signed](x, y T) T

In-depth understanding of generics in golang (Generic)

Before go 1.8, the definition of the interface It is a collection of methods, that is, if the method corresponding to the interface is implemented, it can be converted into the corresponding interface. In the following example, the MyInt type implements the Add method and can therefore be converted to MyInterface.

type MyInterface interface {
    Add(x, y int) int
}

type MyInt int

func (mi myInt) Add(x, y int) int {
    return x + y
}

func main() {
    var mi MyInterface = myInt(1)
    fmt.Println(mi.Add(1, 2))
}

If we think about it from another angle, MyInterface can be regarded as a type collection, which includes all types that implement the add method. Then, MyInterface can be used as a collection of types. For example, we can define a generic function as follows.

func I[T MyInterface](x, y int, i T) int {
    return i.Add(x, y)
}

在泛型中, 我們的類型集合不僅僅是實(shí)現(xiàn)接口中定義方法的類型, 還需要包含基礎(chǔ)的類型。因此, 我們可以對(duì)接口的定義進(jìn)行延伸, 使其支持基礎(chǔ)類型。為了保證向前兼容, 我們需要對(duì)接口類型進(jìn)行分類:

基礎(chǔ)接口類型

只包含方法的集合, 既可以當(dāng)作類型集合, 又可以作為數(shù)據(jù)類型進(jìn)行聲明。如下面的 MyInterface。還有一個(gè)特殊的接口類型 interface{}, 它可以用來(lái)表示任意類型, 即所有的類型都實(shí)現(xiàn)了它的空方法。在 1.8 之后可以使用 any 進(jìn)行聲明。

type any = interface{}

type MyInterface interface {
    Add(x, y int) int
    String() string
    String() string  // 非法: String 不能重復(fù)聲明
    _(x int)         // 非法: 必須要有一個(gè)非空的名字
}

接口組合

可以通過(guò)接口組合的形式聲明新的接口, 從而盡可能的復(fù)用接口。從下面的例子可以看出, ReadWriterReaderWrite 的類型集合的交集。

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

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

// ReadWriter&#39;s methods are Read, Write, and Close.
type ReadWriter interface {
        Reader  // includes methods of Reader in ReadWriter&#39;s method set
        Writer  // includes methods of Writer in ReadWriter&#39;s method set
}

通用接口

上面說(shuō)的接口都必須要實(shí)現(xiàn)具體的方法, 但是類型集合中無(wú)法包含基礎(chǔ)的數(shù)據(jù)類型。如: int, float, string...。通過(guò)下面的定義, 可以用來(lái)表示包含基礎(chǔ)數(shù)據(jù)類型的類型集合。在 golang.org/x/exp/constraints 中定義了基礎(chǔ)數(shù)據(jù)類型的集合。我們可以看到 符號(hào), 它表示包含潛在類型為 int | int8 | int16 | int32 | int64 的類型, | 表示取并集。Singed 就表示所有類型為 int 的類型集合。

// Signed is a constraint that permits any signed integer type.
// If future releases of Go add new predeclared signed integer types,
// this constraint will be modified to include them.
type Signed interface {
     ~int | ~int8 | ~int16 | ~int32 | ~int64
}

type myInt int // 潛在類型為 int

func add[T constraints.Integer](x, y T) T {
        return x + y
}

func main() {
        var x, y myInt = 1, 2
        fmt.Println(add[myInt](x, y))
}

下面來(lái)看一些特殊的定義

// 潛在類型為 int, 并且實(shí)現(xiàn)了 String 方法的類型
type E interface {
    ~int
    String() string
}

type mInt int // 屬于 E 的類型集合
func (m mInt) String() string {
    return fmt.Sprintf("%v", m)
}

// 潛在類型必須是自己真實(shí)的類型
type F interface {
    ~int
    // ~mInt  invalid use of ~ (underlying type of mInt is int)
    // ~error illegal: error is an interface
}

// 基礎(chǔ)接口可以作為形參和類型參數(shù)類型, 通用類型只能作為類型參數(shù)類型, E 只能出現(xiàn)在類型參數(shù)中 [T E]
var x E                    // illegal: cannot use type E outside a type constraint: interface contains type constraints
var x interface{} = E(nil) // illegal: cannot use interface E in conversion (contains specific type constraints or is comparable)

類型推導(dǎo)

由于泛型使用了類型參數(shù), 因此在實(shí)例化泛型時(shí)我們需要指定類型實(shí)參。 看下面的 case, 我們?cè)谡{(diào)用函數(shù)的時(shí)候并沒有指定類型實(shí)參, 這里是編譯器進(jìn)行了類型推導(dǎo), 推導(dǎo)出類型實(shí)參, 不需要顯性的傳入。

func add[T constraints.Integer](x, y T) T {
    return x + y
}

func main() {
    fmt.Println(add(1, 1)) // add[int](1,1)
}

有時(shí)候, 編譯器無(wú)法推導(dǎo)出具體類型。則需要指定類型, 或者更換寫法, 也許可以推斷出具體類型。

// 將切片中的值擴(kuò)大
func Scale[E constraints.Integer](s []E, c E) []E {
    r := make([]E, len(s))
    for i, v := range s {
        r[i] = v * c
    }
    return r
}

func ScaleAndPrint(p Point) {
    r := Scale(p, 2)
    r.string() // 非法, Scale 返回的是 []int32
}

type Point []int32

func (p Point) string() {
    fmt.Println(p)
}

// 方法更新,這樣傳入的是 Point 返回的也是 Point
func Scale[T ~[]E, E constraints.Integer](s T, c E) T {
    r := make([]E, len(s))
    for i, v := range s {
        r[i] = v * c
    }
    return r
}

泛型的使用

go 是在 1.8 版本中開始引入泛型的。下面主要介紹一下什么時(shí)候使用泛型:

內(nèi)置容器類型

在 go 中, 提供以下容器類型:map, slice, channel。當(dāng)我們用到容器類型時(shí), 且邏輯與容器具體的類型無(wú)關(guān), 這個(gè)時(shí)候可以考慮泛型。這樣我們可以在調(diào)用時(shí)指定具體的類型實(shí)參, 從而避免了類型斷言。例如,下面的例子, 返回 map 中的 key。

// comparable 是一個(gè)內(nèi)置類型, 只能用于對(duì)類型參數(shù)的約束。在 map 中, key 必須是可比較類型。
func GetKeys[K comparable, V any](m map[K]V) []K {
    res := make([]K, 0, len(m))
    for k := range m {
        res = append(res, k)
    }
    return res
}

通用的結(jié)構(gòu)體

對(duì)于一些通用的結(jié)構(gòu)體, 我們應(yīng)該使用泛型。例如, 棧、隊(duì)列、樹結(jié)構(gòu)。這些都是比較通用的結(jié)構(gòu)體, 且邏輯都與具體的類型無(wú)關(guān), 因此需要使用泛型。下面是一個(gè)棧的例子:

type Stack[T any] []T

func (s *Stack[T]) Push(item T) {
    *s = append(*s, item)
}

func (s *Stack[T]) Pop() T {
    if len(*s) == 0 {
        panic("can not pop item in emply stack")
    }
    lastIndex := len(*s) - 1
    item := (*s)[lastIndex]
    *s = (*s)[:lastIndex]
    return item
}

func main() {
    var s Stack[int]
    s.Push(9)
    fmt.Println(s.Pop())
    s.Push(9)
    s.Push(8)
    fmt.Println(s.Pop(), s.Pop())
}

通用的函數(shù)

有些類型會(huì)實(shí)現(xiàn)相同的方法, 但是對(duì)于這些類型的處理邏輯又與具體類型的實(shí)現(xiàn)無(wú)關(guān)。例如: 兩個(gè)數(shù)比大小, 只要實(shí)現(xiàn) Ordered 接口即可進(jìn)行大小比較:

func Min[T constraints.Ordered](x, y T) T {
    if x < y {
        return x
    }

    return y
}

func main() {
    fmt.Println(Min(5, 6))
    fmt.Println(Min(6.6, 9.9))
}

總結(jié)

go 在引入泛型算是一次較大的改動(dòng)。我們只有弄清楚類型參數(shù)、類型約束、類型集合、基礎(chǔ)接口、通用接口、泛型函數(shù)、泛型類型、泛型接口等概念, 才能不會(huì)困惑。核心改動(dòng)點(diǎn)還是引入了類型參數(shù), 使用接口來(lái)定義類型集合。

當(dāng)然,也不能為了使用泛型而使用泛型。還是要具體的 case 具體來(lái)分析。 簡(jiǎn)單的指導(dǎo)原則就是, 當(dāng)你發(fā)現(xiàn)你的代碼除了類型不同外, 其余代碼邏輯都相同; 或者你寫了許多重復(fù)代碼, 僅僅是為了支持不同類型; 那么你可以考慮使用泛型。

推薦學(xué)習(xí):Golang教程

The above is the detailed content of In-depth understanding of generics in golang (Generic). For more information, please follow other related articles on the PHP Chinese website!

Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn

Hot AI Tools

Undress AI Tool

Undress AI Tool

Undress images for free

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Clothoff.io

Clothoff.io

AI clothes remover

Video Face Swap

Video Face Swap

Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Tools

Notepad++7.3.1

Notepad++7.3.1

Easy-to-use and free code editor

SublimeText3 Chinese version

SublimeText3 Chinese version

Chinese version, very easy to use

Zend Studio 13.0.1

Zend Studio 13.0.1

Powerful PHP integrated development environment

Dreamweaver CS6

Dreamweaver CS6

Visual web development tools

SublimeText3 Mac version

SublimeText3 Mac version

God-level code editing software (SublimeText3)

How does the switch statement work in Go? How does the switch statement work in Go? Jul 30, 2025 am 05:11 AM

Go's switch statement will not be executed throughout the process by default and will automatically exit after matching the first condition. 1. Switch starts with a keyword and can carry one or no value; 2. Case matches from top to bottom in order, only the first match is run; 3. Multiple conditions can be listed by commas to match the same case; 4. There is no need to manually add break, but can be forced through; 5.default is used for unmatched cases, usually placed at the end.

how to break from a nested loop in go how to break from a nested loop in go Jul 29, 2025 am 01:58 AM

In Go, to break out of nested loops, you should use labeled break statements or return through functions; 1. Use labeled break: Place the tag before the outer loop, such as OuterLoop:for{...}, use breakOuterLoop in the inner loop to directly exit the outer loop; 2. Put the nested loop into the function, and return in advance when the conditions are met, thereby terminating all loops; 3. Avoid using flag variables or goto, the former is lengthy and easy to make mistakes, and the latter is not recommended; the correct way is that the tag must be before the loop rather than after it, which is the idiomatic way to break out of multi-layer loops in Go.

Using the Context Package in Go for Cancellation and Timeouts Using the Context Package in Go for Cancellation and Timeouts Jul 29, 2025 am 04:08 AM

Usecontexttopropagatecancellationanddeadlinesacrossgoroutines,enablingcooperativecancellationinHTTPservers,backgroundtasks,andchainedcalls.2.Withcontext.WithCancel(),createacancellablecontextandcallcancel()tosignaltermination,alwaysdeferringcancel()t

Building a GraphQL Server in Go Building a GraphQL Server in Go Jul 28, 2025 am 02:10 AM

InitializeaGomodulewithgomodinit,2.InstallgqlgenCLI,3.Defineaschemainschema.graphqls,4.Rungqlgeninittogeneratemodelsandresolvers,5.Implementresolverfunctionsforqueriesandmutations,6.SetupanHTTPserverusingthegeneratedschema,and7.RuntheservertoaccessGr

Performance benefits of switching to Go Performance benefits of switching to Go Jul 28, 2025 am 01:53 AM

Gooffersfasterexecutionspeedduetocompilationtonativemachinecode,outperforminginterpretedlanguageslikePythonintaskssuchasservingHTTPrequests.2.Itsefficientconcurrencymodelusinglightweightgoroutinesenablesthousandsofconcurrentoperationswithlowmemoryand

Building Performant Go Clients for Third-Party APIs Building Performant Go Clients for Third-Party APIs Jul 30, 2025 am 01:09 AM

Use a dedicated and reasonably configured HTTP client to set timeout and connection pools to improve performance and resource utilization; 2. Implement a retry mechanism with exponential backoff and jitter, only retry for 5xx, network errors and 429 status codes, and comply with Retry-After headers; 3. Use caches for static data such as user information (such as sync.Map or Redis), set reasonable TTL to avoid repeated requests; 4. Use semaphore or rate.Limiter to limit concurrency and request rates to prevent current limit or blocking; 5. Encapsulate the API as an interface to facilitate testing, mocking, and adding logs, tracking and other middleware; 6. Monitor request duration, error rate, status code and retry times through structured logs and indicators, combined with Op

How to use template.ParseFS with go embed? How to use template.ParseFS with go embed? Jul 30, 2025 am 12:35 AM

Use the template.ParseFS and embed package to compile HTML templates into binary files. 1. Import the embed package and embed the template file into the embed.FS variable with //go:embedtemplates/.html; 2. Call template.Must(template.ParseFS(templateFS,"templates/.html")))) to parse all matching template files; 3. Render the specified in the HTTP processor through tmpl.ExecuteTemplate(w,"home.html", nil)

how to properly copy a slice in go how to properly copy a slice in go Jul 30, 2025 am 01:28 AM

To correctly copy slices in Go, you must create a new underlying array instead of directly assigning values; 1. Use make and copy functions: dst:=make([]T,len(src));copy(dst,src); 2. Use append and nil slices: dst:=append([]T(nil),src...); both methods can realize element-level copying, avoid sharing the underlying array, and ensure that modifications do not affect each other. Direct assignment of dst=src will cause both to refer to the same array and are not real copying.

See all articles