1.函数
-
Go语言中支持函数,匿名和闭包,并且函数在Go语言中属于“一等公民”
-
特点:
• 无需声明原型。
• 支持不定 变参。
• 支持多返回值。
• 支持命名返回参数。
• 支持匿名函数和闭包。
• 函数也是一种类型,一个函数可以赋值给变量。
• 不支持 嵌套 (nested) 一个包不能有两个名字一样的函数。
• 不支持 重载 (overload)
• 不支持 默认参数 (default parameter)。
1函数的定义
-
函数声明包含一个函数名,参数列表, 返回值列表和函数体。如果函数没有返回值,则返回列表可以省略。函数从第一条语句开始执行,直到执行return语句或者执行函数的最后一条语句。函数可以没有参数或接受多个参数。(注意类型在变量名之后 )当两个或多个连续的函数命名参数是同一类型,则除了最后一个类型之外,其他都可以省略。函数可以返回任意数量的返回值。
-
Go语言中定义函数使用func 关键字,具体格式如下:
func 函数名(参数)(返回值){
函数体
}
-
其中:
- 函数名:由字母,数字,下划线组成,但函数名的第一个字母不能是数字,在同一个包内,函数名称不能重名
- 参数:参数由参数变量和参数变量的类型,多个参数之间使用,分隔
- 返回值:返回值由返回值变量和其变量类型组成,也可以只写返回值的类型,多个返回值,必须用()包裹,并用
, 分隔。
- 函数体:实现指定功能的代码块。
-
定义一个简单函数
package main
import "fmt"
//函数
//定义一个不需要参数也没有返回值的函数:sayHello
func sayHello() {
fmt.Println("hello xjk")
}
func main() {
//函数调用
sayHello()
}
-
函数作为参数传递,可以将复杂签名定义为函数类型,以便于阅读
package main
import "fmt"
func test(fn func() int) int {
return fn()
}
// 定义函数类型
type FormatFunc func(s string,x,y int) string
// format接收fn 的类型为为一个函数类型,通过type定义一个函数类型更直观
func format(fn FormatFunc,s string, x,y int) string {
return fn(s,x,y)
}
func main() {
// test 内部为一个匿名函数
s1 := test(func() int { return 100})//直接将匿名函数当参数
// 第一个参数为匿名函数,定义接收类型,返回类型和函数逻辑。
// 第二个参数 "%d,%d" 字符串
// 第三个,第四个 参数为数字
s2 := format(func(s string, x, y int) string {
return fmt.Sprintf(s, x, y)
}, "%d, %d", 10, 20)
println(s1,s2)
// 100 10, 20
}
2.函数参数
-
函数定义时指出,函数定义时有参数,该变量可称为函数的形参。形参就像定义在函数体内的局部变量。
但当调用函数,传递过来的变量就是函数的实参,函数可以通过两种方式来传递参数:
-
值传递:指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
func swap(x,y int) int {
... ...
}
-
引用传递:是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
func swap(x,y *int){
*x,*y = *y,*x
}
func main() {
var a, b int = 1, 2
swap(&a, &b)
fmt.Println(a, b)
}
// 2 1
-
在默认情况下,Go 语言使用的是值传递,即在调用过程中不会影响到实际参数。
-
注意1:无论是值传递,还是引用传递,传递给函数的都是变量的副本,不过,值传递是值的拷贝。引用传递是地址的拷贝,一般来说,地址拷贝更为高效。而值拷贝取决于拷贝的对象大小,对象越大,则性能越低。
-
注意2:map、slice、chan、指针、interface默认以引用的方式传递。
-
不定参数传值 就是函数的参数不是固定的,后面的类型是固定的。(可变参数)
Golang 可变参数本质上就是 slice。只能有一个,且必须是最后一个。
在参数赋值时可以不用用一个一个的赋值,可以直接传递一个数组或者切片,特别注意的是在参数后加上“…”即可。
func myfunc(args ...int) { //0个或多个参数
}
func add(a int, args…int) int { //1个或多个参数
}
func add(a int, b int, args…int) int { //2个或多个参数
}
注意:其中args是一个slice,我们可以通过arg[index]依次访问所有参数,通过len(arg)来判断传递参数的个数。
任意类型的不定参数: 就是函数的参数和每个参数的类型都不是固定的。
用interface{}传递任意类型数据是Go语言的惯例用法,而且interface{}是类型安全的。
func myfunc(args ...interface{}) {
}
func test(s string, n...int) string {
var x int
for _,i := range n{
x += i
}
return fmt.Sprintf(s,x)
}
func main() {
println(test("sum:%d",1,2,3))
}
// sum:6
-
使用 slice 对象做变参时,必须展开。(slice...)
func test(s string, n...int) string {
var x int
for _,i := range n{
x += i
}
return fmt.Sprintf(s,x)
}
func main () {
s := []int{1,2,3}
// s... 将slice展开
res := test("sum:%d",s...)
println(res)
}
3.函数返回值
-
可以用"_" 标识符,用来忽略函数的某个返回值。Go 的返回值可以被命名,并且就像在函数体开头声明的变量那样使用。返回值的名称应当具有一定的意义,可以作为文档使用。没有参数的 return 语句返回各个返回变量的当前值。这种用法被称作“裸”返回。直接返回语句仅应当用在像下面这样的短函数中。在长的函数中它们会影响代码的可读性。
-
demo
func add(a,b int) (c int) {
c = a + b
return
}
// 指定返回的sum和avg类型,所以return 后面可以不接变量
func calc(a,b int) (sum int,avg int) {
sum = a + b
avg = (a + b) / 2
return
}
func main() {
var a,b int = 1,2
c := add(a,b)
sum,avg := calc(a,b)
fmt.Println(a,b,c,sum,avg)
}
// 1 2 3 3 1
-
Golang返回值不能用容器对象接收多返回值。只能用多个变量,或 "_" 忽略。
func test() (int, int) {
return 1,2
}
func main() {
x,_ := test()
println(x)//1
}
-
多返回值可直接作为其他函数调用实参。
func test()(int,int) {
return 1,2
}
func add(a,b int) int {
return a+b
}
func sum(n ...int) int{
var x int
for _,i := range n{
x += i
}
return x
}
func main() {
fmt.Println(add(test()))//3
fmt.Println(sum(test()))//3
}
-
命令返回参数可看作与形参类似的局部变量,最后由return隐式返回。
package main
func add(x,y int) (z int) {
z = x + y
return
}
func main() {
println(add(1,2))//3
}
-
命名返回参数可被同名局部变量遮蔽,此时需要显示返回。
func add(x,y int)(z int){
// var z不能再一个级别重复定义,会引发"z redeclared in this block" 错误
var z = x+y
// 必须显式返回
return z
}
-
命名返回参数允许defer延迟调用通过闭包读取和修改
func add(a,b int) (c int) {
defer func() {
c += 100
}()
c = a + b
return
}
func main() {
println(add(1, 2)) //103
}
-
显示return返回前,会先修改命名返回参数。
func add(x, y int) (z int) {
defer func() {
println("defer:",z) // 输出: 203
}()
z = x + y
return z + 200 // 执行顺序: (z = z + 200) -> (call defer) -> (return)
}
func main() {
println(add(1,2))
}
// defer: 203
// 203
4.匿名函数
-
匿名函数是指不需要定义函数名的一种函数实现方式。1958年LISP首先采用匿名函数。
在Go里面,函数可以像普通变量一样被传递或使用,Go语言支持随时在代码里定义匿名函数。
匿名函数由一个不带函数名的函数声明和函数体组成。匿名函数的优越性在于可以直接使用函数内的变量,不必申明。
package main
import (
"fmt"
"math"
)
func main() {
getSqrt := func(a float64) float64 {
// 开平方根
return math.Sqrt(a)
}
fmt.Println(getSqrt(4))//2
}
上面先定义了一个名为getSqrt 的变量,初始化该变量时和之前的变量初始化有些不同,使用了func,func是定义函数的,可是这个函数和上面说的函数最大不同就是没有函数名,也就是匿名函数。这里将一个函数当做一个变量一样的操作。
-
Golang匿名函数可赋值给变量,做为结构字段,或者在 channel 里传送。
package main
import (
"fmt"
// "math"
)
func main() {
// 复制变量通过() 调用
fn := func() { fmt.Println("Hello World")}
fn()
// 切片定义多个函数
fn2 := [](func(x int) int){
func(x int) int {return x+1},
func(x int) int {return x+2},
}
// 通过索引调用函数
println(fn2[0](100))
// 结构体内定义函数
d := struct {
fn func() string
}{
fn: func() string {return "hello world!"},
}
// 通过点语法调用
println(d.fn())
// 再channel里传送
fc := make(chan func() string,2)
fc <- func() string {return "Hello,Go!"}
println((<-fc)())
}
// Hello World
// 101
// hello world!
// Hello,Go
5.闭包与递归
-
由函数及其相关引用环境组合而成的实体(闭包=函数+引用环境)
-
Go语言支持闭包的,这里只是简单地讲一下在Go语言中闭包是如何实现的
package main
import "fmt"
func a() func() int{
i:= 0
b := func() int {
i++
fmt.Println(i)
return i
}
return b
}
func main() {
c := a()
c()//1
c()//2
// c = a() 调用a() 不会输出 i 的值
}
-
因闭包复制时原对象指针,这样很容易解释延迟引用现象。
func test() func() {
x := 100
fmt.Printf("x (%p) = %d\n", &x, x)
return func() {
fmt.Printf("inner x (%p) = %d\n", &x, x)
}
}
func main() {
f := test()
f()
// x (0x1100a090) = 100
// inner x (0x1100a090) = 100
}
在汇编层 ,test 实际返回的是 FuncVal 对象,其中包含了匿名函数地址、闭包对象指针。当调 匿名函数时,只需以某个寄存器传递该对象即可。
FuncVal { func_address, closure_var_pointer ... }
-
外部引用函数参数局部变量。
package main
import "fmt"
// 外部引用函数参数局部变量
func add(base int) func(int) int {
return func(i int) int {
base += i
return base
}
}
func main() {
tmp1 := add(10)
fmt.Println(tmp1(1), tmp1(2))
// 此时tmp1和tmp2不是一个实体了
tmp2 := add(100)
fmt.Println(tmp2(1), tmp2(2))
}
-
返回2个闭包
func test(base int) (func(int) int, func(int) int ){
add := func(i int) int {
base += i
return base
}
sub := func(i int) int {
base -= i
return base
}
return add,sub
}
func main() {
f1,f2 := test(1)
// base一直是没有消
// f1执行完base为11,f2执行会用base=11在函数进行运算,所以为-4
fmt.Println(f1(10),f2(15))// 11 -4
}
-
递归函数
-
递归,就是在运行的过程中调用自己。 一个函数调用自己,就叫做递归函数。
-
条件:
1.子问题须与原始问题为同样的事,且更为简单。
2.不能无限制地调用本身,须有个出口,化简为非递归状况处理。
-
数字阶乘
- 一个正整数的阶乘(factorial)是所有小于及等于该数的正整数的积,并且0的阶乘为1。自然数n的阶乘写作n!。
package main
import "fmt"
func factorial(i int) int {
if i<=1{
return 1
}
return i * factorial(i-1)
}
func main() {
var i int = 10
fmt.Printf("Factorial of %d is %d\n",i,factorial(i))// Factorial of 10 is 3628800
}
-
斐波那契数列
func fibonaci(i int) int {
if (i ==0) {
return 0
}
if (i == 1) {
return 1
}
return fibonaci(i-1) + fibonaci(i-2)
}
func main() {
var i int
for i = 0; i < 10; i++ {
fmt.Printf("%d\n", fibonaci(i))
}
}
// 0
// 1
// 1
// 2
// 3
// 5
// 8
// 13
// 21
// 34
6.延迟调用 defer
-
defer特性:
1.关键字 defer 用于注册延迟调用
2.这些调用直到return 前才被执行,因此可以用来做资源清理。
3.多个defer语句,按先进后出的方式执行。
4.defer语句中的变量,在defer声明时就决定了。
-
defer用途:
1.关闭文件句柄
2.锁资源释放
3.数据库连接释放
-
Go语言中defer语句会将其后面跟随的语句进行延迟处理,在defer归属的函数即将返回时,将延迟处理的语句按defer定义的逆顺序进行执行,也就是说,先被defer的语句最后被执行,最后被defer的语句最先被执行。
package main
import "fmt"
func main() {
var w [5] struct{}
for i := range w{
defer fmt.Println(i)
}
}
// 4
// 3
// 2
// 1
// 0
-
defer碰上闭包
package main
import "fmt"
func main() {
var w [5] struct{}
for i := range w{
defer func() {fmt.Println(i)}()
}
}
// 4
// 4
// 4
// 4
// 4
函数正常执行,由于闭包用到的变量 i 在执行的时候已经变成4,所以输出全都是4.
-
defer f.Close 存在陷阱
type Test struct {
name string
}
func (t *Test) Close() {
fmt.Println(t.name," closed")
}
func main() {
ts := []Test{{"a"},{"b"},{"c"}}
for _,t := range ts {
defer t.Close()
}
}
// c closed
// c closed
// c closed
上面代码不如我们预期输出的c b a 而是c c c
-
可是按照前面go sepc中说明应该c b a 才对,那么换一种方式调用
type Test struct {
name string
}
func (t *Test) Close() {
fmt.Println(t.name," closed")
}
func Close(t Test){
t.Close()
}
func main() {
ts := []Test{{"a"},{"b"},{"c"}}
for _,t := range ts {
defer Close(t)
}
}
// c closed
// b closed
// a closed
Go语言中并没有把这个struct的this指针做相应处理,没有明确写出来的this指针当作参数看待。而通过定义Close(t Test) 函数讲当前循环struct传入禁区,然后Test结构体内的方法。
-
多个defer注册,按FILO次序(先进后出),最后哪怕函数或某个延迟调用发生错误,也会调用依旧被执行。
func test(x int) {
defer println("a")
defer println("b")
defer func() {
println(100/x)// 这里传入0,会报错,异常未被捕获,逐步往外传递,最终终止进程。
}()
defer println("c")
}
func main() {
test(0)
}
// c
// b
// a
// panic: runtime error: integer divide by zero
-
延迟调用参数在注册时求值或复制,可用指针或闭包“延迟”读取。
package main
import "fmt"
func test() {
x,y := 10,20
defer func(i int) {
fmt.Println("defer:",i,y)// y在内层函数,引用外层变量
}(x) //此时x为值拷贝
x+=10
y+=100
fmt.Println("x=",x,"y=",y)
}
func main() {
test()
}
// x= 20 y= 120
// defer: 10 120
-
滥用defer可能会导致性能问题,尤其是在大循环里:
package main
import (
"fmt"
"sync"
"time"
)
// 声明 sync.Mutex互斥锁
var lock sync.Mutex
func test() {
// 加锁
lock.Lock()
// 解锁
lock.Unlock()
}
func testdefer() {
lock.Lock()
defer lock.Unlock()
}
func main() {
func() {
t1 := time.Now()
for i:=0;i<100000;i++ {
test()
}
// 计算程序运行时间
elapsed := time.Since(t1)
fmt.Println("test elapsed: ",elapsed)
}()
func() {
t1 := time.Now()
for i:=0;i<100000;i++ {
testdefer()
}
// 计算程序运行时间
elapsed := time.Since(t1)
fmt.Println("testdefer elapsed: ",elapsed)
}()
}
// test elapsed: 1.9929ms
// testdefer elapsed: 6.0194ms
-
defer 的陷阱
- defer 与 closure(闭包),如果 defer 后面跟的不是一个 closure 最后执行的时候我们得到的并不是最新的值。
package main
import (
"errors"
"fmt"
)
func foo(a, b int) (i int, err error) {
defer fmt.Printf("first defer err %v\n", err)
//第二个defer值拷贝。
defer func(err error) { fmt.Printf("second defer err %v\n", err) }(err)
defer func() { fmt.Printf("third defer err %v\n", err) }()
if b == 0 {
err = errors.New("divided by zero!")
return
}
i = a / b
return
}
func main() {
foo(2, 0)
}
// third defer err divided by zero!
// second defer err <nil>
// first defer err <nil>
-
defer和return
func foo() (i int) {
i = 0
defer func() {
fmt.Println(i)
}()
return 2
}
func main() {
foo()//2
}
在有具名返回值的函数中(这里具名返回值为i),执行return 2的时候实际上已经将i的值重新赋值为2,所以 defer closure输出结构为2,而不是1
-
defer nil 函数
package main
import "fmt"
//Recover 是一个Go语言的内建函数,可以让进入宕机流程中的 goroutine 恢复过来,recover 仅在延迟函数 defer 中有效,在正常的执行过程中,调用 recover 会返回 nil 并且没有其他任何效果,如果当前的 goroutine 陷入恐慌,调用 recover 可以捕获到 panic 的输入值,并且恢复正常的执行。
func test() {
// 函数指向空地址
var run func() = nil
defer fun()
fmt.Println("runs...")
}
func main() {
defer func() {
// recover捕捉panic输入值
if err:= recover()l err != nil {
fmt.Println(err)
}
}()
test()
}
// runs
// runtime error: invalid memory address or nil pointer dereference
解释:名为 test 的函数一直运行至结束,然后 defer 函数会被执行且会因为值为 nil 而产生 panic 异常。然而值得注意的是,run() 的声明是没有问题,因为在test函数运行完成后它才会被调用。
-
错误的位置使用defer
package main
import (
"net/http"
"fmt"
)
func do() error{
//当 http.Get 失败时会抛出异常。
res,err := http.Get("http://www.google.com")
defer res.Body.Close()
if err != nil {
return err
}
return nil
}
func main() {
do()
}
因为在这里我们并没有检查我们的请求是否成功执行,当它失败的时候,我们访问了 Body 中的空变量 res ,因此会抛出异常
-
改进:
- 总是在一次成功的资源分配下面使用 defer ,对于这种情况来说意味着:当且仅当 http.Get 成功执行时才使用 defer
package main
import (
"net/http"
"fmt"
)
func do() error{
//当 http.Get 失败时会抛出异常。
res,err := http.Get("http://www.baidudddsfw45fgsdfsfds.com")
// <nil> Get http://www.baidudddsfw45fgsdfsfds.com: dial tcp: lookup www.baidudddsfw45fgsdfsfds.com: no such host
fmt.Println(res,err)
if res != nil {
defer res.Body.Close()
}
if err != nil {
return err
}
return nil
}
func main() {
do()
}
解释:在这里,你同样需要检查 res 的值是否为 nil ,这是 http.Get 中的一个警告。通常情况下,出错的时候,返回的内容应为空并且错误会被返回,可当你获得的是一个重定向 error 时, res 的值并不会为 nil ,但其又会将错误返回。上面的代码保证了无论如何 Body 都会被关闭,如果你没有打算使用其中的数据,那么你还需要丢弃已经接收的数据。
-
defer 文件操作:
package main
import "os"
func do() (err error) {
f,err := os.Open("book.txt")
println(f,err)
if err != nil {
println(err)
return err
}
if f != nil {
println("f-->",f)
defer func() {
if ferr:= f.Close();ferr != nil {
err = ferr
}
}()
}
// code...
return nil
}
func main() {
do()
}
f.Close() 可能会返回一个错误,可这个错误会被我们忽略掉,通过命名的返回变量来返回 defer 内的错误。
-
释放相同的资源
package main
import (
"fmt"
"io"
"os"
)
func do() error {
f, err := os.Open("book.txt")
if err != nil {
return err
}
if f != nil {
defer func(f io.Closer) {
if err := f.Close(); err != nil {
fmt.Printf("defer close book.txt err %v\n", err)
}
}(f)
}
// ..code...
f, err = os.Open("another-book.txt")
if err != nil {
return err
}
if f != nil {
defer func(f io.Closer) {
if err := f.Close(); err != nil {
fmt.Printf("defer close another-book.txt err %v\n", err)
}
}(f)
}
return nil
}
func main() {
do()
}
如果你尝试使用相同的变量释放不同的资源,那么这个操作可能无法正常执行。当延迟函数执行时,只有最后一个变量会被用到,因此,f 变量 会成为最后那个资源 (another-book.txt)。而且两个 defer 都会将这个资源作为最后的资源来关闭
7.异常处理
-
Golang没有结构化异常,使用panic抛出错误,recover捕获错误。异常的使用场景简单描述:Go中可以抛出一个panic的异常,然后再defer通过recover捕获异常,然后正常处理。
-
panic
1、内置函数
2、假如函数F中书写了panic语句,会终止其后要执行的代码,在panic所在函数F内如果存在要执行的defer函数列表,按照defer的逆序执行
3、返回函数F的调用者G,在G中,调用函数F语句之后的代码不会执行,假如函数G中存在要执行的defer函数列表,按照defer的逆序执行
4、直到goroutine整个退出,并报告错误
-
recover:
1.内置函数
2、用来控制一个goroutine的panicking行为,捕获panic,从而影响应用的行为
3、一般的调用建议
a). 在defer函数中,通过recever来终止一个goroutine的panicking过程,从而恢复正常代码的执行
b). 可以获取通过panic传递的error
-
注意
1.利用recover处理panic指令,defer 必须放在 panic 之前定义,另外 recover 只有在 defer 调用的函数中才有效。否则当panic时,recover无法捕获到panic,无法防止panic扩散。
2.recover 处理异常后,逻辑并不会恢复到 panic 那个点去,函数跑到 defer 之后的那个点。
3.多个 defer 会形成 defer 栈,后定义的 defer 语句会被最先调用。
-
demo
package main
func main() {
test()
}
func test() {
defer func() {
if err:=recover();err != nil {
// fmt.Printf("%T\n",err)
println(err.(string))// 将 interface{} 转型为具体类型。
}
}()
panic("panic error!")
}
// panic error!
-
panic,recover 参数类型为interface{} ,因此可抛出任何类型对象。
func panic(v interface{})
func recover() interface{}
-
向已经关闭的通道发送数据会引发panic
func main() {
defer func() {
// recover捕获异常
if err := recover(); err != nil {
// 打印异常
fmt.Println(err)//send on closed channel
}
}()
var ch chan int = make(chan int,10)
// 关闭channel
close(ch)
// 向channel 传值
ch <- 1
}
-
延迟调用中引发的错误,可被后续延迟调用捕获,但仅最后一个错误可被捕获。
func test() {
// 捕获最后一个错误。
defer func() {
fmt.Println(recover())//defer panic
}()
defer func() {
panic("defer panic")
}()
panic("test panic")
}
-
捕获函数 recover 只有在延迟调用内直接调用才会终止错误,否则总是返回 nil。任何未捕获的错误都会沿调用堆栈向外传递。
func test() {
defer func() {
fmt.Println("-->",recover()) //无效
}()
defer recover() //无效!
defer fmt.Println(recover()) //无效!
defer func() {
func() {
println("defer inner")
recover() //无效!
}()
}()
defer func() {
fmt.Println("---->",recover()) //有效
}()
panic("test panic")
}
// ----> test panic
// defer inner
// <nil>
// --> <nil>
-
使用延迟匿名函数或下面这样都是有效的。
package main
import (
"fmt"
)
func except() {
fmt.Println(recover())//test panic
}
func test() {
defer except()
panic("test panic")
}
func main() {
test()
}
-
如果需要保护代码 段,可将代码块重构成匿名函数,如此可确保后续代码被执
package main
import "fmt"
func test(x,y int) {
var z int
func() {
// 捕获异常
defer func() {
if recover() != nil{
z = 0
}
}()
// 触发异常
panic("test panic")
z = x/y
return
}()
fmt.Printf("x / y = %d\n", z)//x / y = 0
}
func main() {
test(1,2)
}
-
除用 panic 引发中断性错误外,还可返回 error 类型错误对象来表示函数调用状态。
type error interface {
Error() string
}
-
标准库 errors.New 和 fmt.Errorf 函数用于创建实现 error 接口的错误对象。通过判断错误对象实例来确定具体错误类型。
package main
import (
"fmt"
"errors"
)
// 定义异常对象
var ErrDivZero = errors.New("division by zero")
func div(x,y int) (int, error) {
if y == 0{
return 0,ErrDivZero
}
return x/y,nil
}
func main() {
defer func(){
fmt.Println(recover())//division by zero
}()
switch z,err := div(10,0);err {
case nil:
println(z)
case ErrDivZero:
panic(err)
}
}
-
Go实现try catch的异常
package main
import "fmt"
func Try(fun func(),handler func(interface{})) {
defer func() {
// 执行defer,通过recover捕获异常赋值给err.
// 执行第二个参数的函数,将err传递。
if err:= recover();err!=nil{
handler(err)
}
}()
// 执行第一个参数的函数,触发异常
fun()
}
func main() {
// 传入匿名函数:
// 1.函数定义触发错误
// 2.函数用于打印异常
Try(func() {
panic("test panic")
},func(err interface{}){
fmt.Println(err)// test panic
})
}
如何区别使用 panic 和 error 两种方式?
惯例是:导致关键流程出现不可修复性错误的使用 panic,其他使用 error。
|
请发表评论