狂神说Go语言—Go基础语法(最详细笔记---下)全

管理员

结构体

结构体是由一系列具有相同类型或不同类型的变量数据构成的数据集合我们一般用来封装一类事务的变量,结构体可以理解为一堆变量的集合,结构体表示一项记录,比如保存人的信息,每个人都有以下属性:

  • Name:名字
  • Age:年龄
  • Sex:性别

定义结构体

结构体定义需要使用type和struct语句

struct语句定义了一个新的数据类型,结构体中有一个或多个成员

type语句设定了结构体的名称

package main

import "fmt"

/*
1、创建方式一:定义一个结构体 User  字段 name、age、sex
2、创建一个user对象并赋值
3、创建方式二:快捷创建
4、创建方式三:创建的时候进行赋值操作
*/

// 1、创建方式一:定义一个结构体 User  字段 name、age、sex
type User struct {
	name string //姓名
	age  int    //年龄
	sex  string //性别
}

func main() {
	//2、创建一个user对象并赋值
	var user1 User
	fmt.Println(user1)
	user1.name = "kuangshenshuo"
	user1.age = 18
	user1.sex = "男"
	//可以单个提取
	fmt.Println(user1.name)
	
	fmt.Println(user1)
	
	//3、创建方式二:快捷创建
	user2 := User{}
	user2.name = "feige"
	user2.sex = "男"
	user2.age = 30
	fmt.Println(user2)

	//4、创建方式三:创建的时候进行赋值操作
	user3 := User{"fiege2", 18, "男"}
	fmt.Println(user3)
}

/*
{ 0 }
{kuangshenshuo 18 男}
kuangshenshuo
{feige 30 男}
{fiege2 18 男}
*/

结构体指针

结构体是值类型的

使用内置函数new()创建,new的所有Type都返回指针

user1:=new(Users)

package main

import "fmt"

/*
1、结构体是值类型
2、指针对象
3、new关键字创建也是引用类型
*/
type User2 struct {
	name string
	age  int
	sex  string
}

func main() {
	//1、结构体是值类型
	user := User2{"kuangshen", 18, "男"}
	user2 := user
	user2.name = "zhangsan"
	fmt.Println(user) //将传递后的对象重新赋值之后打印输出,结果未改变,由此得出结构体是值类型

	//2、指针对象
	var user3 *User2
	user3 = &user
	user3.name = "lisi"
	fmt.Println(user) //因为指针是引用类型

	//3、new关键字创建也是引用类型
	user4 := new(User2)
	user4 = &user
	user4.name = "wangwu"
	fmt.Println(user)

}

/*
{kuangshen 18 男}
{lisi 18 男}
{wangwu 18 男}
*/

匿名结构体

匿名结构体:没有名字的结构体

匿名字段:一个结构体的字段没有字段名

package main

import "fmt"


/*
1、定义一个Student结构体 打印输出
2、定义匿名结构体 打印输出
3、定义Teacher结构体,匿名字段 打印输出
*/

//1、定义一个Student结构体 打印输出
type Student struct {
	name string
	age  int
}

//3、定义Teacher结构体,匿名字段 打印输出
type Teacher struct {
	string
	int
}

func main() {
	s1 := Student{"kuangshen", 18}
	fmt.Println(s1)

	//2、定义匿名结构体 打印输出
	s2 := struct {
		name string
		age  int
	}{"秦疆", 18}
	fmt.Println(s2)

	//	匿名字段
	t1 := Teacher{"feige", 3}
	fmt.Println(t1)
}
/*
{kuangshen 18}
{秦疆 18}
{feige 3}
*/

结构体嵌套

一个结构体可能包含一个字段,这个字段又是一个结构体,这被称为结构体嵌套

package main

import "fmt"

/*
结构体嵌套
1、定义Address结构体
2、定义Person结构体,将创建Address的对象添加
3、调用Person结构体,打印输出
*/

