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!") - 方式二:使用
fmt的Errorf创建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函数退出程序。