ToB企服应用市场:ToB评测及商务社交产业平台

标题: Go红队开发—语法补充 [打印本页]

作者: 守听    时间: 昨天 19:44
标题: Go红队开发—语法补充
目录

之前有师傅问这个系列好像跟红队没啥关系,前几期确实没啥关系,由于这都是进行红队工具开发的前置知识点,对于我个人强迫症而言只是想让这个系列更加美满而已,所以前置知识也加进去了,有GO知识的大佬可以等下一期哈!感谢支持。
错误控制

使用
  1. 1. errors.New("错误信息")   //这个属于error类型
  2. 例子://你会看到error返回类型,return 0, errors.New("除数不能为零")
  3. func divide(a, b int) (int, error) {
  4. if b == 0 {
  5. return 0, errors.New("除数不能为零") }
  6. return a / b, nil
  7. }
  8. 2.fmt.Errorf:错误包装,后面会详细讲。这个函数允许你使用格式化字符串创建错误,类似于 fmt.Sprintf。
  9. 这个没啥好说的,只是说New的时候只能是字符串,而你用这个就能够格式化字符串,将变量放在里面格式化输出在错误中。
  10. err := fmt.Errorf("invalid argument: %v", value)
  11. 3.errors.Is:判断错误链中是否包含该错误
  12. if errors.Is(err, myError) {
  13.     // err 是 myError自定义
  14. }
复制代码
自定义错误类型

自定义错误类型:通过实现 error 接口来自定义错误类型。
示例代码:
  1. package main
  2. import "fmt"
  3. type MyError struct {
  4.         When string
  5.         What string
  6. }
  7. func (e *MyError) Error() string {
  8.         return fmt.Sprintf("在 %s 发生 %s ", e.When, e.What)
  9. }
  10. func run() error {
  11.         return &MyError{
  12.                 When: "运行时",
  13.                 What: "错误1",
  14.         }
  15. }
  16. func main() {
  17.         err := run()
  18.         if err != nil {
  19.                 fmt.Println(err)
  20.         }
  21. }
复制代码
留意理解具体执行过程:
错误包装

其实很好理解,就是使用fmt.Errorf("获取数据失败: %w", err),这相称于用获取数据失败:这个字符串包了一下err
所以fmt.Errorf("获取数据失败: %w", err)解包的时候就是等于err,由于背面的Is(is)就是为啥能判定错误链里是否包罗的。
  1. package main
  2. import (
  3.         "errors"
  4.         "fmt"
  5. )
  6. // 定义一个自定义错误
  7. var ErrNotFound = errors.New("资源未找到")
  8. func fetchData(id int) error {
  9.         if id == 0 {
  10.                 return ErrNotFound // 返回原始错误
  11.         }
  12.         return nil
  13. }
  14. func main() {
  15.         // 调用 fetchData 并包装错误
  16.         err := fetchData(0)
  17.         if err != nil {
  18.                 // 使用 fmt.Errorf 包装原始错误,添加上下文
  19.                 wrappedErr := fmt.Errorf("获取数据失败: %w", err)
  20.                 fmt.Println(wrappedErr) // 打印包装后的错误信息
  21.                 // 检查包装的错误是否包含特定错误
  22.                 if errors.Is(wrappedErr, ErrNotFound) {
  23.                         fmt.Println("错误类型: 资源未找到")
  24.                 }
  25.                 // 解包原始错误
  26.                 unwrappedErr := errors.Unwrap(wrappedErr)
  27.                 fmt.Printf("解包后的错误: %v\n", unwrappedErr)
  28.         }
  29. }
复制代码
输出如下:(看下面的输出就知道什么情况了)
  1. 获取数据失败: 资源未找到
  2. 错误类型: 资源未找到
  3. 解包后的错误: 资源未找到
复制代码
errors.Is 和 errors.As

相识了错误包装之后就知道这两的区别了,Is就是判定错误链中是否包罗你这个错误,只要包罗一个即可。
As就是只判定当前的,不管你是否包罗的。
  1. package main
  2. import (
  3.         "errors"
  4.         "fmt"
  5. )
  6. var ErrDivideByZero = errors.New("除数不能为零")
  7. func divide(a, b int) (int, error) {
  8.         if b == 0 {
  9.                 return 0, ErrDivideByZero
  10.         }
  11.         return a / b, nil
  12. }
  13. func main() {
  14.         _, err := divide(4, 0)
  15.         if errors.Is(err, ErrDivideByZero) { //可以是ErrDivideByZero错误的错误链,因为有可能是进行了错误包装
  16.                 fmt.Println("捕获到除以零的错误")
  17.         } else {
  18.                 fmt.Println("其他错误:", err)
  19.         }
  20.    
  21.     if errors.As(err, &ErrDivideByZero) {  //一定要是ErrDivideByZero错误,同时要是一个指针,源码的painc写着target must be a non-nil pointer
  22.                 fmt.Println("捕获到除以零的错误")
  23.         } else {
  24.                 fmt.Println("其他错误:", err)
  25.         }
  26. }
复制代码
panic捕获、recover 、defer

