Go 接口与异常

26 阅读4分钟

空接口

不包含任何方法. 故所有的结构体都默认实现了空接口

空接口可以存储任何的类型

interface{} == any

接口是引用类型~

接口断言

用于检查接口类型是否符合预期~

实现方式

  • i.(T) 判断i接口是否是T类型
  • switch i.(type) { } 用于判断(多种类型 -> case)
package main

import "fmt"

// 断言 t := i.(T) t: t就是i接口 T就是类型 判断i是否为T类型
// 语法 t,ok := i.(T) ok 为返回值, 如果断言成功, ok为true, 否则为false
// 断言可以与switch 一起使用~ (可用于匹配多种的类型)
type Inter interface{}

func test_assert(i interface{}) {
	switch i.(type) { // 若匹配了多个case, 默认选匹配的第一个case执行
	case string: // 注意case的顺序
		fmt.Println("这是string类型")
	case int:
		fmt.Println("这是int类型")
	case nil:
		fmt.Println("变量为nil类型")
	case Inter: // 空接口可以承接所有的类型, 所以位置放在后面
		fmt.Println("this is接口类型")
	default:
		fmt.Println("未知类型")
	}
}
func main() {
	assertInt(199)
	test_assert("hello") // 这是string类型
	var i interface{}    // 定义一个空接口 默认为nil
	test_assert(i)       // 打印结果: 变量为nil类型
	test_assert(12)      // 这是int类型
}

func assertInt(i any) { // any == interface{}
	r, ok := i.(int)
	if ok {
		fmt.Println("是int类型~")
		fmt.Println(r)
	} else {
		fmt.Println("不是int类型")
	}
}

type(别名)

  • type xxx TT 自定义类型
  • type xxx = TT 类型取别名
package main

import "fmt"

// var -> 声明变量, type 类型(结构体、接口)
// type的别名用法
type BigInt int // 这里定义了一个新的类型 BigInt 是从int转换过来, 和int一样, 但不能通过int发生操作, 类型不同~
func main() {
	var a BigInt = 20
	var b int = 10        // a b 两个类型不一样
	fmt.Printf("%T\n", a) // main.BigInt
	fmt.Printf("%T\n", b) // int
	// 可以强转数据类型 T(v) 即可以进行数据操作
	fmt.Println(int(a) + b) // 30
	// 取别名
	type SmallInt = int // 给int取另一个名字 SmallInt~ SmallInt和int类型是一样的
	var c SmallInt = 30
	fmt.Printf("%T\n", c) // int
	fmt.Println(b + c)    // 40
}

异常和错误

错误

指的是程序中预期会发生的结果, 预期之中

是我们在写程序中要思考的问题, 要去做合适处理 应该显示的检查错误

error机制: error接口, 接收错误, 然后判断进一步处理

package main

import (
	"fmt"
	"os"
)

// Go中错误也是一种类型, 错误用内置的error类型表示, 与其他类型类似, 如 int, float64...
func main() {
	// 所有用鼠标or键盘能执行的事件, 都可以用程序执行
	file, err := os.Open("aaa.txt") // 若不去接收err (用_)的话会报程序恐慌panic
	if err != nil {                 // err 有很多种
		fmt.Println(err)
		return
	}
	fmt.Println(file.Name())
}

自己定义一个错误

package main

import (
	"errors"
	"fmt"
)

// 定义错误
// Go中错误也是一种类型, 错误用内置的error类型表示, 与其他类型类似, 如 int, float64...
func main() {
	err := setAge(-100)
	if err != nil {
		// 处理错误
		fmt.Println(err)
	}
	fmt.Printf("%T\n", err) // 类型 *errors.errorString
	err2 := setAge(100)
	if err2 != nil {
		fmt.Println(err2)
	}
	fmt.Printf("%T\n", err2)
	// 方法2 两种方式都会返回 error对象
	err3 := fmt.Errorf("官方提供的输出错误信息:%d\n", 500)
	if err3 != nil {
		fmt.Println(err3)
	}
}

func setAge(age int) error {
	if age < 0 {
		// 抛出一个错误
		age = 18
		return errors.New("不合法输入") // 方法1
	}
	fmt.Println("年龄设置完毕,age=", age)
	return nil
}

error 类型

上面的两种方法返回的错误信息承载有限, 下面是仿照源码的错误进行写自己的Error处理~

package main

import (
	"fmt"
)

type MyError struct {
	code     int
	ErrorMsg string
}

// 取指针 是为了方便操作的是同一个对象  
// 处理error 逻辑
func (myerror *MyError) Error() string { // 实现 error接口 <- Error() 这个单词不能写错~
	return fmt.Sprintf("错误信息: %s, 错误代码:%d\n", myerror.ErrorMsg, myerror.code) // 返回 string
}

func (myerror *MyError) printMsg() bool {
	fmt.Println("这是错误信息")
	return true
}

// 测试错误
func test(i int) (int, error) {
	if i != 0 {
		return i, &MyError{ErrorMsg: "输入不符合预期", code: 500}
	}
	// 正常输出 return i, nil
	return i, nil
}

func main() {
	i, err := test(1)
	if err != nil {
		fmt.Println(err)
		msg, ok := err.(*MyError) 
		if ok { // true/false
			if msg.printMsg() {
				// 处理错误的子错误  ...
				fmt.Println(msg)
			}
			fmt.Println(msg.code)
			fmt.Println(msg.ErrorMsg)
		}
	}
	fmt.Println(i)
}

异常

指的是不该出现问题的地方出现了问题, 意料之外

手动抛出panic: 能够预知危险的情况下, 可以主动抛出 panic("xxx");

处理异常 panic 抛出异常, recover 接收这个异常并处理

recover 结合 panic 处理异常~

package main

import "fmt"

func main() {
	defer fmt.Println("main---1")
	defer fmt.Println("main---2")
	fmt.Println("main---3") // 如果在函数内部处理了panic, 那么程序会继续执行
	testPanic(1)
}

// 出现了 panic 后, 若有panic语句, 则会去执行(panic前)所有的defer语句(包括函数外的) 而panic后面的语句会终止执行
// defer 会按照其逻辑进行(压栈 从上到下执行)
// func recover() any 返回的是panic传递的(参数)值
// func panic(v any) 传入任何类型的值
func testPanic(num int) {
	defer func() {
		msg := recover()
		if msg != nil {
			fmt.Println("recover执行了,msg信息为: ", msg)
			// 处理逻辑
			fmt.Println("程序恢复了~")
		}
	}()
	defer fmt.Println("testPanic---1")
	defer fmt.Println("testPanic---2")
	fmt.Println("testPanic---3")
	if num == 1 {
		panic("手动抛出异常---panic")
	}
	defer fmt.Println("testPanic---3")
	defer fmt.Println("testPanic---4")
}

执行大概顺序:

  1. panic触发

  2. 触发当前函数中panic函数上面的defer语句 (倒序执行)

  3. 直至遇到recover 处理panic, 函数结束

  4. 回到main函数