Go红队开发—语法补充
目录[*]错误控制
[*]使用
[*]自定义错误类型
[*]错误包装
[*]errors.Is 和 errors.As
[*]panic捕获、recover 、defer
[*]错误控制练习
[*]接口
[*]结构体实现接口
[*]基本类型实现接口
[*]切片实现接口
[*]接口练习
[*]Embed嵌入文件
之前有师傅问这个系列好像跟红队没啥关系,前几期确实没啥关系,由于这都是进行红队工具开发的前置知识点,对于我个人强迫症而言只是想让这个系列更加美满而已,所以前置知识也加进去了,有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{}。
[*]当执行
fmt.Println(err)时:
[*]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
ageint
}
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 {
widthfloat64
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/*
示例代码:
https://img2023.cnblogs.com/blog/3392862/202502/3392862-20250227175110199-1911407628.png
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企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]