十三、Go语法进阶(类型和错误)

33 阅读6分钟

Golang中文学习文档地址

1、类型

1.1 静态强类型

  • Go的类型是静态强类型,声明的变量类型在编译期就已经确定,而且在后续的运行过程中,不会被修改。

  • Go的类型自动推断:

    Go的类型自动推断是在编译期就已经确定下来了,所以实际的运行过程中,变量的类型也不会被修改。

1.2 类型声明和类型别名

  • 类型声明

    type MyInt int
    
  • 类型别名

    type MyInt = int
    
  • 类型声明和类型别名的区别

    类型声明是声明一个新的类型,只是这个类型的底层类型是int,和int是属于不同的类型。

    类型别名是给类型取个别名,和int是属于同一个类型。

1.3 类型转换

  • Go的类型转换是没有隐性的,全需要显性转换。
  • 类型转换需要可被目标类型代表的,例如:int可以被int16所代表,但int不可以被string所代表。
import "fmt"

func main() {
	var v int = 8
	v1 := int16(v)
    //输出——转成int16:8
	fmt.Println("转成int16:", v1)

	v2 := string(v)
    //输出——转成string:
	fmt.Println("转成string:", v2)
}

1.4 类型断言

func main() {
	v1 := assertion(1)
	fmt.Println(v1)
	v2 := assertion(1.1)
	fmt.Println(v2)
}
//类型断言
func assertion(v any) any {
    //断言v值是否是int
	val, ok := v.(int)
	if ok {
		return val
	}
	panic("no int")
}

1.5 类型判断

func main() {
	TypeJudgment(1)
	TypeJudgment(1.2)
	TypeJudgment("123")
}
func TypeJudgment(v any) {
	switch v.(type) {
	case int:
		fmt.Println("this is int type!")
	case float32:
		fmt.Println("this is float32 type!")
	case float64:
		fmt.Println("this is float64 type!")
	case string:
		fmt.Println("this is string type!")
	}
}

2、错误

  • Go中的三种错误

    • error

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

    • panic

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

    • fatal

      非常致命的问题,程序应该立即退出。

  • Go的异常是通过error体现的,在Go设计的过程中,对错误的理念是:希望能够将错误可控。所以在设计过程中,Go中都把error作为返回值返回(Go中可以多个返回值)。

    func main() {
        value, err := ReadFile(1)
        // 需要处理错误
        if err != nil {
            fmt.Println(value)
        }
    }
    func ReadFile(val int) (bool, error) {
            if val == 1 {
                    return false, errors.New("This is an error")
            }
            return true, nil
    }
    
  • Go中错误的优缺点

    • 优点

      • 心智负担小:有错误就处理,不处理就返回。
      • 可读性:因为处理的方式非常简单,大部分情况下都很容易读懂代码。
      • 易于调试:每一个错误都是通过函数调用的返回值产生的,可以一层一层往回找到,很少会出现。
      • 突然冒出一个错误却不知道是从哪里来的这种情况。
    • 缺点

      • 错误中没有堆栈信息(需要第三方包解决或者自己封装)
      • 丑陋,重复代码多
      • 自定义错误是通过var来声明的,它是一个变量而不是常量。
      • 变量遮蔽问题。

2.1 error

  • error 的严重级别不足以停止整个程序的运行,和Java中Exception的作用是一样的。
  • error是一个Go预定好的接口。
    type error interface {
        Error() string
    }
    

2.1.1 error的创建

  • 方式一:使用errors创建
    errors.New("this is an error created by errors!")
    
  • 方式二:使用fmtErrorf创建
    fmt.Errorf("this is an error created by %s!", "fmt.Errorf")
    

2.1.2 自定义错误

  • 通过实现error接口,实现自定义的error。
    type error interface {
        Error() string
    }
    

2.1.3 错误的传递

  • 调用者调用的函数返回了一个错误,但是调用者本身不负责处理错误,于是也将错误作为返回值返回,抛给上一层调用者,这个过程叫传递,错误在传递的过程中可能会层层包装,当上层调用者想要判断错误的类型来做出不同的处理时,可能会无法判别错误的类别或者误判,而链式错误正是为了解决这种情况而出现的。
  • 链式错误
    • 原理: 将上层调用者的错误记录在当前的错误,最终形成一个调错误链,可通过记录的错误找到每一层的错误信息和调用关系。
    • 代码
      • wrapError结构是在fmt下的errors,是私有结构体,所以使用链式错误只能使用fmt.Errorf创建错误。
        • fmt.Errorf源码
          type wrapError struct {
             msg string
             err error
          }
          
          func (e *wrapError) Error() string {
             return e.msg
          }
          
          func (e *wrapError) Unwrap() error {
             return e.err
          }
          
          func Errorf(format string, a ...any) error {
                  p := newPrinter()
                  p.wrapErrs = true
                  p.doPrintf(format, a)
                  s := string(p.buf)
                  var err error
                  switch len(p.wrappedErrs) {
                  case 0:
                          err = errors.New(s)
                  case 1:
                  //当有传入错误时,创建的是wrapError(链式错误)
                          w := &wrapError{msg: s}
                          w.err, _ = a[p.wrappedErrs[0]].(error)
                          err = w
                  default:
                          if p.reordered {
                                  slices.Sort(p.wrappedErrs)
                          }
                          var errs []error
                          for i, argNum := range p.wrappedErrs {
                                  if i > 0 && p.wrappedErrs[i-1] == argNum {
                                          continue
                                  }
                                  if e, ok := a[argNum].(error); ok {
                                          errs = append(errs, e)
                                  }
                          }
                          err = &wrapErrors{s, errs}
                  }
                  p.free()
                  return err
          }
          