解释:
留意:recover 只能在 defer 函数中调⽤,并且只有在 panic 发⽣时才会返回⾮ nil 值。
  1. //恐慌捕获 panic recover defer
  2. func start_panic() {
  3.         defer func() {
  4.                 //使用defer来等待后面panic执行一个panic后再进行捕获
  5.                 if r := recover(); r != nil {
  6.                         fmt.Println("捕获到了:", r)
  7.                 }
  8.         }()
  9.         panic("一个panic")
  10. }
  11.        
  12. func main() {
  13.         fmt.Println("开始捕获panic")
  14.         start_panic()
  15.         fmt.Println("结束捕获panic")
  16. }
复制代码
错误控制练习

固定打开一个1.txt文件,然后使用自定义错误类型,同时进行panic捕获,输出预期:错误,非预期:错误
  1. type FileNotFoundError struct {
  2.         filename string
  3. }
  4. func (f FileNotFoundError) Error() string {
  5.         return fmt.Sprintf("文件 %s 不存在。", f.filename)
  6. }
  7. func readfile(file_name string) (string, error) {
  8.         //panic一下
  9.         defer func() {
  10.                 if r := recover(); r != nil {
  11.                         fmt.Println("panic is ", r)
  12.                 }
  13.         }()
  14.         if file_name != "1.txt" {
  15.                 return "", FileNotFoundError{filename: file_name}
  16.         }
  17.         bytes, err := os.ReadFile("1.txt")
  18.         if err != nil {
  19.                 return "", fmt.Errorf("文件 %s 打开出错", file_name)
  20.         } else {
  21.                 return string(bytes), nil
  22.         }
  23. }
  24. func main() {
  25.         file, err := readfile("1.txt")
  26.         if err != nil {
  27.                 if errors.As(err, FileNotFoundError{}) {
  28.                         fmt.Println("预期错误:", err)
  29.                 } else {
  30.                         fmt.Println("非预期错误:", err)
  31.                 }
  32.                 return
  33.         }
  34.         fmt.Println("文件内容为:", file)
  35. }
复制代码
接口

接口在go语言中也有点抽象,对于接口的实现其实很简单,只是在用的时候比较抽象,结构体实现接口反而是最容易接受的,像基本类型还有切片这两种接口的使用就比较抽象。
结构体实现接口

(在结构体实现结构以及方法的调用基本没啥题目,很正常的操作)
  1. package main
  2. import "fmt"
  3. type User interface {
  4.         getName() string
  5.         getAge() int
  6. }
  7. type Person struct {
  8.         name string
  9.         age  int
  10. }
  11. func (p Person) getName() string {
  12.         return p.name
  13. }
  14. func (p Person) getAge() int {
  15.         return p.age
  16. }
  17. func main() {
  18.         p := Person{
  19.                 name: "zhangsan",
  20.                 age:  18,
  21.         }
  22.         fmt.Println(p.getName(), p.getAge())
  23. }
复制代码
基本类型实现接口

这里我分两种情况,string和int实现接口,目标是相识在实现接口后怎么使用这个方法。
  1. type Stringer interface {
  2. //接口的这两个方法写了之后就要实现。所以下面就实现了
  3.         String() string
  4.         Ascii() string
  5. }
  6. type MyString string //string类型
  7. type MyInt int       //int类型
  8. func (s MyString) String() string {
  9.         return string(s) //其实可以不转string也能直接返回,因为MyString本身就是string类型,只是我换了个别名而已
  10. }
  11. func (t MyInt) Ascii() string {
  12.         return string(t) //一定要转,因为本身是int类型
  13. }
  14. func (t MyInt) String() string {
  15.         return fmt.Sprintf("%d", t) //一定要转,因为本身是int类型
  16. }
  17. func main() {
  18.         //结构体实现接口效果
  19.         //start_struct_interface()
  20.         var s Stringer = MyString("传递参数string")  
  21.         var i Stringer = MyInt(97)
  22.         fmt.Println(s.String())
  23.         fmt.Println(i.Ascii())  //将数字转为ascii
  24.         fmt.Println(i.String()) //将数字作为字符串输出
  25. }
复制代码
细节:
实际操作下来其实也没有说很难理解,只是可能类型转换谁人地方卡了一下导致难以理解而已
  1. 重点是看懂这里的代码:
  2. var s Stringer = MyString("传递参数string")  
  3. var i Stringer = MyInt(97)
  4. 接口类型接收实现了接口方法的类型,然后就能够调用接口方法了,就这么理解就行了。
  5. 在MyString和MyInt中都是强制类型转换,将string字符和int数字转为对应的别名,然后给到变量后就能直接使用实现了接口的方法,因为已经转为了那两个基本类型了。
  6. 在强调一遍:本身是没有我们定义的这种类型的,所以要强制转换,然后接口其实就能随便写了,管你要不要用他这个值。
  7. 条件:
  8. 属于这个类型(强制类型转换)
  9. 实现了接口方法(正常实现接口方法)
  10. 调用就直接调用即可(正常)
复制代码
切片实现接口

