4、Golang基础-- 接口基础、接口高级、并发和并行、Go协程、信道、缓冲信道、WaitGroup、defer的使用、panic和recover

1 接口基础

# 接口约束结构体的行为(方法),接口是一系列方法的集合,接口也是一种类型(特殊类型)

# 接口变量.(Dog)--->类型断言
# 接口变量.(type)--->类型选择,配合switch使用
package main

import "fmt"

// 1 接口的实际用途

// Animal 接口定义
type Animal interface {
	eat()
	sleep()
}

// 只要结构体实现了接口中的所有方法,就叫实现该接口
// 如果没有实现接口中的所有方法,就不叫实现该接口
// go 语言也是鸭子类型
// java中:实现接口,必须显示的声明,并且必须实现所有方法
// python,go中,实现接口,不需要显示声明,只要实现了所有方法,就叫实现接口,这种操作叫鸭子类型

// Dog 定义结构体与绑定对应方法
type Dog struct {
	name string
	age  int
}

func (dog Dog) WatchHouse() { fmt.Println("狗看家功能") }
func (dog Dog) eat()        { fmt.Println("狗吃东西") }
func (dog Dog) sleep()      { fmt.Println("狗睡觉") }

// Cat 定义结构体和绑定方法
type Cat struct {
	name string
	age  int
}

func (cat Cat) CatchMouse() { fmt.Println("猫抓老鼠") }
func (cat Cat) eat()        { fmt.Println("猫吃东西") }
func (cat Cat) sleep()      { fmt.Println("猫睡觉") }

// Cat和Dog都实现了Animal接口,所以Cat和Dog都属于Animal这个类型

// goToSleep 定义一个普通函数
func goToSleep(a Animal) {
	a.sleep() // 只要是Animal类型就都有eat、sleep功能
}

func main() {
	var cat1 = Cat{"cat1", 1}
	var dog1 = Dog{"dog1", 1}
	var animal1 Animal
	// 因为Cat和Dog都属于Animal这个类型(实现了Animal接口),所以给animal1赋值可以赋cat1或者dog1
	animal1 = cat1
	fmt.Println(animal1) // {cat1 1}
	animal1 = dog1
	fmt.Println(animal1) // {dog1 1}

	goToSleep(cat1)    // 参数为Animal类型、cat1也是Animal类型  打印猫睡觉
	goToSleep(dog1)    // 参数为Animal类型、dog1也是Animal类型  打印狗睡觉
	goToSleep(animal1) // 参数为Animal类型、animal1也是Animal类型  打印狗睡觉
}

package main

import "fmt"

// 1 接口的实际用途

// Animal 接口定义
type Animal interface {
	eat()
	sleep()
}

// 只要结构体实现了接口中的所有方法,就叫实现该接口
// 如果没有实现接口中的所有方法,就不叫实现该接口
// go 语言也是鸭子类型
// java中:实现接口,必须显示的声明,并且必须实现所有方法
// python,go中,实现接口,不需要显示声明,只要实现了所有方法,就叫实现接口,这种操作叫鸭子类型

// Dog 定义结构体与绑定对应方法
type Dog struct {
	name string
	age  int
}
func (dog Dog) WatchHouse() { fmt.Println("狗看家功能") }
func (dog Dog) eat()        { fmt.Println("狗吃东西") }
func (dog Dog) sleep()      { fmt.Println("狗睡觉") }

// Cat 定义结构体和绑定方法
type Cat struct {
	name string
	age  int
}
func (cat Cat) CatchMouse() { fmt.Println("猫抓老鼠") }
func (cat Cat) eat()        { fmt.Println("猫吃东西") }
func (cat Cat) sleep()      { fmt.Println("猫睡觉") }

// Cat和Dog都实现了Animal接口,所以Cat和Dog都属于Animal这个类型

// goToSleep 定义一个普通函数
func goToSleep(a Animal) {
	a.sleep() // 只要是Animal类型就都有eat、sleep功能
}

// 2 空接口:即不要求满足任何功能就实现了空接口,所以所有类型都属于空接口

// Empty 定义空接口
type Empty interface{}
func myPrint(a Empty) {fmt.Println(a)}  // 定义一个普通函数

// 3 匿名空接口
func myPrint2(a interface{}) {fmt.Println(a)}  // 简写myPrint函数

