type关键字 type:用于类型定义和类型别名
类型定义:type 类型名 Type
类型别名:type 类型名 = Type
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 package mainimport ( "fmt" "strconv" ) func main () { var i1 myint var i2 = 100 i1 = 200 fmt.Println(i1, i2) var name mystr name = "王二狗" var s1 string s1 = "李小花" fmt.Println(name, s1) fmt.Printf("%T,%T,%T,%T\n" , i1, i2, name, s1) fmt.Println("----------------------------------" ) res1 := fun1() fmt.Println(res1(10 , 20 )) fmt.Println("----------------------------------" ) var i3 myint2 i3 = 1000 fmt.Println(i3) i3 = i2 fmt.Println(i3) fmt.Printf("%T,%T,%T\n" , i1, i2, i3) } type myint int type mystr string type myfun func (int , int ) string func fun1 () myfun { fun := func (a, b int ) string { s := strconv.Itoa(a) + strconv.Itoa(b) return s } return fun } type myint2 = int
输出结果:
1 2 3 4 5 6 7 8 9 200 100 王二狗 李小花 main.myint,int,main.mystr,string ---------------------------------- 1020 ---------------------------------- 1000 100 main.myint,int,int
尽管类型别名可以方便地使用现有类型,但它们不会创建一个新的类型。因此,无法在类型别名上定义新方法。只有在本地类型上才能定义新方法。
错误代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 package mainimport "time" func main () {} type MyDuration = time.Durationfunc (m MyDuration) SimpleSet() { }
要么在time包下定义新方法,要么建一个新的命名类型,而不是使用类型别名。
修正:
1 2 3 4 5 6 7 8 9 10 11 12 13 package mainimport "time" type MyDuration time.Durationfunc (m MyDuration) SimpleSet() { } func main () { }
在结构体成员嵌入时使用别名:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 package mainimport "fmt" type Person struct { name string } func (p Person) show() { fmt.Println("Person--->" , p.name) } type People = Personfunc (p People) show2() { fmt.Println("People--->" , p.name) } type Student struct { Person People } func main () { var s Student s.Person.name = "王二狗" s.Person.show() fmt.Printf("%T,%T\n" , s.Person, s.People) s.People.name = "李小花" s.People.show2() s.Person.show() }
输出结果:
1 2 3 4 Person---> 王二狗 main.Person,main.Person People---> 李小花 Person---> 王二狗
错误处理 在实际工程项目中,我们希望通过程序的错误信息快速定位问题,但是又不喜欢错误处理代码写的冗余而又啰嗦。Go语言没有提供像Java、c#语言中的try…catch异常处理方式,而是通过函数返回值逐层往上抛。这种设计,鼓励工程师在代码中显式的检查错误,而非忽略错误,好处就是避免漏掉本应处理的错误。但是带来一个弊端,让代码啰嗦。
什么是错误 错误是什么? 错误指的是可能出现问题的地方出现了问题。比如打开一个文件时失败,这种情况在人们的意料之中。 而异常指的是不应该出现问题的地方出现了问题。比如引用了空指针,这种情况在人们的意料之外。可见,错误是业务过程的一部分,而异常不是。 Go中的错误也是一种类型。错误用内置的error类型表示。就像其他类型的,如int,float64。错误值可以存储在变量中,从函数中返回,等等。
演示错误 让我们从一个示例程序开始,这个程序尝试打开一个不存在的文件。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package mainimport ( "fmt" "os" ) func main () { f, err := os.Open("test.txt" ) if err != nil { fmt.Println(err) if ins, ok := err.(*os.PathError); ok { fmt.Println("1.Op:" , ins.Op) fmt.Println("2.Path:" , ins.Path) fmt.Println("3.Err:" , ins.Err) } return } fmt.Println(f.Name(), "打开文件成功。。" ) }
输出结果:
1 2 3 4 open test.txt: The system cannot find the file specified. 1.Op: open 2.Path: test.txt 3.Err: The system cannot find the file specified.
这行代码尝试将错误值 err
转换为 *os.PathError
类型,并将结果存储在变量 ins
中。这是一个类型断言的示例,它检查错误类型是否是 *os.PathError
,并且返回一个布尔值 ok
表示是否成功进行了类型转换。如果类型断言成功,这些代码将打印 *os.PathError
类型的特定字段信息。Op
字段表示操作类型,Path
字段表示操作的路径,Err
字段表示底层错误。
自定义函数返回错误 error:内置的数据类型,内置的接口 定义方法:Error() string
使用go语言提供好的包: errors包下的函数:New(),创建一个error对象,fmt包下的Errorf()函数: func Errorf(format string, a …interface{}) error
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package mainimport ( "errors" "fmt" ) func main () { err1 := errors.New("自己创建玩的。。" ) fmt.Println(err1) fmt.Printf("%T\n" , err1) err2 := fmt.Errorf("错误的信息码:%d" , 100 ) fmt.Println(err2) fmt.Printf("%T\n" , err2) fmt.Println("-----------------" ) err3 := checkAge(-30 ) if err3 != nil { fmt.Println(err3) return } fmt.Println("程序。。。go on。。。" ) } func checkAge (age int ) error { if age < 0 { err := fmt.Errorf("您给定的年龄是:%d,不合法" , age) return err } fmt.Println("年龄是:" , age) return nil }
输出结果:
1 2 3 4 5 6 自己创建玩的。。 *errors.errorString 错误的信息码:100 *errors.errorString ----------------- 您给定的年龄是:-30,不合法
错误的类型表示
1 2 3 4 5 if ins, ok := err.(*os.PathError); ok { fmt.Println("1.Op:" , ins.Op) fmt.Println("2.Path:" , ins.Path) fmt.Println("3.Err:" , ins.Err) }
获得更多信息的第二种方法是断言底层类型,并通过调用struct类型的方法获取更多信息。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package mainimport ( "fmt" "net" ) func main () { addr, err := net.LookupHost("www.baidu.com" ) fmt.Println(err) if ins, ok := err.(*net.DNSError); ok { if ins.Timeout() { fmt.Println("操作超时。。" ) } else if ins.Temporary() { fmt.Println("临时性错误。。" ) } else { fmt.Println("通常错误。。" ) } } fmt.Println(addr) }
输出结果:
1 2 <nil> [182.61.200.6 182.61.200.7]
3.直接比较 获得更多关于错误的详细信息的第三种方法是直接与类型错误的变量进行比较。让我们通过一个例子来理解这个问题。 filepath包的Glob函数用于返回与模式匹配的所有文件的名称。当模式出现错误时,该函数将返回一个错误ErrBadPattern。 在filepath包中定义了ErrBadPattern,如下所述:
1 var ErrBadPattern = errors.New("syntax error in pattern" )
errors.New()用于创建新的错误。 当模式出现错误时,由Glob函数返回ErrBadPattern。
让我们写一个小程序来检查这个错误:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport ( "fmt" "path/filepath" ) func main () { files, err := filepath.Glob("[" ) if err != nil && err == filepath.ErrBadPattern { fmt.Println(err) return } fmt.Println("files:" , files) }
输出结果:
记住永远不要忽略一个错误。忽视错误会招致麻烦。
自定义错误 示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package mainimport ( "fmt" "math" ) func main () { radius := -3.0 area, err := circleArea(radius) if err != nil { fmt.Println(err) if err, ok := err.(*areaError); ok { fmt.Printf("半径是:%.2f\n" , err.radius) } return } fmt.Println("圆形的面积是:" , area) } type areaError struct { msg string radius float64 } func (e *areaError) Error() string { return fmt.Sprintf("error:半径,%.2f,%s" , e.radius, e.msg) } func circleArea (radius float64 ) (float64 , error ) { if radius < 0 { return 0 , &areaError{"半径是非法的" , radius} } return math.Pi * radius * radius, nil }
输出结果:
1 2 error:半径,-3.00,半径是非法的 半径是:-3.00
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 package mainimport "fmt" func main () { length, width := -6.7 , -9.1 area, err := rectArea(length, width) if err != nil { fmt.Println(err) if err, ok := err.(*areaError); ok { if err.legnthNegative() { fmt.Printf("error:长度,%.2f,小于零\n" , err.length) } if err.widthNegative() { fmt.Printf("error:宽度,%.2f,小于零\n" , err.width) } } return } fmt.Println("矩形的面积是:" , area) } type areaError struct { msg string length float64 width float64 } func (e *areaError) Error() string { return e.msg } func (e *areaError) legnthNegative() bool { return e.length < 0 } func (e *areaError) widthNegative() bool { return e.width < 0 } func rectArea (length, width float64 ) (float64 , error ) { msg := "" if length < 0 { msg = "长度小于零" } if width < 0 { if msg == "" { msg = "宽度小于零" } else { msg += ",宽度也小于零" } } if msg != "" { return 0 , &areaError{msg, length, width} } return length * width, nil }
输出结果:
1 2 3 长度小于零,宽度也小于零 error:长度,-6.70,小于零 error:宽度,-9.10,小于零
panic和recover Golang中引入两个内置函数panic和recover来触发和终止异常处理流程,同时引入关键字defer来延迟执行defer后面的函数。 一直等到包含defer语句的函数执行完毕时,延迟函数(defer后的函数)才会被执行,而不管包含defer语句的函数是通过return的正常结束,还是由于panic导致的异常结束。你可以在一个函数中执行多条defer语句,它们的执行顺序与声明顺序相反。 当程序运行时,如果遇到引用空指针、下标越界或显式调用panic函数等情况,则先触发panic函数的执行,然后调用延迟函数。调用者继续传递panic,因此该过程一直在调用栈中重复发生:函数停止执行,调用延迟执行函数等。如果一路在延迟函数中没有recover函数的调用,则会到达该协程的起点,该协程结束,然后终止其他所有协程,包括主协程(类似于C语言中的主线程,该协程ID为1)。panic :
内建函数
假如函数F中书写了panic语句,会终止其后要执行的代码,在panic所在函数F内如果存在要执行的defer函数列表,按照defer的逆序执行
返回函数F的调用者G,在G中,调用函数F语句之后的代码不会执行,假如函数G中存在要执行的defer函数列表,按照defer的逆序执行,这里的defer有点类似 try-catch-finally中的finally
直到goroutine整个退出,并报告错误
recover :
内建函数
用来控制一个goroutine的panicking行为,捕获panic,从而影响应用的行为
一般的调用建议 a).在defer函数中,通过recover来终止一个gojroutine的panicking过程,从而恢复正常代码的执行 b).可以获取通过panic传递的error
简单来讲: go中可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常,然后正常处理 。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package mainimport "fmt" func main () { defer func () { if msg := recover (); msg != nil { fmt.Println(msg, "程序恢复啦。。。" ) } }() funA() defer myprint("defer main:3....." ) funB() defer myprint("defer main:4....." ) fmt.Println("main..over。。。。" ) } func myprint (s string ) { fmt.Println(s) } func funA () { fmt.Println("我是一个函数funA()...." ) } func funB () { fmt.Println("我是函数funB()..." ) defer myprint("defer funB():1....." ) for i := 1 ; i <= 10 ; i++ { fmt.Println("i:" , i) if i == 5 { panic ("funB函数,恐慌了" ) } } defer myprint("defer funB():2....." ) }
输出结果:
1 2 3 4 5 6 7 8 9 10 我是一个函数funA().... 我是函数funB()... i: 1 i: 2 i: 3 i: 4 i: 5 defer funB():1..... defer main:3..... funB函数,恐慌了 程序恢复啦。。。
由于恐慌发生在循环内部,之后的语句将不会执行。然而,之前已经被defer
关键字延迟执行的myprint
函数仍然会执行。因此,”defer funB():1…..”会被输出。funB函数的执行完毕后,恐慌会被传播到调用处,也就是main
函数中的匿名函数。在这个匿名函数中,我们通过recover
函数检测到了恐慌,所以会输出恐慌信息:”funB函数,恐慌了”,并打印”程序恢复啦。。。”。
错误和异常从Golang机制上讲,就是error和panic的区别。很多其他语言也一样, 比C++/Java,没有error有errno,没有panic但有throw。 Golang错误和异常是可以互相转换的: 1.错误转异常,比如程序逻辑上尝试请求某个URL,最多尝试三次,尝试三次的过程中请求失败是错误,尝试完第三次还不成功的话,失败就被提升为异常了。 2.异常转错误,比如panic触发的异常被recover恢复后,将返回值中error类型的变量进行赋值,以便上层函数继续走错误处理流程。
什么情况下用错误表达,什么情况下用异常表达,就得有一套规则,否则很容易出现一切皆错误或一切皆异常的情况。 以下给出异常处理的作用域(场景)∶
1.空指针引用 2.下标越界 3.除数为0 4.不应该出现的分支,比如default 5.输入不应该引起函数错误
其他场景我们使用错误处理,这使得我们的函数接口很精炼。对于异常,我们可以选择在一个合适的上游去recover,并打印堆栈信息,使得部署后的程序不会终止。