其实到了切片实现接口就很容易理解了
我发现其实是你这个类型实现了这个接口后,就可以用了
我发现要用这个方法只是仅仅的需要你是这个类型,而不是说在于什么逼迫类型转换,而是你要用这个接口方法是由于谁人类型实现了这个接口啊,所以你要逼迫类型转换,所以要实例化这个类型啊,确实有点悟道了,也有点不明白我之前到底为啥会卡住。
切片实现接口也很简单,到这里其实已经不分什么结构体、基本类型、切片的了,本质就是你实例化一个实现了接口的类型,然后你的某个类型实现了接口类型的方法,那么你就直接实例化给到接口类型就拿这个类型去调用方法就行了。 (有点抽象,照旧直接看代码吧)
  1. type I_slice interface { //接口类型
  2.         sum() int //返回切片的和
  3. }
  4. type MySlice []int //切片类型,换一个intslice别名而已,方便自定义,且实现接口方法
  5. func (ms I_slice) sum() int { //就是自己定义的类型实现了接口类型而已,很好理解
  6.         s := 0
  7.         for _, v := range ms {
  8.                 s += v
  9.         }
  10.         return s
  11. }
  12. func main() {
  13.         var s I_slice = MySlice{1, 2, 3, 4, 5} //我们自己自定义的切片类型然后赋值给接口类型
  14.    
  15.      //var s MySlice = MySlice{1, 2, 3, 4, 5}
  16.     //var s = MySlice{1, 2, 3, 4, 5} //这两其实也可以,就是我们自定义的类型本来就是有实现这个方法的,只不过你没有赋值给接口类型,所以不算实现了接口的方法而已,但是你本身就拥有的方法当然可以用啦
  17.         fmt.Println(s.sum())
  18. }
复制代码
接口练习

项⽬描述
  1. 创建⼀个形状计算器,它可以计算不同形状的⾯积。需要定义⼀个 Shape 接⼝,
  2. 并为不同的形状(如圆形和矩形)实现这个接⼝。
  3. 然后编写⼀个函数来计算并打印这些形状的⾯积。
  4. 步骤
  5. 定义⼀个 Shape 接⼝,包含⼀个 area ⽅法。
  6. 创建⼀个 Circle 结构体,并实现 Shape 接⼝。
  7. 创建⼀个 Rectangle 结构体,并实现 Shape 接⼝。
  8. 编写⼀个函数 printArea,接受⼀个 Shape 类型的参数,并打印它的⾯积。
  9. 在 main 函数中创建⼀些 Circle 和 Rectangle 实例,并调⽤ printArea 函数来打印它们的⾯积。
复制代码
示例代码
  1. type Shape interface {
  2.         Area() float64
  3. }
  4. type Circle struct {
  5.         radius float64
  6. }
  7. type Rectangle struct {
  8.         width  float64
  9.         height float64
  10. }
  11. func (c Circle) Area() float64 {
  12.         return (c.radius * c.radius * math.Pi)
  13. }
  14. func (r Rectangle) Area() float64 {
  15.         return (r.width * r.height)
  16. }
  17. func printArea(s Shape) {
  18.         /*
  19.                 这里实际上就相当于强制类型转换了,
  20.                 因为两个结构体实现了Area方法,
  21.                 那么就强制类型转换为Shape后能够在函数中正常使用该方法
  22.         */
  23.         fmt.Printf("%.2f\n", s.Area())
  24. }
  25. func main() {d
  26.         c := Circle{radius: 2}
  27.         r := Rectangle{width: 2, height: 4}
  28.         printArea(c)
  29.         printArea(r)
  30. }
复制代码
Embed嵌入文件

只支持嵌入为string, byte切片和embed.FS三种类型。相对来说比较难理解的是embed.FS。
使⽤//go:embed后,下⽅必须是全局变量。
使用embed可以嵌⼊⽂件夹下的⽂件,使⽤通配符*,比如static/*
示例代码:
  1. package main
  2. import (
  3.         "embed"
  4.         _ "embed"
  5.         "fmt"
  6. )
  7. //go:embed a.txt
  8. var a string
  9. //go:embed static/1.txt
  10. var s []byte
  11. //go:embed static/*
  12. var f1 embed.FS
  13. //go:embed static/* static/2.txt
  14. var f2 embed.FS
  15. //go:embed static/1.txt
  16. //go:embed static/2.txt
  17. //go:embed static/3.txt
  18. var f3 embed.FS
  19. func main() {
  20.         fmt.Println("============string接收================")
  21.         fmt.Println(a)
  22.         fmt.Println("============byte接收================")
  23.         fmt.Printf("%q\n", s)
  24.         fmt.Println(string(s))
  25.         fmt.Println("============FS单个文件================")
  26.         data, _ := f1.ReadFile("static/1.txt")
  27.         fmt.Println(string(data))
  28.         fmt.Println("============FS目录,当前目录等等多个文件,go:embed空格隔开================")
  29.         data, _ = f2.ReadFile("static/2.txt")
  30.         fmt.Println(string(data))
  31.         fmt.Println("============FS多个文件,go:embed可以不用空格隔开================")
  32.         data, _ = f3.ReadFile("static/3.txt")
  33.         fmt.Println(string(data))
  34. }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4