『每周译Go』Go 官方出品泛型教程:如何开始使用泛型

Posted GoCN

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了『每周译Go』Go 官方出品泛型教程:如何开始使用泛型相关的知识,希望对你有一定的参考价值。

备注:这是一个beta 版本的内容

这个教程介绍了Go泛型的基础概念。通过泛型,你可以声明并使用函数或者是类型,那些用于调用代码时参数需要兼容多个不同类型的情况。

在这个教程里,你会声明两个普通的函数,然后复制一份相同的逻辑到一个泛型的方法里。

你会通过以下几个章节来进行学习:

  • 为你的代码创建一个文件夹;
  • 添加非泛型函数;
  • 添加一个泛型函数来处理多种类型;
  • 在调用泛型函数时删除类型参数;
  • 声明一个类型约束。
  • 备注:对其他教程,可以查看教程(https://gocn.vip/topics/20885) 

    备注:你同时也可以使用 “Go dev branch”(https://gocn.vip/topics/20885)模式来编辑和运行你的代码,如果你更喜欢以这种形式的话



    前提条件


  • 安装Go 1.18 Beta 1或者更新的版本。对安装流程,请看安装并使用beta版本。
  • 代码编辑器。任何你顺手的代码编辑器。
  • 一个命令终端。Go在任何终端工具上都很好使用,比如Linux 、Mac、PowerShell或者Windows上的cmd。
  • 安装并使用beta版本

    这个教程需要Beta 1的泛型特性。安装beta版本,需要通过下面几个步骤:

    1、 执行下面的指令安装beta版本

     $ go install golang.org/dl/go1.18beta1@latest

    2、 执行下面的指令下载更新

     $ go1.18beta1 download

    3、用beta版本执行go命令,而不是Go的发布版本(如果你本地有安装的话)

    你可以使用beta版本名称或者把beta重命名成别的名称来执行命令。

  • 使用beta版本名称,你可以通过go1.18beta1来执行指令而不是go:
  • $ go1.18beta1 version
  • 通过对beta版本名称重命名,你可以简化指令:shell

  • $ alias go=go1.18beta1

    $ go version

    在这个教程中将假设你已经对beta版本名称进行了重命名。



    为你的代码创建一个文件夹



    在一开始,先给你要写的代码创建一个文件夹

    1、 打开一个命令提示符并切换到/home文件夹

    在Linux或者Mac上:

    cd

    在windows上:

     C:\\> cd %HOMEPATH%

    在接下去的教程里面会用$来代表提示符。指令在windows上也适用。

    2、 在命令提示符下,为你的代码创建一个名为generics的目录

     $ mkdir generics
       $ cd generics

    3、 创建一个module来存放你的代码

    执行go mod init指令,参数为你新代码的module路径

     $ go mod init example/generics
       go: creating new go.mod: module example/generics

    备注:对生产环境,你会指定一个更符合你自己需求的module路径。更多的请看依赖管理(https://go.dev/doc/modules/managing-dependencies)

    接下来,你会增加一些简单的和maps相关的代码。



    添加普通函数



    在这一步中,你将添加两个函数,每个函数都会累加 map 中的值 ,并返回总和。

    你将声明两个函数而不是一个,因为你要处理两种不同类型的map:一个存储int64类型的值,另一个存储float64类型的值。

    写代码

    1、 用你的文本编辑器,在generics文件夹里面创建一个叫main.go的文件。你将会在这个文件内写你的Go代码。

    2、 到main.go文件的上方,粘贴如下的包的声明。

     package main

    一个独立的程序(相对于一个库)总是在main包中。

    3、 在包的声明下面,粘贴以下两个函数的声明。

     // SumInts adds together the values of m.
       func SumInts(m map[string]int64) int64 
           var s int64
           for _, v := range m 
               s += v
           
           return s
       
       
       // SumFloats adds together the values of m.
       func SumFloats(m map[string]float64) float64 
           var s float64
           for _, v := range m 
               s += v
           
           return s
       

    在这段代码中,你:

  • 声明两个函数,将一个map的值加在一起,并返回总和。
  • SumFloats接收一个map,key为string类型,value为floa64类型。
  • SumInt接收一个map,key为string类型,value为int64类型。
  • 4、 在main.go的顶部,包声明的下面,粘贴以下main函数,用来初始化两个map,并在调用你在上一步中声明的函数时将它们作为参数。
    func main() 
       // Initialize a map for the integer values
       ints := map[string]int64
           "first": 34,
           "second": 12,
       
       
       // Initialize a map for the float values
       floats := map[string]float64
           "first": 35.98,
           "second": 26.99,
       
       
       fmt.Printf("Non-Generic Sums: %v and %v\\n",
           SumInts(ints),
           SumFloats(floats))
       

    在这段代码中,你:

  • 初始化一个key为string,value为float64的map和一个key为string,value为int64的map,各有2条数据;
  • 调用之前声明的两个方法来获取每个map的值的总和;
  • 打印结果。
  • 5、 靠近main.go顶部,仅在包声明的下方,导入你刚刚写的代码所需要引用的包。

    第一行代码应该看起来如下所示:

    package main
       import "fmt"

    6、 保存main.go.

    运行代码

    在main.go所在目录下,通过命令行运行代码

    $ go run .
    Non-Generic Sums: 46 and 62.97

    有了泛型,你可以只写一个函数而不是两个。接下来,你将为maps添加一个泛型函数,来允许接收整数类型或者是浮点数类型。



    添加泛型函数处理多种类型


    在这一节,你将会添加一个泛型函数来接收一个map,可能值是整数类型或者浮点数类型的map,有效地用一个函数替换掉你刚才写的2个函数。

    为了支持不同类型的值,这个函数需要有一个方法来声明它所支持的类型。另一方面,调用代码将需要一种方法来指定它是用整数还是浮点数来调用。

    为了实现上面的描述,你将会声明一个除了有普通函数参数,还有类型参数的函数。这个类型参数实现了函数的通用性,使得它可以处理多个不同的类型。你将会用类型参数和普通函数参数来调用这个泛型函数。

    每个类型参数都有一个类型约束,类似于每个类型参数的meta-type。每个类型约束都指定了调用代码时每个对应输入参数的可允许的类型。

    虽然类型参数的约束通常代表某些类型,但是在编译的时候类型参数只代表一个类型-在调用代码时作为类型参数。如果类型参数的类型不被类型参数的约束所允许,代码则无法编译。

    需要记住的是类型参数必须满足泛型代码对它的所有的操作。举个例子,如果你的代码尝试去做一些string的操作(比如索引),而这个类型参数包含数字的类型,那代码是无法编译的。

    在下面你要编写的代码里,你会使用允许整数或者浮点数类型的限制。

    写代码 

    1、 在你之前写的两个函数的下方,粘贴下面的泛型函数

     // SumIntsOrFloats sums the values of map m. It supports both int64 and float64
       // as types for map values.
       func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V 
           var s V
           for _, v := range m 
               s += v
           
           return s
       

    在这段代码里,你:

  • 声明了一个带有2个类型参数(方括号内)的SumIntsOrFloats函数,K和V,一个使用类型参数的参数,类型为map[K]V的参数m。
  • 为K类型参数指定可比较的类型约束。事实上,针对此类情况,在Go里面可比较的限制是会提前声明。它允许任何类型的值可以作为比较运算符==和!=的操作符。在Go里面,map的key是需要可比较的。因此,将K声明为可比较的是很有必要的,这样你就可以使用K作为map变量的key。这样也保证了调用代码方使用一个被允许的类型做map的key。
  • 为V类型参数指定一个两个类型合集的类型约束:int64和float64。使用|指定了2个类型的合集,表示约束允许这两种类型。任何一个类型都会被编译器认定为合法的传参参数。
  • 指定参数m为类型map[K]V,其中K和V的类型已经指定为类型参数。注意到因为K是可比较的类型,所以map[K]V是一个有效的map类型。如果我们没有声明K是可比较的,那么编译器会拒绝对map[K]V的引用。
  • 2、 在main.go里,在你现在的代码下方,粘贴如下代码
     fmt.Printf("Generic Sums: %v and %v\\n",
           SumIntsOrFloats[string, int64](ints),
           SumIntsOrFloats[string, float64](floats))

    在这段代码里,你:

  • 调用你刚才声明的泛型函数,传递你创建的每个map。

  • 指定类型参数-在方括号内的类型名称-来明确你所调用的函数中应该用哪些类型来替代类型参数。

    你将会在下一节看到,你通常可以在函数调用时省略类型参数。Go通常可以从代码里推断出来。

  • 打印函数返回的总和。

  • 运行代码

    在main.go所在目录下,通过命令行运行代码

    $ go run .
    Non-Generic Sums: 46 and 62.97
    Generic Sums: 46 and 62.97

    为了运行你的代码,在每次调用的时候,编译器都会用该调用中指定的具体类型替换类型参数。

    在调用你写的泛型函数时,你指定了类型参数来告诉编译器用什么类型来替换函数的类型参数。正如你将在下一节所看到的,在许多情况下,你可以省略这些类型参数,因为编译器可以推断出它们。



    当调用泛型函数时移除类型参数



    在这一节,你会添加一个泛型函数调用的修改版本,通过一个小的改变来简化代码。在这个例子里你将移除掉不需要的类型参数。

    当Go编译器可以推断出你要使用的类型时,你可以在调用代码中省略类型参数。编译器从函数参数的类型中推断出类型参数。

    注意这不是每次都可行的。举个例子,如果你需要调用一个没有参数的泛型函数,那么你需要在调用函数时带上类型参数。

    写代码
  • 在main.go的代码下方,粘贴下面的代码。
  • fmt.Printf("Generic Sums, type parameters inferred: %v and %v\\n",
          SumIntsOrFloats(ints),
          SumIntsOrFloats(floats))

    在这段代码里,你:

  • 调用泛型函数,省略类型参数。
  • 运行代码

    在main.go所在目录下,通过命令行运行代码

    $ go run .
    Non-Generic Sums: 46 and 62.97
    Generic Sums: 46 and 62.97
    Generic Sums, type parameters inferred: 46 and 62.97

    接下来,你将通过把整数和浮点数的合集定义到一个你可以重复使用的类型约束中,比如从其他的代码,来进一步简化这个函数。



    声明类型约束



    在最后一节中,你将把你先前定义的约束移到它自己的interface中,这样你就可以在多个地方重复使用它。以这种方式声明约束有助于简化代码,尤其当一个约束越来越复杂的时候。

    你将类型参数定义为一个interface。约束允许任何类型实现这个interface。举个例子,如果你定义了一个有三个方法的类型参数interface,然后用它作为一个泛型函数的类型参数,那么调用这个函数的类型参数必须实现这些方法。

    你将在本节中看到,约束interface也可以指代特定的类型。

    写代码

    1、 在main函数上面,紧接着import下方,粘贴如下代码来定义类型约束。

    type Number interface 
           int64 | float64
       

    在这段代码里,你:

  • 声明一个Number interface类型作为类型限制

  • 在interface内声明int64和float64的合集

    本质上,你是在把函数声明中的合集移到一个新的类型约束中。这样子,当你想要约束一个类型参数为int64或者float64,你可以使用Number interface而不是写 int64 | float64。

  • 2、 在你已写好的函数下方,粘贴如下泛型函数,SumNumbers。

     // SumNumbers sums the values of map m. Its supports both integers
       // and floats as map values.
       func SumNumbers[K comparable, V Number](m map[K]V) V 
           var s V
           for _, v := range m 
               s += v
           
           return s
       

    在这段代码,你:

  • 声明一个泛型函数,其逻辑与你之前声明的泛型函数相同,但是是使用新的interface类型作为类型参数而不是合集。和之前一样,你使用类型参数作为参数和返回类型。
  • 3、 在main.go,在你已写完的代码下方,粘贴如下代码。
     fmt.Printf("Generic Sums with Constraint: %v and %v\\n",
           SumNumbers(ints),
           SumNumbers(floats))

    在这段代码里,你:

  • 每个map依次调用SumNumbers,并打印数值的总和。
  • 与上一节一样,你可以在调用泛型函数时省略类型参数(方括号中的类型名称)。Go编译器可以从其他参数中推断出类型参数。
  • 运行代码

    在main.go所在目录下,通过命令行运行代码

    $ go run .
    Non-Generic Sums: 46 and 62.97
    Generic Sums: 46 and 62.97
    Generic Sums, type parameters inferred: 46 and 62.97
    Generic Sums with Constraint: 46 and 62.97



    总结



    完美结束!你刚才已经给你自己介绍了Go的泛型。

    如果你想继续试验,你可以尝试用整数约束和浮点数约束来写Number interface,来允许更多的数字类型。

    建议阅读的相关文章:

  • Go Tour 是一个很好的,手把手教Go基础的介绍。
  • 你可以在 Effective Go(https://go.dev/doc/effective_go) 和 How to write Go code(https://go.dev/doc/code) 中找到非常实用的GO的练习。


  • 完整代码


    你可以在Go playground运行这个代码。在playground只需要点击Run按钮即可。

    package main

    import "fmt"

    type Number interface 
        int64 | float64


    func main() 
        // Initialize a map for the integer values
        ints := map[string]int64
            "first": 34,
            "second": 12,
        

        // Initialize a map for the float values
        floats := map[string]float64
            "first": 35.98,
            "second": 26.99,
        

        fmt.Printf("Non-Generic Sums: %v and %v\\n",
            SumInts(ints),
            SumFloats(floats))

        fmt.Printf("Generic Sums: %v and %v\\n",
            SumIntsOrFloats[string, int64](ints),
            SumIntsOrFloats[string, float64](floats))

        fmt.Printf("Generic Sums, type parameters inferred: %v and %v\\n",
            SumIntsOrFloats(ints),
            SumIntsOrFloats(floats))

        fmt.Printf("Generic Sums with Constraint: %v and %v\\n",
            SumNumbers(ints),
            SumNumbers(floats))


    // SumInts adds together the values of m.
    func SumInts(m map[string]int64) int64 
        var s int64
        for _, v := range m 
            s += v
        
        return s


    // SumFloats adds together the values of m.
    func SumFloats(m map[string]float64) float64 
        var s float64
        for _, v := range m 
            s += v
        
        return s


    // SumIntsOrFloats sums the values of map m. It supports both floats and integers
    // as map values.
    func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V 
        var s V
        for _, v := range m 
            s += v
        
        return s


    // SumNumbers sums the values of map m. Its supports both integers
    // and floats as map values.
    func SumNumbers[K comparable, V Number](m map[K]V) V 
        var s V
        for _, v := range m 
            s += v
        
        return s




    原文信息


    原文地址:

    https://go.dev/doc/tutorial/generics


    原文作者:

    go.dev


    本文永久链接:https://github.com/gocn/translator/

    blob/master/2021/w49_Tutorial_Getting_started_

    with_generics.md


    译者:zxmfke


    校对:cvley



    想要了解关于 Go 的更多资讯,还可以通过扫描的方式,进群一起探讨哦~

    分享 2 个 Go1.18 新特性的官方教程

    大家好,我是煎鱼。

    最近官方更新了一篇新博文《Two New Tutorials for 1.18[1]》,用于面向有一些复杂和理解难度的新特性进行分享和教学。

    今天煎鱼就整理了内容,文末有获取在线和离线教程的方式,方便大家进行快速的学习和理解。

    泛型特性

    第一个新教程《Tutorial: Getting started with generics[2]》:

    该教程将帮助你开始使用泛型,引导你创建一个可以处理多种类型的泛型函数,并从你的代码中调用它。

    一旦创建了一个泛型函数后,就需要了解类型约束,并为你的函数编写一些约束。也可以考虑看看 GopherCon 关于《Generics[3]》的讲座,以学习更多。

    模糊测试

    第二个新教程《Tutorial: Getting started with fuzzing[4]》:

    该教程将帮助你开始进行模糊处理。演示了模糊分析如何在你的代码中发现错误,并介绍了诊断和修复问题的过程。

    在本教程中,将编写有一些 BUG 的代码,并使用模糊分析来寻找、修复和验证使用 Go 命令的 BUG。

    总结

    Go1.18 Beta1 已经在前段时间发布,在本月(2月份)很快就会发布 Go 1.18,也就是泛型即将要正式问世了。

    这个新版本包含一些 Go 的全新概念,想必官方对社区接受度也有一定的担忧,为此发布了两个新教程来帮助大家了解这些即将推出的功能。建议大家都看看!

    可以在这个公众号回复【118】,会有离线版的 Go 官方教程获取

    未关注的需关注:

    也可以直接根据下方的参考链接访问外网在线查看

    学起来!

    参考资料
    [1]

    Two New Tutorials for 1.18: https://go.dev/blog/tutorials-go1.18

    [2]

    Tutorial: Getting started with generics: https://go.dev/doc/tutorial/generics

    [3]

    Generics: https://www.youtube.com/watch?v=35eIxI_n5ZM&t=1755s

    [4]

    Tutorial: Getting started with fuzzing: https://go.dev/doc/tutorial/fuzz


    关注煎鱼,获取业内第一手消息和知识

    以上是关于『每周译Go』Go 官方出品泛型教程:如何开始使用泛型的主要内容,如果未能解决你的问题,请参考以下文章

    分享 2 个 Go1.18 新特性的官方教程

    『每周译Go』golang 垃圾回收器如何标记内存?

    『每周译Go』Golang 在大规模流处理场景下的最小化内存使用

    取代泛型,错误处理成为新挑战,Go开发者Q2报告出炉!

    『GCTT 出品』测试 Go 语言 Web 应用

    Go 1.18 终于来了!