//2、定义Person结构体,将创建Address的对象添加
type Person struct {
	name    string
	age     int
	address Address //结构体嵌套
}
//1、定义Address结构体
type Address struct {
	city, state string
}

func main() {
	//3、调用Person结构体,打印输出
	pesoon1 := Person{"kuangshen", 18, Address{"中国", "广州"}}
	fmt.Println(pesoon1)
}
//{kuangshen 18 {中国 广州}}

结构体导出

如果结构体名称首字母小写,则结构体不会被导出。这时,即使结构体成员字段名首字母大写也不会导出

如果结构体首字母大写,则结构体有可能导出,只会导出大写首字母的成员字段,那些小写首字母的成员字段不会被导出

如果存在嵌套结构体,即使嵌套在内层的结构体名称首字母小写,外部也能访问到其中首字母大写的成员字段

这里自己出现一个小插曲,导包问题!

  • 直接在main方法中使用,系统会自动帮忙导入对应包,而自己手动导入,只要不使用就会消失
package main

import (
   "awesomeProject/pojo"
   "fmt"
)

func main() {
   var user pojo.User
   user.Name = "kuangshen"
   user.Money = 100
   fmt.Println(user)
}
//{kuangshen 0 100}

面向对象编程

OOP思想

Go语言不是面向对象语言,只是让大家理解一些面向对象的思想,通过一些方法来模拟面向对象

语言的进化发展跟生物的进化发展是一回事,都是"物以类聚"

语句多了,我们将完成同样功能相近的语句,聚到一块,便于我们使用,于是,函数出现了

变量多了,我们将功能相近的变量组在一起,聚到一起归类,便于我们调用。于是,结构体出现了

再后来,方法多了,变量多了!结构体不够用!我们就将功能相近的变量和方法聚到了一起,于是类和对象就出现了

企业的发展也是"物以类聚"的过程,完成市场维护的人员聚到了一起形成了市场部。完成技术开发的人员聚到了一起形成了开发部

面向对象的思维模式

面向对象的思维模式是简单的线性思维,思考问题首先陷入第一步做什么,第二步做什么的细节中,这种思维模式适合处理简单的事情,比如:上厕所

如果面对复杂的事情,这种思维模式会陷入令人发疯的状态!比如:如何造火箭!

面向对象的思维模式

面向对象的思维模式说白了就是分类思维模式,首先思考解决问题,需要哪些分类,然后对这些分类进行单独思考。最后,才对某个分类下的细节进行面向过程的思索

这样就可以形成很好的协作分工。比如:架构师分了10个类,然后将10个类交给了10个人分别进行详细设计和编码!

显然,面向对象适合处理复杂的问题,适合处理需要多人协作的问题!

如果一个问题需要多人协作一起解决,那么你一定要用面向对象的方式来思考!

对于描述复杂的事物,为了从宏观上把握、从整体上合理分析,我们需要使用面向对象的思路来分析整个系统。但,仍然需要面向过程的思路去处理。
面向对象的三大特点:封装、继承、多态I

继承

兔子和羊属于食草动物类,狮子和豹属于食肉动物类。

食草动物和食肉动物又是属于动物类。
所以继承需要符合的关系是: is-a,父类更通用,子类更具体。
虽然食草动物和食肉动物都是属于动物,但是两者的属性和行为上有差别,所以子类会具有父类的一般特性也会具有自身的特性。
继承就是子类继承父类的特征和行为,使得子类具有父类的属性和方法,使得子类具有父类相同的行为。子类会具有父类的一般特性也会具有自身的特性。

package main

import "fmt"

/*
1、父类结构体Person
2、子类结构体Student、创建一个父类匿名结构体对象
3、创建父类的对象
4、创建子类的对象
*/


//1、父类结构体Person
type Person struct {
	name string
	age  int
}
//2、子类结构体Student、创建一个父类匿名结构体对象
type Student struct {
	Person //匿名结构体
	school string
}