// 4 类型断言
func testDog (a Animal) {
	dog := a.(Dog)  // 类型断言 如果a不是Dog类型就断言失败
	fmt.Println(dog)
}
func testDog2 (a Animal) {
	dog,ok := a.(Dog)  // 类型断言 两个变量接收,断言成功返回调用的具体实参和true,否则返回具体实参的零值和false
	fmt.Println(dog,ok)
}
func testDog3(a Animal) {
	dog,ok := a.(Dog)
	if ok{
		dog.WatchHouse()  // 断言成功返回的dog能调用Dog类型绑定的的所有方法
	}
}
func testDog4(a Animal) {
	if dog,ok:=a.(Dog);ok{  // testDog3的简写,if判断后的条件前可以加执行语句用分号隔开
		dog.WatchHouse()
	}
}

// 5 类型选择
func testDog5(i interface{}) {
	switch obj:=i.(type) {
	case Dog:
		obj.WatchHouse()
	case Cat:
		obj.CatchMouse()
	case int:
		fmt.Println("这是int类型")
	case string:
		fmt.Println("这是string类型")
	default:
		fmt.Println("其他类型")
	}
}

func main() {
	// 1 接口的实际应用
	var cat1 = Cat{"cat1", 1}
	var dog1 = Dog{"dog1", 1}
	var animal1 Animal
	// 因为Cat和Dog都属于Animal这个类型(实现了Animal接口),所以给animal1赋值可以赋cat1或者dog1
	animal1 = cat1
	fmt.Println(animal1) // {cat1 1}
	animal1 = dog1
	fmt.Println(animal1) // {dog1 1}

	goToSleep(cat1)    // 参数为Animal类型、cat1也是Animal类型  打印猫睡觉
	goToSleep(dog1)    // 参数为Animal类型、dog1也是Animal类型  打印狗睡觉
	goToSleep(animal1) // 参数为Animal类型、animal1也是Animal类型  打印狗睡觉

	// 2 空接口的应用
	myPrint(10)  // 10
	myPrint("cx")  // cx
	var a2 [3]int
	myPrint(a2)  // [0 0 0]
	// 总结:函数的参数类型为空接口类型时,可以传任意类型,因为所有类型都实现了空接口,属于空接口类型
	var i2 interface{}
	i2 = 0
	fmt.Println(i2)  // 0
	i2 = "cx"
	fmt.Println(i2)  // cx
	// 总结: 变量为空接口类型时,可以给该变量赋任意类型值

	// 4 类型断言
	testDog(dog1)  // {dog1 1}
	//testDog(cat1)  // 报错  因为断言失败

	testDog2(dog1)  // {dog1 1} true   dog1和true
	testDog2(cat1)  // { 0} false  cat1的零值和false

	testDog3(dog1)  // 狗看家功能
	testDog3(cat1)  // if判断失败,后面代码没执行

	// 5 类型选择: 用switch case做类型断言
	testDog5(cat1)  // 猫抓老鼠
	testDog5(dog1)  // 狗看家功能
	testDog5(100)  // 这是int类型
	testDog5("cx")  // 这是string类型
	testDog5([3]int{1,2,3})  // 其他类型

	// 6 接口的零值
	var animal2 Animal1
	fmt.Println(animal2)

	// 7 接口的底层实现:引用:  {具体类型(Dog), 指针(指向具体的类型}
}

2 接口高级

package main

import "fmt"

// 1 实现接口:也就是实现接口对应方法,有两种:值接收者、指针接收者

// Animal1 定义接口
type Animal1 interface {
	eat()
}

// Duck 结构体定义与绑定方法
type Duck struct {
	name string
}
func (duck Duck) eat() {
	fmt.Println("鸭子吃", duck)
}

// Chicken 结构体定义与绑定方法
type Chicken struct {
	name string
}
func (chicken *Chicken) eat() {
	fmt.Println("鸡吃", chicken)
}


// 2 接口嵌套

// Animal2 接口定义
type Animal2 interface {
	eat()
}
// Duck2 接口嵌套的定义
type Duck2 interface {  // 需要实现所有方法才算实现接口、包括Animal2方法
	run()
	speak()
	Animal2  // 等同于把Animal2的所有方法放在了这
}


// 3 同时实现多个接口

// Animal3 接口定义
type Animal3 interface {
	eat()
}
// Duck3 接口嵌套的定义
type Duck3 interface {  // 需要实现所有方法才算实现接口、包括Animal2方法
	run()
	speak()
	Animal3  // 等同于把Animal2的所有方法放在了这
}

