Go语言特性代码展示与新特性泛型 | 青训营笔记

100 阅读9分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 8 天

Go18

go 1.18 的正式版(go version go1.18 windows/amd64 ),今天我们就GO的新特性:泛型 进行简单的尝鲜使用。 GO 中泛型涉及到两个关键词:类型参数类型约束

值、引用、指针

泛型和约束

GO 中泛型的语法

以下示例中 [] 类型参数type0、type2,其中type0受可比较类型约束 ,type1 受 int64 或 float64 类型约束。

 func funcName[type0 comparable, type1 int64 | float64](arg0 type0, arg1 type1) {}

该函数内部打印每个参数的类型和值

 func main() {
     funcName("arg0", 1)
     funcName("arg0", 3.5)
 }
 func funcName[type0 comparable, type1 int | float64](arg0 type0, arg1 type1) {
     fmt.Printf("arg0 type: %T value: %v\t", arg0, arg0)
     fmt.Printf("arg1 type: %T value: %v\n", arg1, arg1)
 }

结果:

 arg0 type: string value: arg0   arg1 type: int value: 1
 arg0 type: string value: arg0   arg1 type: float64 value: 3.5

Go泛型 若存在违反泛型函数中的类型约束,能够在编译时捕获

我们尝试给funcName 函数的第二个参数传如string字面量

 func main(){
     funcName("arg0", "string")
 }

编译结果:

 command-line-arguments
 ​
 .\test_1.go:9:10: string does not implement int|float64 

指定类型参数调用

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

 func main(){
     funcName[string, int]("arg0", 4)
 }

通过interfac进行类型约束

类型约束可以通过interface 进行绑定

