接口
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级别的问题一般很少会显式的去触发,大多数情况都是被动触发。