// tDuck 结构体定义与方法绑定  同时实现Duck3、Animal3接口
type tDuck struct {
	name string
	age int
}
func (duck tDuck) eat()   {fmt.Println("duck eat")}
func (duck tDuck) speak() {fmt.Println("duck speak")}
func (duck tDuck) run()   {fmt.Println("duck run")}


func main() {
	// 1 值接收者、指针接收者
	duck := Duck{"鸭子"}
	chicken := Chicken{"鸡"}
	duck.eat()  // 值接收者,值类型调用时将duck复制传入  鸭子吃 {鸭子}
	chicken.eat()  // 指针接收者,值类型调用时将chicken的地址传入  鸡吃 &{鸡}

	duck1 := &Duck{"鸭子"}
	chicken1 := &Chicken{"鸡"}
	duck1.eat()  // 值接收者,指针类型调用时将指针对应值复制传入 鸭子吃 {鸭子}
	chicken1.eat()  // 指针接收者,指针类型调用时将指针复制传入  鸡吃 &{鸡}

	var animal1 Animal1 = duck
	animal1.eat()  // 调用的是duck的值接收者  鸭子吃 {鸭子}
	var animal11 Animal1 = &duck
	animal11.eat()  // 调用的duck的值接收者  鸭子吃 {鸭子}

	//var animal2 Animal1 = chicken  // 报错 如果是用指针接收者实现的Animal1接口,那么只能传指针给Animal1接口
	var animal2 Animal1 = &chicken
	animal2.eat()  // 鸡吃 &{鸡}
	// 总结:
	//	 1、如果用值接收者实现接口,可以将指针或者值赋值给接口类型
	//	 2、如果用指针接收者实现接口,只能把指针赋值给接口类型


	// 3 实现多接口
	var duck3 = tDuck{"t_duck",1}
	var animal3 Animal3 = duck3
	var duckInt Duck3 = duck3  // duck3 既可以赋值给Animal3也可以赋值给Duck3类型
	fmt.Println(animal3, duckInt)  // {t_duck 1} {t_duck 1}
	// 注意:
	//    duck3可以使用eat run speak
	//    animal3可以使用eat(如果要使用其他两种方式、可以使用类型断言)
	//    duckInt可以使用ear run speak
}

3 并发和并行

# 并发:并发是指立即处理多个任务的能力,在一个时间段内,可以进行多个任务(干多个活),单核cpu,多个任务执行,只能每个任务执行一会,这就是并发
# 并行:并行指的是同时处理多个任务,在同一时刻,可以进行多个任务,必须有多个cpu的支持


# 只针对于Python语言,cpython 解释器,GIL锁,同一时刻,只能有一个线程执行
# Python如果是IO密集型,开启线程
# Python如果是计算密集型,开启进程,多个进程中的线程可以运行在多个cpu上

# 但是到了go语言,不存在这个问题,go中开多个线程,能够运行在多个cpu上,能够利用多核优势

# 并且go没有开启线程和进程的代码,咱们开启的统统叫做协程,可以实现单线程下的并发---》go开启的协程本质是:线程池+协程

# 开启多个线程,如果遇到io操作,线程会切换,但是是操作系统层面切换,比较消耗资源
# 如果一个线程下,开启多个协程,协程的切换,是程序(代码)控制的,不消耗过多资源
# 同种情况下,协程更节约资源(针对的是IO密集型)

4 go协程

package main

import (
	"fmt"
	"time"
)

// Go协程(线程池+协程)  goroutine

func printHello()  {
	fmt.Println("hello")
}

------------------------------------------
func main() {
	fmt.Println("程序开始了")
	printHello()
	fmt.Println("程序结束了")
	
	// 1 开启goroutine
	fmt.Println("程序开始了")
	go printHello()  // 开启协程
	go printHello()  // 开启协程
	time.Sleep(1*time.Second)  // 只传1是1纳秒,等一秒是为了让协程执行打印hello完毕,否则执行主程序完毕后可能协程还没有执行完毕。此时开启的协程将会被关闭
	fmt.Println("程序结束了")
}


------------------------------------------
// 2 同时开启多个goroutine,尽管用,开上万个都没问题
func main() {
	fmt.Println("程序开始了")

	go printHello()
	go printHello()
	go printHello()
    ...
    ...
	time.Sleep(1*time.Second)
	fmt.Println("程序结束了")
}


------------------------------------------
// 开启多个goroutine,可以利用多核优势

