go学习笔记03-接口、泛型、异常

接口

Person接口,有两个对外暴露的方法Walk和Say,在接口里,函数的参数名变得不再重要,当然如果想加上参数名和返回值名也是允许的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// 起重机接口
type Crane interface {
  JackUp() string
  Hoist() string
}

// 起重机A
type CraneA struct {
  work int
}

// Go 的接口是 隐式实现 的(不需要写 implements),任何类型只要实现了JackUp、Hoist这两个方法,就自动满足 Crane 接口!
func (c CraneA) Work() {
  fmt.Println("使用技术A")
}
func (c CraneA) JackUp() string {
  c.Work()
  return "jackup"
}
func (c CraneA) Hoist() string {
  c.Work()
  return "hoist"
}

// 起重机B
type CraneB struct {
  boot string
}
func (c CraneB) Boot() {
  fmt.Println("使用技术B")
}
func (c CraneB) JackUp() string {
  c.Boot()
  return "jackup"
}
func (c CraneB) Hoist() string {
  c.Boot()
  return "hoist"
}

type ConstructionCompany struct {
  Crane Crane
}

// 在Build() 方法没有直接修改 ConstructionCompany 的字段,但接口的底层实现可能涉及状态变更或方法调用的一致性。此处 传递指针接收者,可以避免每次调用方法时复制整个结构体!
func (c *ConstructionCompany) Build() {
  fmt.Println(c.Crane.JackUp())
  fmt.Println(c.Crane.Hoist())
  fmt.Println("建筑完成")
}

func main() {
  // 使用起重机A ,CraneA{} 表示:创建一个 CraneA 类型的零值实例;ConstructionCompany{CraneA{}}表示将CraneA 类型的零值实例赋值给类型的零值实例;ConstructionCompany的属性Crane
  company := ConstructionCompany{CraneA{}}
  company.Build()
  fmt.Println()
  // 更换起重机B
  company.Crane = CraneB{}
  company.Build()
}

泛型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
type SayAble[T int | string] interface {
   // 返回类型为 T
   Say() T
}

type Person[T int | string] struct {
   msg T
}

func (p Person[T]) Say() T {
   return p.msg
}

func main() {
  var s SayAble[string]
  s = Person[string]{"hello world"}
  fmt.Println(s.Say())
}

类型集-并集

接口类型 SignedInt 是一个类型集,是有符号整数类型的超集、并集。

1
2
3
type SignedInt interface {
   int8 | int16 | int | int32 | int64
}

类型集-交集

SignedInt和Integer的交集,就是SignedInt

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
type SignedInt interface {
   int8 | int16 | int | int32 | int64
}

type Integer interface {
   int8 | int16 | int | int32 | int64 | uint8 | uint16 | uint | uint32 | uint64
}

type Number interface {
  SignedInt
  Integer
}

func Do[T Number](n T) T {
   return n
}
Do[int](2)
DO[uint](2) //无法通过编译

类型集-空集

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
type SignedInt interface {
  int8 | int16 | int | int32 | int64
}

type UnsignedInt interface {
  uint8 | uint16 | uint | uint32 | uint64
}

type Integer interface {
  SignedInt
  UnsignedInt
}

队列

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 定义了一个泛型类型 Queue,类型参数 T 可以是任意类型(any 是 interface{} 的别名),底层使用 切片 []T 存储元素
type Queue[T any] []T

// 传递指针进去
func (q *Queue[T]) Push(e T) {
   // 使用 append 扩展底层数组
  *q = append(*q, e)
}

func (q *Queue[T]) Pop() (_ T) {
  if q.Size() > 0 {
    res := q.Peek()
    *q = (*q)[1:]
    return res
  }
  return
}

func (q *Queue[T]) Peek() (_ T) {
  if q.Size() > 0 {
    return (*q)[0]
  }
  return
}

func (q *Queue[T]) Size() int {
  return len(*q)
}

go的异常

Go 没有try-catch-finally,Go 的异常有三种级别:error、panic、fatal

error

正常的流程出错,需要处理,直接忽略掉不处理程序也不会崩溃。

error本身是一个预定义的接口,该接口下只有一个方法Error(),该方法的返回值是字符串,用于输出错误信息。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
type error interface {
Error() string
}

