这是我参与「第五届青训营 」伴学笔记创作活动的第 6 天
今天和大家分享go基础的最后一部分。主要包括异常错误,断言,反射,结构体标签的概念和常见操作。
错误与异常
错误
go中内建了一个错误接口,任何实现该接口的方法都可以使用错误:
type error interface {
Error() string
}
例如,fmt.Println就会在内部调用Error()方法来返回错误字符串:
func FindFile() {
file, err := os.Open("/a.txt") //找一个不存在的文件
if err != nil {
fmt.Println(err)
} else {
fmt.Println(file)
}
}
//open /a.txt: no such file or directory
自定义错误:
//定义一个结构体
type errorString struct {
s string
}
//自定义error
func MyError(text string) error {
return &errorString{s: text}
}
//实现Error方法,来实现error接口
func (e *errorString) Error() string {
return e.s
}
func divide(a, b int) (int, error) {
if b == 0 {
return 0, MyError("被除数不能为零")
}
return a / b, nil
}
func main() {
fmt.Println(divide(1, 0)) //0 被除数不能为零
}
更简单的,我么可以使用return errors.New(“错误信息”)或者fmt.Errorf("错误信息")快速返回自定义信息的错误
异常
异常指的是不应该出现问题的地方真的出现了问题。
我们可以使用panic来触发异常:
panic("asd") //panic: asd
fmt.Println("123") //并不会执行
程序发生异常的时候,会在panic处停止,执行完所有延迟函数后,执行并打印panic中的值,就此返回,并返回堆栈信息。在没有panic的时候,发生异常则也会立即停止,执行延迟函数,打印错误信息,返回堆栈信息。可以看做在异常的下一句直接执行了panic。
同时,我们可以通过recover 捕获异常:
func main() {
read(2)
fmt.Println("123") //会打印出来
}
func read(i int) {
defer func() {
err := recover() // 只用被延迟的方法可以使用recover()
fmt.Println(err)
}()
arr := [1]int{1}
fmt.Println(arr[i])
}
//runtime error: index out of range [2] with length 1
//123
会继续执行的原因是函数内发生了异常,本应立即返回,但是被defer中的recover函数捕获了,所以没有在外层产生异常。
异常会不断向上传递,所以我们可以在顶层函数上的延迟函数中捕获异常保证程序不崩溃
断言
基础
断言就是将接口类型的值x转换成类型T。格式为:x.(T)
- 类型断言的必要条件就x是接口类型,非接口类型的x不能做类型断言;
- T可以是非接口类型(基础类型,结构体或者指针等),如果想断言合法,则T必须实现x的接口;
- T也可以是接口,则x的动态类型也应该是接口T;
- 类型断言如果非法,运行时会导致错误,为了避免这种错误,应该总是使用下面的方式来进行类型断言:
package main
import (
"fmt"
)
func main() {
var x interface{}
x = 100
value1,ok :=x.(int)
if ok {
fmt.Println(value1)
}
value2,ok :=x.(string)
if ok {
fmt.Println(value2)
}
}
需要注意的如果不接收第二个参数也就是ok,这里失败的话则会直接panic。这里还存在一种情况就是x为nil同样会panic
若类型检查成功提取到的值也将拥有对应type的方法:
package main
import "fmt"
func main() {
var a interface{}
a =A{}
value :=a.(A)
value.Hi()
fmt.Println("看是否输出",value.Name)
}
type A struct {
Name string
}
func (a *A) Hi() {
a.Name="fushaohua"
fmt.Println(a)
}
这里我们定义一个结构体,又定义了一个方法,其中方法的参数类型为一个泛型,那么此时我们想调用参数的属性或方法就会有问题:
type User struct {
Name string
Age int
Sex bool
}
func (u User) SayName() (name string) {
fmt.Println(u.Name)
return
}
func main() {
u := User{
Name: "aei",
Age: 10,
Sex: true,
}
check(u)
}
func check(v interface{}) {
v.SayName() // 报错 type interface{} has no field or method SayName
}
这个时候我们使用断言就可以结局这个问题:
直接使用断言:
v.(结构体类型).属性或方法
func check(v interface{}) {
v.(User).SayName() // 不再报错
}
同时我们可以通过v.(type)和switch结合使用获得动态的类型值:
func check(v interface{}) {
switch v.(type) {
case User:
v.(User).SayName()
}
}
断言和指针
type A struct {
name string
}
type Boy interface {
getName() string
}
func (receiver A) getName() string {
return receiver.name
}
func main() {
a := A{
name: "xiaoming",
}
formatName(&a)
fmt.Println(a.name) // Mr.xiaoming} 如果我们在下面使用 ns.(A) ,则值不会被修改
}
// 这里的形参类型为接口,我们传递结构体或者指针都可以
func formatName(boy Boy) {
//n, _ := ns.(A) // 这里我们断言为结构体是不会报错的,go会帮我们自动解引用,但是这样我们后续的修改就不会影响传过来的参数了
n, _ := boy.(*A)
n.name = "Mr." + n.name
}
反射
go提供了一种机制,能够在运行时更新变量和检查它们的值,调用他们的方法和它们支持的内在操作,而不需要在编译时就知道这些变量的具体类型。
go中reflect包实现了运行时反射。reflect包会帮助识别interface{}变量的底层具体类型和具体值
func main() {
reflectType(123) //int
reflectType("abc") //string
}
func reflectType(x interface{}) {
obj := reflect.TypeOf(x)
fmt.Println(obj)
}
与reflect.Typeof()类型不同,Kind()代表一个大的种类:
type Book struct {
value string
}
func main() {
book := Book{
value: "《自行车》",
}
reflectType(book)
}
func reflectType(x interface{}) {
typeb := reflect.TypeOf(x)
kind := typeb.Kind() //注意这里是对reflect.TypeOf(x)返回的值进行.kind
fmt.Println(typeb)
fmt.Println(kind)
}
//main.Book 类型
//struct 种类
我们也可以通过NumField来返回字段的数量:
type Book struct {
label string
value int
}
func main() {
book := Book{
label: "《自行车》",
value: 12,
}
reflectType(book)
}
func reflectType(x interface{}) {
typeb := reflect.TypeOf(x)
kind := typeb.Kind()
if kind == reflect.Struct {
fmt.Println(reflect.ValueOf(x).NumField())
}
}
// 2
同时我们可以和reflect.ValueOf(obj).Field(index)搭配起来,对结构体进行遍历:
type person struct {
name string
age int
}
func main() {
v := reflect.ValueOf(person{"steve", 30})
count := v.NumField()
for i := 0; i < count; i++ {
f := v.Field(i)
fmt.Println(f)
}
}
// steve
// 30
也可以通过
func (v Value) FieldByIndex(index []int) Value
func (v Value) FieldByName(name string) Value
来拿到对应下标和对应属性名的值
结构体标签
在结构体上使用反引号加上字符串称为Tag,通常写作键值对的形式:
type person struct {
Name string `json:"name"` //json 包只识别以大写字母开头的属性
Age int `json:"age,omitempty"` //加上omitempty当字段为空的时候,不填充该字段
}
func main() {
p := reflect.TypeOf(person{})
name, _ := p.FieldByName("Name")
tag := name.Tag
keyValue, _ := tag.Lookup("json")
fmt.Printf("tag和key值:%s %s\n", tag, keyValue)
}
//tag和key值:json:"name" name
tag可用作json的encode:
type person struct {
Name string `json:"name"` //json 包只导出以大写字母开头的属性,我们通过标签改变其导出时的格式
Age int `json:"age,omitempty"` //加上omitempty当字段为空的时候,不填充改字段
}
func main() {
var v person
v = person{
Name: "steve",
}
data, err := json.Marshal(v)
if err == nil {
fmt.Printf("%s", data)
}
}
//{"name":"steve"} 在JSON序列化的时候会根据结构体标签导出对应格式