func loop_dead(name string)  {  // 死循环函数
	for  {  
		fmt.Println(name)
	}
}

func main() {
	// goroutine 能够利用多核优势
	// python 如果想利用多核优势,必须开多进程
	// 8核cpu,开一个死循环,只能占住1核,cpu的使用率,只占八分之一
	// 如果使用python 开启多个线程,无论开多少个,如果是8核cpu,都只占八分之一
	go loop_dead("cx1")
	go loop_dead("cx2")
	go loop_dead("cx3")
	go loop_dead("cx4")
	go loop_dead("cx5")
	go loop_dead("cx6")
}

----------------------------------------
/*
补充:
	在main或其他函数中开协程,主程序结束了,协程还继续执行吗?

	结论:main函数中的协程,如果main结束了,协程也会结束

	其他函数里的协程,函数结束了,只要main没结束,协程就会执行。
*/

5 信道

package main
import (
	"fmt"
	"time"
)
// 1 信道的快速使用


// 信道:信道可以想象成Go协程之间通信的管道。如同管道中的水会从一端流到另一端
//     通过信道,数据也可以从一端发送,在另一端接收
//     信道也是一个变量

func go1(a chan bool) {
	fmt.Println("其他goroutine开始")
	time.Sleep(time.Second * 2)
	a <- true // 向a信道放true
	fmt.Println("其他goroutine结束")

}

func main() {
	// 1 信道的快速使用
	var c chan bool = make(chan bool) // 简写:var c = make(chan bool)
	fmt.Println("主的goroutine开始")
	go go1(c)  // 将信道c传入协程

	// 阻塞,等接收到值后再向下运行。通过信道 可以实现使go协程完成后主协程再继续运行
	b := <-c   // 从c信道取值(信道在<-后面就是取值,在<-前面就是放值),没有值接收不会报错,代表不要了
	fmt.Println("主的goroutine结束,接收了b为:", b)

	// 1.2 定义信道
	var c2 chan interface{}
	fmt.Println(c2)  // <nil>

	var c21 = make(chan interface{})
	fmt.Println(c21)  // 0xc00008c000  是一个内存地址

	// 1.3 信道的取值和赋值、默认都是阻塞的
	//    - 有go协程发送值,就必须有go协程接受值,否则报死锁
	//<- c21  // - deadlock!  报错 因为信道没放值,取不到,报死锁错误
}
package main
import "fmt"

// 2 案例: 有一个数字,计算这个数字每一位的平方和和立方和
//同步写法

func calcSquares(number int,a chan int)  {  // 345
	total:=0
	for number!=0{
		digit := number % 10  // 对10取余数  5   4  3
		total+=digit*digit       //5*5+4*4+3*3
		number=number/10    // 34  3  简写:number/=10
	}
	a<-total
}
func calcCubes(number int,b chan int) {  // 345
	total:=0
	for number!=0{
		digit := number % 10  // 对10取余数  5   4  3
		total+=digit*digit*digit       //5*5+4*4+3*3
		number=number/10    // 34  3  简写:number/=10
	}
	b<-total
}

func main() {
	number:=889   //1962     同步计算
	var a =make(chan int)
	var b =make(chan int)
	go calcSquares(number,a)  // 算完平方和假如: 5s
	go calcCubes(number,b)  // 算完立方和假如: 7s
    
	// 阻塞,直到两个信道的值都取到  
	res1, res2 := <-a, <-b  // 因为goroutine可以利用多核优势,所以两个都接收到得时间接近算完立方和的时间 7s
	fmt.Println(res1+res2)  // 1962
}
package main
import "fmt"

// 3 定义一个信道,要么只能写,要么只能放

func task1(c chan<- int) {
	// 在这个任务中,信道只能放值,不能取值
	c <- 9
}
func main() {
	c := make(chan int)
	go task1(c)
	<-c
}

package main
import "fmt"

// 4 信道的循环

func producer(chnl chan int) {
	for i := 0; i < 10; i++ {
		chnl <- i
		fmt.Println("放入", i)
	}
	close(chnl) //关闭信道
}

func main() {
	// 1 使用死循环
	ch := make(chan int)
	go producer(ch)
	for {
		v, ok := <-ch   // 信道取值,如果取不到,且没有关闭,ok就是true,如果关闭了,ok就是false
		if ok == false {
			break
		}
		fmt.Println("Received ", v, ok)
	}

	// 2 使用range循环信道,如果信道关闭,range循环也就结束了
	go producer(ch)
	for v := range ch { // 一旦信道被关闭,for循环就结束了
		fmt.Println("取出: ", v)
	}
}