func main() {
	//3、创建父类的对象
	person := Person{"kuangshen", 18}
	fmt.Println(person)

	//4、创建子类的对象
	student := Student{Person{"feige", 35}, "清华"}
	fmt.Println(student)
}
/*
{kuangshen 18}
{{feige 35} 清华}
*/

方法讲解

什么是方法

Go语言中同时拥有函数和方法

  • 方法:
    • 某个类别的行为功能。需要指定接收者调用
    • 一段独立的功能代码,可以直接调用
  • 函数
    • 一段独立功能的代码,可以直接调用
    • 命名不能冲突
package main

import "fmt"

/*
1、定义一个Dog、cat结构体
2、定义方法接收Dog、cat类型
3、调用方法输出
*/

// 1、定义一个Dog、cat结构体
type Dog struct {
	name string
	age  int
}
type Cat struct {
	name string
	age  int
}

// eat方法接收者为Dog类型 只有Dog类型的对象才能调用方法
func (dog Dog) eat() {
	fmt.Printf("%s...eatn", dog.name)
}
func (dog Dog) sleep() {
	fmt.Printf("%s...sleepn", dog.name)
}

// sleep方法接收者为cat类型 只有cat类型的对象才能调用方法
func (cat Cat) sleep() {
	fmt.Printf("%s...sleepn", cat.name)
}
func main() {
	//2、定义方法接收Dog、cat类型
	dog := Dog{"二哈", 3}
	cat := Cat{"喵喵", 2}
	//3、调用方法输出
	dog.eat()
	dog.sleep()
	cat.sleep()
}

/*
二哈...eat
二哈...sleep
喵喵...sleep
*/

方法重写

子类可以重写父类的方法override

子类可以新增自己的属性和方法

子类可以直接访问父类的属性和方法

package main

import "fmt"
/*
1、定义Animal、Dog2、Cat2结构体;	Dog2和Cat2继承Animal
2、定义Animal的方法;重写Dog2、cat2方法
3、调用父类的方法和重写的方法
*/

//1、定义Animal、Dog2、Cat2结构体;	Dog2和Cat2继承Animal
type Animal struct {
	name string
	age  int
}

type Dog2 struct {
	Animal
	sex string
}
type Cat2 struct {
	Animal
	color string
}

//2、定义Animal的方法;重写Dog2、cat2方法
func (animal Animal) eat() {
	fmt.Println("eat...")
}
func (animal Animal) sleep() {
	fmt.Println("sleep...")
}
func (dog Dog2) eat() {
	fmt.Println("Dog...eat...")
}
func (cat Cat2) sleep() {
	fmt.Println("Dog...eat...")
}

func main() {
	a1 := Animal{"二哈", 3}
	a1.eat()
	a1.sleep()
	
	//3、调用父类的方法和重写的方法
	dog := Dog2{Animal{"旺财", 5}, "公"}
	dog.Animal.eat() 
	dog.eat()        
	
	cat:=Cat2{Animal{"喵喵",10},"红色"}
	cat.Animal.sleep()
	cat.sleep()
}

接口实现

Go语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现接口定义的全部方法就是实现了这个接口

接口只做定义,不做具体的方法实现,具体实现交给实现方法

// go语言不需要显示的接口
// 实现接口中的方法,就算实现了接口
// go语言中,接口和实现类的关系,是非侵入式的

package main

import "fmt"

/*
1、定义一个USB接口、OInput、oOutput方法
2、定义一个Mouse鼠标结构体
3、实现接口中的所有方法,并约束对象为Mouse
4、测试方法
5、调用测试方法
6、示例二
*/

// 1、定义一个USB接口、OInput、oOutput方法
type USB interface {
	OInput()
	oOutput()
}

// 2、定义一个Mouse鼠标结构体
type Mouse struct {
	name string
}

// 3、实现接口中的所有方法,并约束对象为Mouse
func (mouse Mouse) OInput() {
	fmt.Println(mouse.name, "鼠标输入")
}
func (mouse Mouse) oOutput() {
	fmt.Println(mouse.name, "鼠标输出")
}