声明一个Number interface类型作为类型限制 在interface内声明int64float64的合集

 type Number interface {
    float64 | int

修改实例函数 arg1 类型参数的类型约束为接口 Number

 func main() {
     funcName("arg0", 1)
     funcName("arg0", 3.5)
 }
 func funcName[type0 comparable, type1 Number](arg0 type0, arg1 type1) {
     fmt.Printf("arg0 type: %T value: %v\t", arg0, arg0)
     fmt.Printf("arg1 type: %T value: %v\n", arg1, arg1)
 }

执行结果

 arg0 type: string value: arg0   arg1 type: int value: 1
 arg0 type: string value: arg0   arg1 type: float64 value: 3.5

# Go语言退出、结束函数或者协程方式

::: tip Go语言中有很多让我觉得误解的一些终结语句,我想 blockcontinue 只是最基本的也是最简单的。

Go语言几种退出程序的方式:

  1. os.Exit() 退出程序
  2. panic() 抛出异常
  3. return 退出函数
  4. defer + recover() 捕获异常
  5. runtime.Goexit() 退出当前协程
  6. os.Kill 杀死进程
  7. os.Interrupt 中断进程
  8. block 跳出循环 (不讲)
  9. continue 跳出当前循环 继续下一次循环 (不讲)

:::

runtime.Goexit() 退出当前协程

💡简单的一个案例如下:

 package main
 ​
 import (
     "fmt"
     "runtime"
     "time"
 )
 ​
 func main() {
     //Go语言几种退出程序的方式
     //1. os.Exit() 退出程序
     //2. panic() 抛出异常
     //3. return 退出函数
     //4. defer + recover() 捕获异常
     //5. runtime.Goexit() 退出当前协程
     //6. os.Kill 杀死进程
     //7. os.Interrupt 中断进程
     //8. block 跳出循环
     //9. continue  跳出当前循环 继续下一次循环
 ​
     fmt.Println("main() 开始的部分")
     go func() {
         fmt.Println("go func() 开始的部分")
         func() {
             fmt.Println("func() 开始的部分")
             go func() {
                 fmt.Println("最里面go func() 开始的部分")
                 runtime.Goexit() //退出当前协程
 ​
                 fmt.Println("最里面go func() 结束的部分")
             }()
             fmt.Println("func() 结束的部分")
             //等待2s
             time.Sleep(2 * time.Second)
         }()
         fmt.Println("go func() 结束的部分")
     }()
     //等待5s
     time.Sleep(time.Second * 5)
     fmt.Println("main() 结束的部分")
 }
 ​

🚀 编译结果如下:

 [Running] go run "d:\文档\最近的\awesome-golang\docs\code\go-super\61-main.go"
 main() 开始的部分
 go func() 开始的部分
 func() 开始的部分
 最里面go func() 开始的部分
 func() 结束的部分
 go func() 结束的部分
 main() 结束的部分

::: warning 📜 对上面的解释:

 runtime.Goexit() //退出当前协程

只是退出当前的 go func() 协程,并不是当前函数。

:::

os.Exit(-1) 退出当前程序

::: tip 函数定义: 函数定义:func Exit(code int)

Exit 函数可以让当前程序以给出的状态码 code 退出。一般来说,状态码 0 表示成功,非 0 表示出错。程序会立刻终止,并且 defer 的函数不会被执行。

 package main
 ​
 import (
     "fmt"
     "os"
 )
 ​
 func main() {
     var num int = 0
 ​
     fmt.Printf("Enter number:")
     fmt.Scanf("%d", &num)
 ​
     if num > 0 {
         fmt.Printf("n > 0  Program terminated\n")
         os.Exit(0)
     } else if num < 0 {
         fmt.Printf("n < 0  Program continue\n")
     }
     fmt.Printf("Program finished normally\n")
 }

🚀 编译结果如下:

 PS D:\文档\最近的\awesome-golang\docs\code\go-super> go run .\66-main.go
 Enter number:2
 n > 0  Program terminated
 PS D:\文档\最近的\awesome-golang\docs\code\go-super> go run .\66-main.go
 Enter number:-1
 n < 0  Program continue
 Program finished normally
 PS D:\文档\最近的\awesome-golang\docs\code\go-super> go run .\66-main.go
 Enter number:0
 Program finished normally

:::

💡简单的一个案例如下:

 package main
 ​
 import (
     "fmt"
     "os"
     "time"
 )
 ​
 func main() {
     fmt.Println("main() 开始的部分")
     go func() {
         fmt.Println("go func() 开始的部分")
         func() {
             fmt.Println("func() 开始的部分")
             go func() {
                 fmt.Println("最里面go func() 开始的部分")
                 os.Exit(-1) //退出程序
 ​
                 fmt.Println("最里面go func() 结束的部分")
             }()
             //等待2s
             time.Sleep(2 * time.Second)    
             fmt.Println("func() 结束的部分")
         }()
         fmt.Println("go func() 结束的部分")
     }()
     //等待5s
     time.Sleep(time.Second * 5)
     fmt.Println("main() 结束的部分")
 }
 ​

🚀 编译结果如下:

 [Running] go run "d:\文档\最近的\awesome-golang\docs\code\go-super\61-main.go"
 main() 开始的部分
 go func() 开始的部分
 func() 开始的部分
 最里面go func() 开始的部分
 exit status 0xffffffff

::: warning 📜 对上面的解释:

 os.Exit(-1) //退出程序

退出当前的 func main() 程序

:::

os.Exit(0) 中断进程

💡简单的一个案例如下:

 package main
 ​
 import (
     "fmt"
     "os"
     "time"
 )
 ​
 func main() {
     fmt.Println("main() 开始的部分")
     go func() {
         fmt.Println("go func() 开始的部分")
         func() {
             fmt.Println("func() 开始的部分")
             go func() {
                 fmt.Println("最里面go func() 开始的部分")
                 //中断进程
                 os.Exit(0)
 ​
                 fmt.Println("最里面go func() 结束的部分")
             }()
             //等待2s
             time.Sleep(2 * time.Second)
             fmt.Println("func() 结束的部分")
 ​
         }()
         fmt.Println("go func() 结束的部分")
     }()
     //等待5s
     time.Sleep(time.Second * 5)
     fmt.Println("main() 结束的部分")
 }
 ​

🚀 编译结果如下:

 [Running] go run "d:\文档\最近的\awesome-golang\docs\code\go-super\63-main.go"
 main() 开始的部分
 go func() 开始的部分
 func() 开始的部分
 最里面go func() 开始的部分

::: warning 📜 对上面的解释: 状态码为 0 ,退出但是不报错

:::

panic() 抛出异常退出

::: details 或许你可以使用defer func() 抛出异常继续执行

 package main
 ​
 import (
     "fmt"
     "time"
 )
 ​
 func main() {
     fmt.Println("main() 开始的部分")
     go func() {
         fmt.Println("go func() 开始的部分")
         func() {
             fmt.Println("func() 开始的部分")
             go func() {
                 fmt.Println("最里面go func() 开始的部分")
                 //defer异常捕获
                 defer func() {
                     if err := recover(); err != nil {
                         fmt.Println("最里面go func() defer异常捕获", err)
                     }
                 }()
                 panic("最里面go func() 抛出异常退出")
                 fmt.Println("最里面go func() 结束的部分")
             }()
             //等待2s
             time.Sleep(2 * time.Second)
             fmt.Println("func() 结束的部分")
         }()
         fmt.Println("go func() 结束的部分")
     }()
     //等待5s
     time.Sleep(time.Second * 5)
     fmt.Println("main() 结束的部分")
 }
 ​

🚀 编译结果如下:

 [Running] go run "d:\文档\最近的\awesome-golang\docs\code\go-super\61-main.go"
 main() 开始的部分
 go func() 开始的部分
 func() 开始的部分
 最里面go func() 开始的部分
 最里面go func() defer异常捕获 最里面go func() 抛出异常退出
 func() 结束的部分
 go func() 结束的部分
 main() 结束的部分

:::

💡简单的一个案例如下:

 package main
 ​
 import (
     "fmt"
     "time"
 )
 ​
 func main() {
     //Go语言几种退出程序的方式
     //1. os.Exit() 退出程序
     //2. panic() 抛出异常
     //3. return 退出函数
     //4. defer + recover() 捕获异常
     //5. runtime.Goexit() 退出当前协程
     //6. os.Kill 杀死进程
     //7. os.Interrupt 中断进程
     //8. block 跳出循环
     //9. continue  跳出当前循环 继续下一次循环
 ​
     fmt.Println("main() 开始的部分")
     go func() {
         fmt.Println("go func() 开始的部分")
         func() {
             fmt.Println("func() 开始的部分")
             go func() {
                 fmt.Println("最里面go func() 开始的部分")
 ​
                 panic("最里面go func() 抛出异常退出")
                 fmt.Println("最里面go func() 结束的部分")
             }()
             //等待2s
             time.Sleep(2 * time.Second)
             fmt.Println("func() 结束的部分")
         }()
         fmt.Println("go func() 结束的部分")
     }()
     //等待5s
     time.Sleep(time.Second * 5)
     fmt.Println("main() 结束的部分")
 }
 ​

🚀 编译结果如下:

 [Running] go run "d:\文档\最近的\awesome-golang\docs\code\go-super\61-main.go"
 main() 开始的部分
 go func() 开始的部分
 func() 开始的部分
 最里面go func() 开始的部分
 panic: 最里面go func() 抛出异常退出
 ​
 goroutine 7 [running]:
 main.main.func1.1.1()
     d:/文档/最近的/awesome-golang/docs/code/go-super/61-main.go:28 +0x65
 created by main.main.func1.1
     d:/文档/最近的/awesome-golang/docs/code/go-super/61-main.go:25 +0x65
 exit status 2

::: warning 📜 对上面的解释: 可以看出来这两者之间的差距,recover()使得 程序结束当前协程~继续执行使用。

不使用的化,会直接退出~

:::

return 跳出当前函数

return 是我们最常用的一个用法:在设定返回值为1002,然后return触发defer语句,执行完成后(即便是defer抛出panic后被捕获),返回1002。

我们都知道 panic 会触发defer 由于 return会触发 defer,函数抛出 panic 也会触发 defer。所以我们可以在 defer 中,特别是通过 recovery() 函数捕获 panic 后,修改函数的返回值。

 func main() {
     fmt.Println(Test0())
 }
  
 func Test0()(ret string){
     defer func() {
         err:=recover()
         if err!=nil{
             ret=fmt.Sprint(err)
         }
     }()
     panic("this is a panic")
     return "normal"
 }

🚀 编译结果如下:

 this is a panic

::: warning 📜 对上面的解释: 注意 panicreturn 的循序很重要,如果当 return "normal" 在前面,那么很明显会输出 normalthis is a panic 将不会输出。

:::

💡简单的一个案例如下:

 package main
 ​
 import (
     "fmt"
     "time"
 )
 ​
 func main() {
     fmt.Println("main() 开始的部分")
     go func() {
         fmt.Println("go func() 开始的部分")
         func() {
             fmt.Println("func() 开始的部分")
             go func() {
                 fmt.Println("最里面go func() 开始的部分")
                 return
 ​
                 fmt.Println("最里面go func() 结束的部分")
             }()
             //等待2s
             time.Sleep(2 * time.Second)
             fmt.Println("func() 结束的部分")
         }()
         fmt.Println("go func() 结束的部分")
     }()
     //等待5s
     time.Sleep(time.Second * 5)
     fmt.Println("main() 结束的部分")
 }
 ​

🚀 编译结果如下:

 [Running] go run "d:\文档\最近的\awesome-golang\docs\code\go-super\61-main.go"
 main() 开始的部分
 go func() 开始的部分
 func() 开始的部分
 最里面go func() 开始的部分
 func() 结束的部分
 go func() 结束的部分
 main() 结束的部分

::: warning📜 对上面的解释: return 直接跳出当前的函数, 注意这个函数也可以是当前的协程 go func()

:::

Lable跳转标签

💡简单的一个案例如下:

 package main
 ​
 import (
     "fmt"
     "time"
 )
 ​
 func main() {
     //Go语言几种退出程序的方式
     //1. os.Exit() 退出程序
     //2. panic() 抛出异常
     //3. return 退出函数
     //4. defer + recover() 捕获异常
     //5. runtime.Goexit() 退出当前协程
     //6. os.Kill 杀死进程
     //7. os.Interrupt 中断进程
     //8. block 跳出循环
     //9. continue  跳出当前循环 继续下一次循环
 ​
     fmt.Println("main() 开始的部分")
     go func() {
         fmt.Println("go func() 开始的部分")
         func() {
 ​
             fmt.Println("func() 开始的部分")
             go func() {
             Lable1:
                 fmt.Println("最里面go func() 开始的部分")
                 //等待1s
                 time.Sleep(time.Second)
                 // 标签跳转
                 goto Lable1
 ​
                 fmt.Println("最里面go func() 结束的部分")
             }()
             //等待2s
             time.Sleep(2 * time.Second)
             fmt.Println("func() 结束的部分")
 ​
         }()
         fmt.Println("go func() 结束的部分")
     }()
     //等待5s
     time.Sleep(time.Second * 5)
     fmt.Println("main() 结束的部分")
 }
 ​

🚀 编译结果如下:

 PS D:\文档\最近的\awesome-golang\docs\code\go-super> go run .\64-main.go
 main() 开始的部分
 go func() 开始的部分
 func() 开始的部分
 最里面go func() 开始的部分
 最里面go func() 开始的部分
 func() 结束的部分
 go func() 结束的部分
 最里面go func() 开始的部分
 最里面go func() 开始的部分
 最里面go func() 开始的部分
 main() 结束的部分