4 缓冲信道

package main

import "fmt"

// 有缓冲信道

func main() {
	//var a =make(chan int,0)
	//a<-999   // 无缓冲信道,放不进去,必须有另一个goroutine接收,才不会报错

	// 1 定义有缓冲信道,缓冲信道为3
	var a1 =make(chan int,3)  // 信道最多能放3个int值
	a1<-999
	a1<-888
	a1<-777
	//a1<-666   // 报死锁

	fmt.Println(a1)   // 0xc000108080 信道类似管道,先进先出
	<-a1    // 999
	<-a1    //888
	fmt.Println(<-a1)   //777
	//fmt.Println(<-a1)   // 也会死锁
	// 最开始学的是的无缓冲信道,相当于有缓冲信道数字是0


	// 2 长度和容量

	// 长度:目前信道有几个值
	// 容量:信道最大能够放多少,初始化的时候,数字决定,没有扩容这一说
	var c =make(chan int)
	fmt.Println(len(c))  // 0
	fmt.Println(cap(c))  // 0
	// 无缓冲信道,长度容量都是0
	
	var c2 =make(chan int,3)
	fmt.Println(len(c2))   // 0
	fmt.Println(cap(c2))  //3
	c2<-999
	c2<-888
	fmt.Println(len(c2))   // 2
	fmt.Println(cap(c2))  //3
	c2<-888   // 长度和容量一样
	//c2<-888   // 报错

	// 实际生产中运用:信道中可以传各种类型数据
	var c3 =make(chan entity.Person,3)
	c3<-entity.NewPerson("lqz",19,"lyf")
	var p entity.Person=<-c3
	fmt.Println(p)
}


5 WaitGroup

package main

import (
	"fmt"
	"sync"
)

// WaitGroup 是一个类型,开了很多goroutine,主的goroutine,等待所有goroutine执行完,主的再继续执行

// 1 使用信道实现多个goroutine执行完成,主goroutine再继续执行
func task3(s string,c chan  int)  {
	fmt.Println("打印",s)
	c<-999
}
func main() {
	fmt.Println("主开始了")
	var c =make(chan int,3)
	go task3("lqz",c)
	go task3("111",c)
	go task3("222",c)

	for i:=0;i<cap(c);i++{
		<-c
	}
	fmt.Println("主结束了")
}



// 2 使用WaitGroup 实现等待多个任务完成
func task3(s string, wg *sync.WaitGroup) {
	fmt.Println(s)
	wg.Done() // 其中一个任务完成
}
func main() {
	// 1 简单版
	fmt.Println("主开始了")
	var wg sync.WaitGroup // 值类型,不用初始化就有默认零值;当参数传递,如果改原值,需要取地址
	fmt.Println(wg)       // {{} [0 0 0]}
	go task3("lqz", &wg)
	go task3("111", &wg)
	go task3("222", &wg)
	wg.Add(3) // 表明有3个任务

	wg.Wait() // 等待3个任务完成
	fmt.Println("主结束了")

	// 2 实用版
	fmt.Println("主开始了")
	var wg2 sync.WaitGroup
	for i := 0; i < 5; i++ {  // 每开一个goroutine add一个
		go task3("ssss", &wg2) // 数字和字符串的相互转换  string(1)  int("1") 都不行
		//go task3(i,&wg2)
		wg2.Add(1)
	}
	wg2.Wait()
	fmt.Println("主结束了")
}

7 defer的使用

package main

import "fmt"

// defer的使用---》注册代码,延迟调用,等函数执行完后,按注册顺序倒序执行defer,先注册的,最后执行


// 如果打开一个文件,最后要关闭,一打开文件,立马用defer注册关闭
--------------------------------------------
func main() {  // 顺序:开始 结束 3 2 1
	fmt.Println("我开始执行了")
	defer fmt.Println("我是defer注册的代码1")
	defer fmt.Println("我是defer注册的代码2")
	defer fmt.Println("我是defer注册的代码3")
	fmt.Println("我执行结束了")
}

--------------------------------------------
func main() {  // 顺序:开始 结束 10
	fmt.Println("我开始执行了")
	var a int=10
	defer func(i int) {
		fmt.Println("我是匿名函数的执行")
		fmt.Println(i)
	}(a)  // 已经传入,但是没执行,copy传递,复制了一份a=10传入了

	a++  // a 自增1
	fmt.Println("我执行结束了")
}