//4、测试方法
func test(u USB) {
	u.OInput()
	u.oOutput()
}

//6、示例二
type KeyBored struct {
	name string
}

func (k KeyBored) OInput() () {
	fmt.Println(k.name, "鼠标输入")
}
func (k KeyBored) oOutput() () {
	fmt.Println(k.name, "鼠标输出")
}

//5、调用测试方法
func main() {
	m := Mouse{"罗技"}
	test(m)

	k := KeyBored{"雷蛇"}
	test(k)
}

/*
罗技 鼠标输入
罗技 鼠标输出
雷蛇 键盘输入
雷蛇 键盘输出
*/

多态

多态:一个事务拥有多种形态,是面向对象中很重要的一个特点

Go语言通过接口来模拟多态

package main

import "fmt"

/*
1、定义Animal2接口、eat、sleep方法
2、定义Dogs2结构体
3、实现接口的方法
4、测试方法,传参为Animal2类型的对象
*/

//1、定义Animal2接口、eat、sleep方法
type Animal2 interface {
	eat()
	sleep()
}
//2、定义Dogs2结构体
type Dogs2 struct {
	name string
}

//3、实现接口的方法
func (dogs2 Dogs2) eat() {
	fmt.Println(dogs2.name, "...eat")
}
func (dogs2 Dogs2) sleep() {
	fmt.Println(dogs2.name, "...sleep")
}

//4、测试方法,传参为Animal2类型的对象
func test02(animal2 Animal2) {
	animal2.eat()
	animal2.sleep()
}
func main() {
	dogs2 := Dogs2{"旺财"}
	dogs2.eat()
	dogs2.sleep()
	test02(dogs2)
}

/*
旺财 ...eat
旺财 ...sleep
旺财 ...eat
旺财 ...sleep
*/

空接口

不包含任何的地方,正因为如此,所有的类型都实现了空接口,因此空接口可以存储任意类型的值

package main

import "fmt"

/*
1、定义 Dog3 、Cat3结构体
2、定义空接口A
3、main方法中传参空接口A的对象,打印输出
4、创建测试方法
5、调用测试方法
*/

// 1、定义 Dog3 、Cat3结构体
type Dog3 struct {
	name string
}
type Cat3 struct {
	name  string
	color string
}

// 2、定义空接口A
type A interface {
}

// 4、创建测试方法
func test03(a A) {
	fmt.Println(a)
}

func main() {
	//如果定义空接口,我们可以将任意类型对象赋值
	//3、main方法中传参空接口A的对象,打印输出
	var a1 A = Dog3{"大黄"}
	var a2 A = Cat3{"喵喵", "白色"}
	var a3 A = "haha"
	var a4 A = 100
	fmt.Println(a1)
	fmt.Println(a2)
	fmt.Println(a3)
	fmt.Println(a4)

	//5、调用测试方法
	test03(a1)
	test03(a2)
	test03(a3)
	test03(a4)

	//6、map 输入string类型 传参为interface{}任意类型
	map1 := make(map[string]interface{})
	map1["name"] = "旺财"
	map1["age"] = 17
	fmt.Println(map1)

	//7、切片
	s1 := make([]interface{}, 0, 10)
	s1 = append(s1, a1, a2, a3, a4) //将a1-a4追加到s1
	fmt.Println(s1)

}

/*
{大黄}
{喵喵 白色}
haha
100
{大黄}
{喵喵 白色}
haha
100
map[age:17 name:旺财]
[{大黄} {喵喵 白色} haha 100]
*/

接口嵌套

接口可以继承,还可以多继承

package main

import "fmt"

/*
1、定义两个接口 A2、B2分别创建testa、testb方法
2、接口C2继承A2和B2,创建testc方法
3、定义结构体Dogg22
4、实现testc方法需先实现testa、testb方法
5、创建A2、B2、C2的对象,然后调用它们方法
*/

// 1、定义两个接口 A2、B2分别创建testa、testb方法
type A2 interface {
	testa()
}
type B2 interface {
	testb()
}

