目录
之前有师傅问这个系列好像跟红队没啥关系,前几期确实没啥关系,由于这都是进行红队工具开发的前置知识点,对于我个人强迫症而言只是想让这个系列更加美满而已,所以前置知识也加进去了,有GO知识的大佬可以等下一期哈!感谢支持。
错误控制
使用
- 1. errors.New("错误信息") //这个属于error类型
- 例子://你会看到error返回类型,return 0, errors.New("除数不能为零")
- func divide(a, b int) (int, error) {
- if b == 0 {
- return 0, errors.New("除数不能为零") }
- return a / b, nil
- }
- 2.fmt.Errorf:错误包装,后面会详细讲。这个函数允许你使用格式化字符串创建错误,类似于 fmt.Sprintf。
- 这个没啥好说的,只是说New的时候只能是字符串,而你用这个就能够格式化字符串,将变量放在里面格式化输出在错误中。
- err := fmt.Errorf("invalid argument: %v", value)
- 3.errors.Is:判断错误链中是否包含该错误
- if errors.Is(err, myError) {
- // err 是 myError自定义
- }
复制代码 自定义错误类型
自定义错误类型:通过实现 error 接口来自定义错误类型。
示例代码:- package main
- import "fmt"
- type MyError struct {
- When string
- What string
- }
- func (e *MyError) Error() string {
- return fmt.Sprintf("在 %s 发生 %s ", e.When, e.What)
- }
- func run() error {
- return &MyError{
- When: "运行时",
- What: "错误1",
- }
- }
- func main() {
- err := run()
- if err != nil {
- fmt.Println(err)
- }
- }
复制代码 留意理解具体执行过程:
- run() 返回一个 error 接口值,其动态类型为 *MyError,动态值为 &MyError{}。
- 当执行时:
- err.Error() 被隐式调用。
- 动态类型 *MyError 的 Error() 方法被执行,返回一个字符串。
错误包装
其实很好理解,就是使用fmt.Errorf("获取数据失败: %w", err),这相称于用获取数据失败:这个字符串包了一下err
所以fmt.Errorf("获取数据失败: %w", err)解包的时候就是等于err,由于背面的Is(is)就是为啥能判定错误链里是否包罗的。- package main
- import (
- "errors"
- "fmt"
- )
- // 定义一个自定义错误
- var ErrNotFound = errors.New("资源未找到")
- func fetchData(id int) error {
- if id == 0 {
- return ErrNotFound // 返回原始错误
- }
- return nil
- }
- func main() {
- // 调用 fetchData 并包装错误
- err := fetchData(0)
- if err != nil {
- // 使用 fmt.Errorf 包装原始错误,添加上下文
- wrappedErr := fmt.Errorf("获取数据失败: %w", err)
- fmt.Println(wrappedErr) // 打印包装后的错误信息
- // 检查包装的错误是否包含特定错误
- if errors.Is(wrappedErr, ErrNotFound) {
- fmt.Println("错误类型: 资源未找到")
- }
- // 解包原始错误
- unwrappedErr := errors.Unwrap(wrappedErr)
- fmt.Printf("解包后的错误: %v\n", unwrappedErr)
- }
- }
复制代码 输出如下:(看下面的输出就知道什么情况了)- 获取数据失败: 资源未找到
- 错误类型: 资源未找到
- 解包后的错误: 资源未找到
复制代码 errors.Is 和 errors.As
相识了错误包装之后就知道这两的区别了,Is就是判定错误链中是否包罗你这个错误,只要包罗一个即可。
As就是只判定当前的,不管你是否包罗的。- package main
- import (
- "errors"
- "fmt"
- )
- var ErrDivideByZero = errors.New("除数不能为零")
- func divide(a, b int) (int, error) {
- if b == 0 {
- return 0, ErrDivideByZero
- }
- return a / b, nil
- }
- func main() {
- _, err := divide(4, 0)
- if errors.Is(err, ErrDivideByZero) { //可以是ErrDivideByZero错误的错误链,因为有可能是进行了错误包装
- fmt.Println("捕获到除以零的错误")
- } else {
- fmt.Println("其他错误:", err)
- }
-
- if errors.As(err, &ErrDivideByZero) { //一定要是ErrDivideByZero错误,同时要是一个指针,源码的painc写着target must be a non-nil pointer
- fmt.Println("捕获到除以零的错误")
- } else {
- fmt.Println("其他错误:", err)
- }
- }
复制代码 panic捕获、recover 、defer
解释:
- panic 能够改变步伐的控制流,调用 panic 后会立刻停止执行当前函数的剩余代码,并在当前 Goroutine 中递归执行调用方的 defer;
- recover 可以中止 panic 造成的步伐崩溃。它是一个只能在 defer 中发挥作用的函数,在其他作用域中调用不会发挥作用;
留意:recover 只能在 defer 函数中调⽤,并且只有在 panic 发⽣时才会返回⾮ nil 值。- //恐慌捕获 panic recover defer
- func start_panic() {
- defer func() {
- //使用defer来等待后面panic执行一个panic后再进行捕获
- if r := recover(); r != nil {
- fmt.Println("捕获到了:", r)
- }
- }()
- panic("一个panic")
- }
-
- func main() {
- fmt.Println("开始捕获panic")
- start_panic()
- fmt.Println("结束捕获panic")
- }
复制代码 错误控制练习
固定打开一个1.txt文件,然后使用自定义错误类型,同时进行panic捕获,输出预期:错误,非预期:错误- type FileNotFoundError struct {
- filename string
- }
- func (f FileNotFoundError) Error() string {
- return fmt.Sprintf("文件 %s 不存在。", f.filename)
- }
- func readfile(file_name string) (string, error) {
- //panic一下
- defer func() {
- if r := recover(); r != nil {
- fmt.Println("panic is ", r)
- }
- }()
- if file_name != "1.txt" {
- return "", FileNotFoundError{filename: file_name}
- }
- bytes, err := os.ReadFile("1.txt")
- if err != nil {
- return "", fmt.Errorf("文件 %s 打开出错", file_name)
- } else {
- return string(bytes), nil
- }
- }
- func main() {
- file, err := readfile("1.txt")
- if err != nil {
- if errors.As(err, FileNotFoundError{}) {
- fmt.Println("预期错误:", err)
- } else {
- fmt.Println("非预期错误:", err)
- }
- return
- }
- fmt.Println("文件内容为:", file)
- }
复制代码 接口
接口在go语言中也有点抽象,对于接口的实现其实很简单,只是在用的时候比较抽象,结构体实现接口反而是最容易接受的,像基本类型还有切片这两种接口的使用就比较抽象。
结构体实现接口
(在结构体实现结构以及方法的调用基本没啥题目,很正常的操作)- package main
- import "fmt"
- type User interface {
- getName() string
- getAge() int
- }
- type Person struct {
- name string
- age int
- }
- func (p Person) getName() string {
- return p.name
- }
- func (p Person) getAge() int {
- return p.age
- }
- func main() {
- p := Person{
- name: "zhangsan",
- age: 18,
- }
- fmt.Println(p.getName(), p.getAge())
- }
复制代码 基本类型实现接口
这里我分两种情况,string和int实现接口,目标是相识在实现接口后怎么使用这个方法。- type Stringer interface {
- //接口的这两个方法写了之后就要实现。所以下面就实现了
- String() string
- Ascii() string
- }
- type MyString string //string类型
- type MyInt int //int类型
- func (s MyString) String() string {
- return string(s) //其实可以不转string也能直接返回,因为MyString本身就是string类型,只是我换了个别名而已
- }
- func (t MyInt) Ascii() string {
- return string(t) //一定要转,因为本身是int类型
- }
- func (t MyInt) String() string {
- return fmt.Sprintf("%d", t) //一定要转,因为本身是int类型
- }
- func main() {
- //结构体实现接口效果
- //start_struct_interface()
- var s Stringer = MyString("传递参数string")
- var i Stringer = MyInt(97)
- fmt.Println(s.String())
- fmt.Println(i.Ascii()) //将数字转为ascii
- fmt.Println(i.String()) //将数字作为字符串输出
- }
复制代码 细节:
实际操作下来其实也没有说很难理解,只是可能类型转换谁人地方卡了一下导致难以理解而已- 重点是看懂这里的代码:
- var s Stringer = MyString("传递参数string")
- var i Stringer = MyInt(97)
- 接口类型接收实现了接口方法的类型,然后就能够调用接口方法了,就这么理解就行了。
- 在MyString和MyInt中都是强制类型转换,将string字符和int数字转为对应的别名,然后给到变量后就能直接使用实现了接口的方法,因为已经转为了那两个基本类型了。
- 在强调一遍:本身是没有我们定义的这种类型的,所以要强制转换,然后接口其实就能随便写了,管你要不要用他这个值。
- 条件:
- 属于这个类型(强制类型转换)
- 实现了接口方法(正常实现接口方法)
- 调用就直接调用即可(正常)
复制代码 切片实现接口
其实到了切片实现接口就很容易理解了
我发现其实是你这个类型实现了这个接口后,就可以用了
我发现要用这个方法只是仅仅的需要你是这个类型,而不是说在于什么逼迫类型转换,而是你要用这个接口方法是由于谁人类型实现了这个接口啊,所以你要逼迫类型转换,所以要实例化这个类型啊,确实有点悟道了,也有点不明白我之前到底为啥会卡住。
切片实现接口也很简单,到这里其实已经不分什么结构体、基本类型、切片的了,本质就是你实例化一个实现了接口的类型,然后你的某个类型实现了接口类型的方法,那么你就直接实例化给到接口类型就拿这个类型去调用方法就行了。 (有点抽象,照旧直接看代码吧)- type I_slice interface { //接口类型
- sum() int //返回切片的和
- }
- type MySlice []int //切片类型,换一个intslice别名而已,方便自定义,且实现接口方法
- func (ms I_slice) sum() int { //就是自己定义的类型实现了接口类型而已,很好理解
- s := 0
- for _, v := range ms {
- s += v
- }
- return s
- }
- func main() {
- var s I_slice = MySlice{1, 2, 3, 4, 5} //我们自己自定义的切片类型然后赋值给接口类型
-
- //var s MySlice = MySlice{1, 2, 3, 4, 5}
- //var s = MySlice{1, 2, 3, 4, 5} //这两其实也可以,就是我们自定义的类型本来就是有实现这个方法的,只不过你没有赋值给接口类型,所以不算实现了接口的方法而已,但是你本身就拥有的方法当然可以用啦
- fmt.Println(s.sum())
- }
复制代码 接口练习
项⽬描述- 创建⼀个形状计算器,它可以计算不同形状的⾯积。需要定义⼀个 Shape 接⼝,
- 并为不同的形状(如圆形和矩形)实现这个接⼝。
- 然后编写⼀个函数来计算并打印这些形状的⾯积。
- 步骤
- 定义⼀个 Shape 接⼝,包含⼀个 area ⽅法。
- 创建⼀个 Circle 结构体,并实现 Shape 接⼝。
- 创建⼀个 Rectangle 结构体,并实现 Shape 接⼝。
- 编写⼀个函数 printArea,接受⼀个 Shape 类型的参数,并打印它的⾯积。
- 在 main 函数中创建⼀些 Circle 和 Rectangle 实例,并调⽤ printArea 函数来打印它们的⾯积。
复制代码 示例代码- type Shape interface {
- Area() float64
- }
- type Circle struct {
- radius float64
- }
- type Rectangle struct {
- width float64
- height float64
- }
- func (c Circle) Area() float64 {
- return (c.radius * c.radius * math.Pi)
- }
- func (r Rectangle) Area() float64 {
- return (r.width * r.height)
- }
- func printArea(s Shape) {
- /*
- 这里实际上就相当于强制类型转换了,
- 因为两个结构体实现了Area方法,
- 那么就强制类型转换为Shape后能够在函数中正常使用该方法
- */
- fmt.Printf("%.2f\n", s.Area())
- }
- func main() {d
- c := Circle{radius: 2}
- r := Rectangle{width: 2, height: 4}
- printArea(c)
- printArea(r)
- }
复制代码 Embed嵌入文件
只支持嵌入为string, byte切片和embed.FS三种类型。相对来说比较难理解的是embed.FS。
使⽤//go:embed后,下⽅必须是全局变量。
使用embed可以嵌⼊⽂件夹下的⽂件,使⽤通配符*,比如static/*
示例代码:
- package main
- import (
- "embed"
- _ "embed"
- "fmt"
- )
- //go:embed a.txt
- var a string
- //go:embed static/1.txt
- var s []byte
- //go:embed static/*
- var f1 embed.FS
- //go:embed static/* static/2.txt
- var f2 embed.FS
- //go:embed static/1.txt
- //go:embed static/2.txt
- //go:embed static/3.txt
- var f3 embed.FS
- func main() {
- fmt.Println("============string接收================")
- fmt.Println(a)
- fmt.Println("============byte接收================")
- fmt.Printf("%q\n", s)
- fmt.Println(string(s))
- fmt.Println("============FS单个文件================")
- data, _ := f1.ReadFile("static/1.txt")
- fmt.Println(string(data))
- fmt.Println("============FS目录,当前目录等等多个文件,go:embed空格隔开================")
- data, _ = f2.ReadFile("static/2.txt")
- fmt.Println(string(data))
- fmt.Println("============FS多个文件,go:embed可以不用空格隔开================")
- data, _ = f3.ReadFile("static/3.txt")
- fmt.Println(string(data))
- }
复制代码 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |