vs2017做的网站如何发布,眉山网站优化,wap自助建站排板,昆明app制作公司在哪里文章目录 错误控制使用自定义错误类型错误包装errors.Is 和 errors.Aspanic捕获、recover 、defer错误控制练习 接口结构体实现接口基本类型实现接口切片实现接口 接口练习Embed嵌入文件 之前有师傅问这个系列好像跟红队没啥关系#xff0c;前几期确实没啥关系#xff0c;因为… 文章目录 错误控制使用自定义错误类型错误包装errors.Is 和 errors.Aspanic捕获、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 mainimport fmttype MyError struct {When stringWhat 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 mainimport (errorsfmt
)// 定义一个自定义错误
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 mainimport (errorsfmt
)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 pointerfmt.Println(捕获到除以零的错误)} else {fmt.Println(其他错误:, err)}
}
panic捕获、recover 、defer
解释
panic 能够改变程序的控制流调用 panic 后会立刻停止执行当前函数的剩余代码并在当前 Goroutine 中递归执行调用方的 deferrecover 可以中止 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 mainimport fmttype User interface {getName() stringgetAge() int
}type Person struct {name stringage 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() stringAscii() 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()) //将数字转为asciifmt.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 : 0for _, 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 float64height 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() {dc : 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 mainimport (embed_ embedfmt
)//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.FSfunc 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))
}