// 2、接口C2继承A2和B2,创建testc方法
type C2 interface {
	A2
	B2
	testc()
}

// 3、定义结构体Dogs2
type Dogg2 struct {
}

// 4、实现testc方法需先实现testa、testb方法
func (d2 Dogg2) testa() {
	fmt.Println("testa")
}
func (d2 Dogg2) testb() {
	fmt.Println("testb")
}
func (d2 Dogg2) testc() {
	fmt.Println("testc")
}

func main() {
	//5、创建A2、B2、C2的对象,然后调用它们方法
	var dog2 Dogg2 = Dogg2{}

	var ia A2 = dog2 //A2和B2的接口,创建的对象ia和ib只能调用自己的方法 不能调用ic方法
	ia.testa()
	var ib B2 = dog2
	ib.testb()

	var ic C2 = dog2 //由于C2继承了A2和B2的接口,因此可以调用A2和B2的方法
	ic.testa()
	ic.testb()
	ic.testb()
}

/*
testa
testb
testa
testb
testb
*/

接口断言

检查接口类型变量的值是否实现了期望的接口,就是检查当前接口类型的值有没有实现指定的接口

把接口类型变量的值转换为其他类型或其他接口,go语言空interface{}可以保存任何类型的变量,当程序中需要使用变量的时候,需要把接口类型变量的值转换为具体类型,可以通过接口类型断言

其实很好理解,假如接口类型保存的值时数字,当前的值要参与数学运算,就必须转为int类型才可以参加运算,这就是利用接口断言来实现类型转换的例子

被断言的对象必须是接口类型,否则会报错

t:=i.(T)

t:=i.(T)

/*
断言成功,则t为T类型的接口值
断言失败则报错:panic
*/

代码:

package main

import "fmt"

func main() {
   assertString("aaa")
   assertString(1)
}
func assertString(i interface{}) {
   s := i.(string) //判断传入进来的参数是否为string
   fmt.Println(s)
}
/*
输出结果:
aaa
panic: interface conversion: interface {} is int, not string
*/

v,ok :=i.(T)

v,ok :=i(T)
/*
断言成功,则v为T类型的接口值,ok为true
断言失败,则v为空值,ok为false
*/

代码:

func assertInt(i interface{}) {
   s, ok := i.(int)
   if ok {
      fmt.Println("接口变量i是int类型")
      fmt.Println(s)
   } else {
      fmt.Println("接口变量i不是int类型")
   }
}
func main() {
	assertInt("aaa")
	assertInt(1)
}

type-switch

type switch
switch 接口变量.(type){
 	case 类型1:
 	//变量是类型1是的处理
 	case 类型2:
 	//变量是类型2是的处理
 	case nil:
 	//空接口进入此流程
 	default:
 	//变量不是所有case中列举的类型时的处理
}

代码:

func test08(i interface{}) {
	switch i.(type) { //默认为空
	case string:
		fmt.Println("string")
	case int:
		fmt.Println("int")
	case bool:
		fmt.Println("bool")
	case nil:
		fmt.Println("nil")
	}
}
func main() {
	test08(nil)
	test08(true)
	test08(123)
	test08("haha")
}

type别名

自定义类型

package main

import "fmt"

type Diyint int

代码:

package main

import "fmt"

//同过type关键字的定义,DiyInt就是一种新的类型,它具有int的特性
type DiyInt int
func main()  {
   var a DiyInt=10
   var b int=20
   c:=int(a)+b //需要进行转换才能参与运算
   fmt.Println(c)
}

类型别名

若想要计算使用,需要进行转换,所以为了省点麻烦,在函数里进行赋值定义

package main

import "fmt"

func main() {
	//定义int类型的别名为myInt
	//MyInt类型只会在代码中存在,编译完成并不会有myint类型
	type myint=int
	var d myint=40
	var e  int=50
	fmt.Println(d+e)
}

错误与异常

什么是错误

什么是错误