2.1.4 错误的处理

  • 使用errors包下函数处理和检查错误
    • func Unwrap(err error) error
      • 作用 解包错误链

      • 源码

        func Unwrap(err error) error {
        //判断是否实现了Unwrap()。
            u, ok := err.(interface {
                    Unwrap() error
            })
            if !ok {
            //未实现,说明是一个普通错误。
                    return nil
            }
        //有实现,说明是一个链式错误,调用Unwrap()返回上层调用者的错误。
            return u.Unwrap()
        }
        
      • 使用

        func main() {
           err := errors.New("这是一个原始错误")
           wrapErr := fmt.Errorf("错误,%w", err)
           newErr := errors.Unwrap(wrapErr)
           fmt.Println(newErr)
        }
        
    • func Is(err, target error) bool
      • 作用 判断整个链式错误err中,是否包含target这个错误。
      • 源码
        func Is(err, target error) bool {
             if err == nil || target == nil {
                     return err == target
             }
        
             isComparable := reflectlite.TypeOf(target).Comparable()
             return is(err, target, isComparable)
         }
         func is(err, target error, targetComparable bool) bool {
                 for {
                         if targetComparable && err == target {
                                 return true
                         }
                         if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
                                 return true
                         }
                         switch x := err.(type) {
                         case interface{ Unwrap() error }:
                                 err = x.Unwrap()
                                 if err == nil {
                                         return false
                                 }
                         case interface{ Unwrap() []error }:
                                 for _, err := range x.Unwrap() {
                                         if is(err, target, targetComparable) {
                                                 return true
                                         }
                                 }
                                 return false
                         default:
                                 return false
                         }
                 }
         }
        
      • 使用
        func main() {
           err := errors.New("这是一个原始错误")
           wrapErr := fmt.Errorf("错误,%w", err)
           is := errors.Is(newErr, err)
           fmt.Println(is)
        }
        
    • func As(err error, target any) bool
      • 作用

        判断整个错误链err中,是否有error类型与target相同,如果有,返回true,并把错误链中第一个和target类型相同的error,赋给target

      • 源码

        func As(err error, target any) bool {
              if err == nil {
                      return false
              }
              if target == nil {
                      panic("errors: target cannot be nil")
              }
              val := reflectlite.ValueOf(target)
              typ := val.Type()
              if typ.Kind() != reflectlite.Ptr || val.IsNil() {
                      panic("errors: target must be a non-nil pointer")
              }
              targetType := typ.Elem()
              if targetType.Kind() != reflectlite.Interface && !targetType.Implements(errorType) {
                      panic("errors: *target must be interface or implement error")
              }
          return as(err, target, val, targetType)
        }
        
      • 使用

        func main() {
          err1 := errors.New("这是一个原始错误")
          wrapErr := fmt.Errorf("错误,%w", err1)
          fmt.Println(wrapErr, "==", err1)
        
          is1 := errors.As(wrapErr, &err1)
        
          fmt.Println(is1, "==", wrapErr, "==", err1)
        }
        //输出:
        //错误,这是一个原始错误 == 这是一个原始错误
        //true == 错误,这是一个原始错误 == 错误,这是一个原始错误
        
  • 官方提供的另一个error增强包:github.com/pkg/errors
    • 原因

      官方原本的error中,没有错误的堆栈信息,使用该增强包,也可以输出错误堆栈信息。

    • 使用

      import (
          "fmt"
          "github.com/pkg/errors"
      )
      
      func Do() error {
          return errors.New("error")
      }
      
      func main() {
          err := Do()
          //%+v:在打印结构体时,除了打印值外,还会打印字段名称。
          fmt.Printf("%+v", err)
      }
      /*  输出
      error
      main.Do
              C:/work/dev/workspace/go/start_demo/main.go:9
      main.main
              C:/work/dev/workspace/go/start_demo/main.go:13
      runtime.main
              C:/work/dev/Go/src/runtime/proc.go:283
      runtime.goexit
              C:/work/dev/Go/src/runtime/asm_amd64.s:1700
      */
      

2.2 panic

  • panic,非常严重的异常问题,导致程序无法执行下去,必须立刻处理,否则程序会停止,并输出堆栈信息。

2.2.1 panic的创建

  • 使用内置函数:panic

2.2.2 panic的善后

  • panic退出之前,会执行defer语句,做最后的处理。

2.2.3 panic的恢复

  • 当发生panic时,使用内置函数recover()可以及时的处理并且保证程序继续运行,必须要在defer语句中运行。

2.3 fatal

  • fatal是一种极其严重的问题,当发生fatal时,程序需要立刻停止运行,不会执行任何善后工作,通常情况下是调用os包下的Exit函数退出程序。