// 1. 使用errors包下的New函数
err := errors.New("这是一个错误")

// 2. 使用fmt包下的Errorf函数,可以得到一个格式化参数的 error
err := fmt.Errorf("这是%d个格式化参数的的错误", 1)

// 3. 定义自定义错误类型
type MyError struct {
    Code    int
    Message string
    ID      string
}
//  实现 error 接口
func (e MyError) Error() string {
    return fmt.Sprintf("error [%d] %s (ID: %s)", e.Code, e.Message, e.ID)
}
// 构造函数
func NewMyError(code int, msg, id string) error {
    return MyError{Code: code, Message: msg, ID: id}
}

一般为了维护,不会临时创建 error,而是将常用的 error 当作全局变量使用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var (
  ErrInvalid = fs.ErrInvalid // "invalid argument"

  ErrPermission = fs.ErrPermission // "permission denied"
  ErrExist      = fs.ErrExist      // "file already exists"
  ErrNotExist   = fs.ErrNotExist   // "file does not exist"
  ErrClosed     = fs.ErrClosed     // "file already closed"

  ErrNoDeadline       = errNoDeadline()       // "file type does not support deadline"
  ErrDeadlineExceeded = errDeadlineExceeded() // "i/o timeout"
)

传递

错误在传递的过程中可能会层层包装,当上层调用者想要判断错误的类型来做出不同的处理时,可能会无法判别错误的类别或误判,而链式错误正是为了解决这种情况而出现的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
type wrapError struct {
   msg string
   err error
}

func (e *wrapError) Error() string {
   return e.msg
}

// wrappError实现了error接口,多了一个方法Unwrap,用于返回其内部对于原 error 的引用
func (e *wrapError) Unwrap() error {
   return e.err
}

处理

  • errors.Is():判断错误用errors.Is(),不应该使用==
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import "fmt"
import "errors"

var originalErr = errors.New("this is an error")

func wrap1() error { // 包裹原始错误
   return fmt.Errorf("wrapp error %w", wrap2())
}

func wrap2() error { // 原始错误
   return originalErr
}

func main() {
  err := wrap1()
  /**
  func Is(err, target error) bool 作用是判断错误链中是否包含指定的错误
  */
   if errors.Is(err, originalErr) { // 如果使用if err == originalErr 将会是false
      fmt.Println("original")
   }
}
  • errors.As():错误链中寻找第一个类型匹配的错误,并将值赋值给传入的err
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

// 自定义error
type TimeError struct {
   Msg  string
   Time time.Time //记录发生错误的时间
}

func (m TimeError) Error() string {
   return m.Msg
}

func NewMyError(msg string) error {
   return &TimeError{
      Msg:  msg,
      Time: time.Now(),
   }
}

func wrap1() error { // 包裹原始错误
   return fmt.Errorf("wrapp error %w", wrap2())
}

func wrap2() error { // 原始错误
   return NewMyError("original error")
}

func main() {
   var myerr *TimeError
   err := wrap1()
   // 检查错误链中是否有*TimeError类型的错误
   if errors.As(err, &myerr) {
      fmt.Println("original", myerr.Time)
   }
}

panic

很严重的问题,程序应该在处理完问题后立即退出。

当程序中存在多个协程时,只要任一协程发生panic,如果不将其捕获的话,整个程序都会崩溃。

1
2
3

// 显式创建panic
func panic(v any)

recover() 是 Go 语言中用于捕获并处理 panic 的内置函数,它能让程序从“崩溃边缘”恢复过来,避免整个 goroutine 退出。

recover() 只能在 defer 函数中调用,用于捕获当前 goroutine 中的 panic,阻止程序崩溃。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
func main() {
    defer func() {
      /**
      如果没有 panic → 返回 nil
      如果有 panic → 返回 panic() 传入的值(类型为 any)
      */
        if r := recover(); r != nil {
            fmt.Println("捕获到 panic:", r)
        }
    }()

    panic("出错了!") // 触发 panic
    fmt.Println("这行不会执行")
}

fatal

非常致命的问题,程序应该立即退出,不会执行任何善后工作,通常情况下是调用os包下的Exit函数退出程序。

fatal级别的问题一般很少会显式的去触发,大多数情况都是被动触发。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计