错误指的是可能出现问题的地方出现了问题。比如打开一个文件时失败,这种情况在人们的意料之中。
和错误类似的还有一个异常,指的是不应该出现问题的地方出现了问题。比如引用了空指针,这种情况在人们的意料之外。可见,错误是业务过程的一部分,而异常不是。

打开文件,存在错误情况

package main

import (
	"fmt"
	"os"
)

func main() {
	//打开一个文件,当前并没有aaa.txt所以会报错
	file, error := os.Open("aaa.txt")
	if error != nil {
		fmt.Println(error) //open aaa.txt: The system cannot find the file specified.
		return             //return 默认返回为nil
	} else {
		fmt.Println(file.Name())
	}
}

在实际工程项目中,我们希望通过程序的错误信息快速定位问题,但是又不喜欢错误处理代码写的冗余而又啰嗦。

Go语言没有提供像Java.c#语言中的try..catch异常处理方式,而是通过函数返回值逐层往上抛。

这种设计,鼓励工程师在代码中显式的检查错误,而非忽略错误

好处就是避兔漏掉本应处理的错误。但是带来一个弊端,让代码繁琐。

Go中的错误也是一种类型。错误用内置的error类型表示。就像其他类型的,如int, float64。错误值可以存储在变量中,从函数中返回,等等。

返回错误error

//创建一个自己的error错误信息,通过errors可以创建
1、errors.New("我是一个错误信息")

//也可以通过fmt创建
2、fmt.Errorf("我是一个错误信息%T", 500)
package main

import (
	"errors"
	"fmt"
)

/*
1、通过errors.New创建错误信息
2、定义setage函数
3、err接收errors.New返回的错误
4、还可以通过fmt创建
*/

func main() {
	//1、通过errors.New创建
	errorinfo := errors.New("我是一个错误信息")
	fmt.Printf("%Tn", errorinfo)

	//3、err接收errors.New返回的错误
	err := setage(-1)
	if err != nil {
		fmt.Println(err)
	}

	//4、还可以通过fmt创建
	errorinfo2 := fmt.Errorf("我是一个错误信息%T", 500)
	fmt.Println(errorinfo2)
}

// 2、定义setage函数
func setage(age int) error {
	if age < 0 {
		return errors.New("年龄输入不合法")
	} else {
		fmt.Println(age)
		return nil
	}
}

/*
*errors.errorString
年龄输入不合法
我是一个错误信息int
*/

错误类型

error类型是一个接口类型

type error interface{
	Error()string
}

通过断言,来判断具体的错误类型,然后进行对应的错误处理

package main

import (
	"fmt"
	"os"
)

/*
1、将Dome01的代码粘贴
2、通过断言来判断错误类型
*/
func main() {
	//1、将Dome01的代码粘贴
	file, error := os.Open("aaa.txt")//打开一个文件,当前并没有aaa.txt所以会报错
	if error != nil {
		fmt.Println(error) //open aaa.txt: The system cannot find the file specified.
	
		//2、通过断言来判断错误类型
		temp, ok := error.(*os.PathError)
		if ok {
			fmt.Println(temp.Op)
			fmt.Println(temp.Err)
			fmt.Println(temp.Path)
		}
		
		
		return //return 默认返回为nil
	} else {
		fmt.Println(file.Name())
	}
}
/*
open aaa.txt: The system cannot find the file specified.
open
The system cannot find the file specified.
aaa.txt
*/
package main

import (
   "fmt"
   "net"
)

func main() {

   addrs, err := net.LookupHost("www.baidu.com")
   fmt.Println(err)
   dnsError, ok := err.(*net.DNSError)
   if ok {
      if dnsError.Timeout() {
         fmt.Println("超时")
      } else if dnsError.Temporary() {
         fmt.Println("临时错误")
      } else {
         fmt.Println("其他错误")
      }
   }
   fmt.Println(addrs)
}
/*
<nil>
[14.215.177.38 14.215.177.39]
*/

自定义错误类型

package main

import "fmt"