--------------------------------------------
func main() {  // 顺序:开始 结束 11
	fmt.Println("我开始执行了")
	var a int=10
	defer func() {
		fmt.Println("我是匿名函数的执行")
		fmt.Println(a)  // 对外部的引用 , 打印出11
	}()

	a++  // a 自增1
	fmt.Println("我执行结束了")
}

--------------------------------------------
func main() {  // 顺序:开始 结束 [999,7,8]
	fmt.Println("我开始执行了")
	var a =[]int{6,7,8}
	defer func() {
		fmt.Println("我是匿名函数的执行")
		fmt.Println(a)  // 对外部的引用 ,  [999,7,8]
	}()
	a[0]=999
	fmt.Println("我执行结束了")
}

--------------------------------------------
func main() {  顺序:开始 结束 [999,7,8]
	fmt.Println("我开始执行了")
	var a =[]int{6,7,8}
	defer func(s []int) {
		fmt.Println("我是匿名函数的执行")
		fmt.Println(s)  //   虽然是copy传递,但是s是引用,改了会影响原来的[999,7,8]
	}(a)
	a[0]=999
	fmt.Println("我执行结束了")
}


--------------------------------------------
func main() {  // 顺序:开始 结束 循环第0123456789 10 0
	fmt.Println("我开始执行了")
	i:=0
	defer fmt.Println("普通目前i的值是:",i) //  0
             
	defer func() {  // 对外部的引用
		fmt.Println("函数内目前i的值是:",i)  //10
	}()
             
	defer func() {  // 对外部的引用
		for ;i<10;i++{
			fmt.Println("循环第:",i)
		}
	}()
	fmt.Println("我执行结束了")
}

8 panic和recover

package main

import "fmt"

// defer :  先注册,后执行,即便程序报了panic的错误,也会执行
// panic :  跟python中raise,java中throw是一样的,主动抛出异常
// recover: 恢复程序,继续执行

func main() {
	fmt.Println("主开始了")
	panic("我出错了")
	fmt.Println("主结束了") // 永远不会被执行到
}

----------------------------------
// panic 主动抛出异常
func test8()  {
	fmt.Println("我是test8")
	panic("我出错了")
	fmt.Println("我是test8,我执行完了")
}
func main() {
	fmt.Println("主开始了")
	test8()
	fmt.Println("主结束了") // 永远不会被执行到
}


----------------------------------
func test8(s []int)  {
	fmt.Println(s)
	fmt.Println(s[9]) // 越界,报错,抛异常,程序抛出的
}
func main() {
	var s=[]int{7,8,9}
	test8(s)
	fmt.Println("我结束了")  // 不会被执行
}


----------------------------------
// 程序出异常,捕获异常,处理异常,程序继续执行
func test8(s []int)  {
	defer func() {  // 即使报错后也会执行,所以报错后可以恢复、但报错后代码不会运行
		recover()
	}()
	fmt.Println(s)
	fmt.Println(s[9]) // 越界,报错,抛异常,程序抛出的
}
func main() {
		var s=[]int{7,8,9}
		test8(s)
		fmt.Println("我结束了")  // 会运行
}


----------------------------------
/* 
Python异常处理:
try:
	f2()
except Exception as e:
	print(e)
finally:
	print(无论是否出错,都会执行)
 */

// go 语言中异常处理
func main() {
	fmt.Println("我开始了")
	f1()
	f2() // 知道f2会异常,我想后面的继续执行
	f3()
	fmt.Println("我结束了")
}

func f1() {
	fmt.Println("我是f1")
}
func f2() {
	defer func() {
		//recover()  // 恢复程序,如果有错误,会返回错误对象,如果没有错误,会返回nil
		if err:=recover();err!=nil{ // 说明出错了
			fmt.Println(err) // 打印错误
		}
		fmt.Println("这是其他语言finally的代码")
	}()
	//panic("我主动抛出")
	//var a =[]int{9,8,7}
	//fmt.Println(a[9])
	fmt.Println("我是f2")
}
func f3() {
	fmt.Println("我是f3")
}

----------------------------------
// 总结:go 中异常捕获:把这段代码,放到可能会出错代码的上方
defer func() {
	if err:=recover();err!=nil{
		// except的代码
	}
	//finally的代码
}()

posted @ 2022-03-05 23:57  简爱cx  阅读(69)  评论(0编辑  收藏  举报