/*
1、自定义一个错误结构体
2、定义一个方法,接收者为自定义结构体的对象,返回信息类型为string
3、定义测试方法
4、调用测试方法并接收错误信息,输出
*/

// 1、自定义一个错误结构体
type Mydiyerror struct {
	msg  string
	code int
}

// 2、定义一个方法,接收者为自定义结构体的对象,返回信息类型为string
func (e Mydiyerror) Error() string {
	return fmt.Sprint("错误信息", e.msg, "状态码", e.code)
}

// 3、定义测试方法
func test04(i int) (int, error) {
	if i != 0 {
		return i, &Mydiyerror{"非零数据", 500}
	}
	return i, nil
}
func main() {
	//4、调用测试方法并接收错误信息,输出
	i, err := test04(1)//传入0或者其他数测试
	if err != nil {
		myerr, ok := err.(*Mydiyerror)
		if ok {
			fmt.Println(myerr.msg)
			fmt.Println(myerr.code)
		}
	}
	fmt.Println(i)
}

panic和recover

go语言追求简洁,所以go语言中没有try...catch语句

因为go语言的作者认为将异常和控制语句混在一起,很容易让这个程序变得混乱,异常也很容易被滥用。

所以在go语言中,为了防止异常被滥用,我们常常使用函数的返回值来返回错误,而不是用异常来代替错误

如果在一些场景下确实需要处理异常,就可以使用panic和recover

panic用来抛出异常,recover用来恢复异常

panic

如果函数F中书写并触发了panic语句,会终止其后要执行的代码。在panic所在函数F内如果存在要执行的defer函数列表,则按照defer书写顺序的逆序执行

package main

import "fmt"
/*
1、创建测试方法,执行defer和正常语句 观察异常出现后执行情况
2、调用测试方法,在主方法中统一再添加defer和正常语句 观察异常出现后执行情况
*/

//1、创建测试方法,执行defer和正常语句 观察异常出现后执行情况
func test05(num int) {
	defer fmt.Println("test---1")
	defer fmt.Println("test---2")
	fmt.Println("test---3")
	if num == 1 {
		panic("出现异常")
	}
	fmt.Println("test---4")
	defer fmt.Println("test---5")
}

//2、调用测试方法,在主方法中统一再添加defer和正常语句 观察异常出现后执行情况
func main() {
	defer fmt.Println("main---1")
	defer fmt.Println("main---2")
	fmt.Println("main---3")
	test05(1)
	fmt.Println("main---4")
	defer fmt.Println("main---5")
}
/*
main---3
test---3
test---2
test---1
main---2
main---1
panic: 出现异常
*/

recover

recover的作用是捕获panic,从而恢复正常代码执行

recover必须配合defer使用

recover没有传入的参数,但是有返回值,返回值就是panic传递的值

package main

import "fmt"

/*
1、导入上一个Dome
2、创建一个匿名函数  使用recover覆盖,defer执行
*/


//1、导入上一个Dome
func test05(num int) {
	//2、创建一个匿名函数  使用recover覆盖,defer执行
	defer func() {
		msg := recover()
		if msg != nil {
			fmt.Println("程序恢复了---")
		}
	}()

	defer fmt.Println("test---1")
	defer fmt.Println("test---2")
	fmt.Println("test---3")
	if num == 1 {
		panic("出现异常")
	}
	fmt.Println("test---4")
	defer fmt.Println("test---5")
}


func main() {
	defer fmt.Println("main---1")
	defer fmt.Println("main---2")
	fmt.Println("main---3")
	test05(1)
	fmt.Println("main---4")
	defer fmt.Println("main---5")
}

/*
main---3
test---3
test---2
test---1
程序恢复了---
main---4
main---5
main---2
main---1
*/
  • 触发panic
  • 逆序执行test05函数中的panic前三条的语句
  • 执行到第三条defer时,recover恢复恐慌并输出信息
  • 返回外部函数,程序继续执行

由于recover恢复恐惧,所以程序就不会panic而退出执行,外部函数还能够正常执行下去