go基础知识(二)|青训营笔记

172 阅读6分钟

6.go语言的 break continue 关键字

break的使用

break语句可以结束forswitchselect的代码块。

1.单独在select中使用break和不使用break没有啥区别。

2.单独在表达式switch语句,并且没有fallthrough,使用break和不使用break没有啥区别。

3.单独在表达式switch语句,并且有fallthrough,使用break能够终止fallthrough后面的case语句的执行。

4.带标签的break可以跳出多层select/switch作用域。让break更加灵活,写法更加简单灵活,不需要使用控制变量一层一层跳出循环,没有带break的只能跳出

//特例:跳转到标签处
func main(){
MY_LABEL:
    for i := 0; i < 5; i++ {
        if i == 3 {
            break MY_LABEL
        }
        fmt.Printf("%v\n", i)
    }
    fmt.Println("end...")
}

continue的使用

continue只能用在循环中,在go中只能用在for循环中,它可以终止本次循环,进行下一次循环。

continue语句后添加标签时,表示开始标签对应的循环

//特例跳转到标签
func main(){
    MY_LABEL:
    for i:=0;i<5;i++{
        if i==3{
            continue MY_LABEL
        }
        fmt.Printf("%v\n",i)
    }
    fmt.Println("end...")
}

7.数组

go语言中的数组是相同数据类型的一组数据的集合,数组一旦定义长度不能修改,数组可以通过下标(或者叫索引) 来访问元素

数组不能用make()函数来声明初始化

go语言数组的定义

var 数组名称 [数组长度,必须是常量] 数组保存元素的类型
//例子:
var arr [3] int//初始化
arr = [3]int{1, 2, 3}//初始化在赋值法
for k, v := range arr {
    fmt.Println(k, v)
}

go语言数组的初始化

初始化,就是给数组的元素赋值,没有初始化的数组,默认元素值都是零值,布尔类型是false,字符串是空字符串

//1.使用初始化列表
    var a = [3]int{1,2,3}
    var s = [2]string{"小明","小刘"}
    var b = [2]bool{true,false}
//2.使用初始化列表(类型推断法)
    a:=[3]int{1,2}
    s:=[2]string{"小明","小刘"}
    b:=[2]bool{true,false}
//3.省略数组长度 数组长度可以省略,使用...代替,初始化值得数量自动判断
    var a = [...]int{1,2,3}
    var s = [...]string{"小明","小刘"}
    var b = [...]bool{true,false}
    a2: = [...]int{1,2,3}
//4.指定索引值的方式来初始化 可以通过指定所有的方式来初始化,未指定的int,float默认为零值,布尔为false string为空字符串
    a:=[...]int{0:1,2:3}
    s:=[...]string{0:"小明",2:"小刘"}
    b:=[...]bool{0:true,2:false}
}

go语言访问数组元素

可以通过下标的方式,来访问数组元素。数组的最大下标为数组长度-1,大于这个下标会发生数组越界

func main{
    var arr=[...]int{1,2,3}
    fmt.Println(arr[0])//对
    fmt.Println(arr[3])//越界
    //修改arr[0] arr[1]
    arr[0]=4
    arr[1]=5
}

根据数组长度遍历数组

可以根据数组长度,通过for循环和for range循环的方式来遍历数组,数组的长度可以用lengo语言中内置函数来获得

//1.for循环
func main{
    a := [...]string{"小明", "小刘", "小张"}
    for i := 0; i < len(a); i++ {
        fmt.Printf("下标:%v 值:%v", i, a[i])
    }
}
//2.for range循环
func main(){
    var arr = [...]bool{true, false, true, false}
    for i, v := range arr {
        fmt.Printf("下标为:%v,值为:%v", i, v)
    } 
}

多维(二维)数组

多维数组中的每一个元素又是一个数组

//二维数组的定义
var arrs[][]string
//1.初始化写法
var arrs=[][]string{
    {"小明","小张"},
    {"小刘","小兰"},
    {"小红","小强"},
}
//2.初始化写法
arrs:=[...][]string{
    {"小明","小张"},
    {"小刘","小兰"},
    {"小红","小强"},
}
//3.不支持写法
arrs:=[][...]string{
    {"小明","小张"},
    {"小刘","小兰"},
    {"小红","小强"},
}
//1.二维数组的遍历方法1 for循环
func main(){
    var arr = [...][]int{
        {1, 2},
        {3, 4},
        {5, 6},
    }
    for _, v1 := range arr {
        for _, v2 := range v1 {
            fmt.Printf("值:%v", v2)
        }
    }
}
//2.二维数组的遍历方法2 for range循环
func main(){
    var arr = [...][]int{
        {1, 2},
        {3, 4},
        {5, 6},
    }
    for i := 0; i < len(arr); i++ {
        for j := 0; j < len(arr[i]); j++ {
            fmt.Print(arr[i][j])
        }
        fmt.Println()
    }
}

8.切片

1.前面我们学习了数组,数组是固定长度,可以容纳相同数据类型的元素的集合。当长度固定时,使用还是带来一些

限制,比如:我们申请的长度太大浪费内存,大小又不够用

2.鉴于上述原因,我们有了go语言的切片,可以把切片理解为,可变长度的数组,其实它底层就是用数组实现的,增加

了自动扩容功能。切片(slice)是一个拥有相同类型元素的可变长度的序列

3.map引用类型

切片相关的语法

声明一个切片和声明一个数组类似,只要不添加长度就可以了

var sli [] int

切片的正常初始化

var sli = []int{1,2,3}

切片是引用类型,可以用make函数来创建切片:

var sli = make([]int,len)
还可以简写为
sli:=make([]int,len)

也可以指定容量,其中capacity为可选参数

make([]int,len,cap)

这里的len是数组的长度且也是切片的初始长度

切片的长度和容量

切片拥有自己的长度和容量,我们可以通过使用内置的len()函数求长度,使用内置的cap()函数求切片的容量

var names = []string{"小明", "小刘"}
var num = []int{1, 2, 3}
fmt.Printf("len:%d cap:%d\n", len(names), cap(names))
fmt.Printf("len:%d cap:%d\n", len(num), cap(num))
var s []int = make([]int, 2, 3)
var s1 = make([]string, 3, 4)
fmt.Printf("len:%d cap:%d\n", len(s), cap(s))
fmt.Printf("len:%d cap:%d\n", len(s1), cap(s1))

切片的初始化

切片的初始化方法很多,可以直接初始化,也可以使用数组初始化等

直接初始化

s := []int{1, 2, 3}
    fmt.Println(s)

使用数组初始化

arr := [...]int{1, 2, 3}
    s1 := arr[:]
    fmt.Println(s1)

使用数组的部分元素初始化(切片表达式)

切片的底层就是一个数组, 所以我们可以基于数组通过切片表达式得到切片。切片表达式中的low和high表示一个

索引范围 (左包含,右不包含) ,得到的切片长度=high-low,容量等于得到的切片的起始位置开始到切片长度

注意:数组和切片都可以这样划分得到新数组或者新切片

arr := [...]int{1, 2, 3, 4, 5, 6}
s1 := arr[:]                      //[]
fmt.Println(s1, len(s1), cap(s1)) //1,2,3,4,5,6 len6 cap6
s2 := arr[2:5]                    //[)
fmt.Println(s2, len(s2), cap(s2)) //3,4,5 len3 cap4
s3 := arr[:3]                     //[)
fmt.Println(s3, len(s3), cap(s3)) //1,2,3 len3 cap6
s4 := arr[2:]                     //2]
fmt.Println(s4, len(s4), cap(s4)) //3,4,5,6 len4 cap4

空(nil)切片

一个切片在未初始化之前默认为nil,长度为0,容量为0

var s1 []int
fmt.Println(s1==nil)//true
fmt.Printf("len:%d,cap:%d\n",len(s1),cap(s1))//0,0

切片的遍历

两种方式:forfor range

//1.for
s1 := []int{1, 2, 3, 4, 5, 6}
    for i := 0; i < len(s1); i++ {
        fmt.Println(s1[i])
    }
//2.for range
s1 := []int{1, 2, 3, 4, 5, 6}
for i, v := range s1 {
        fmt.Println(i, v)
    }

切片的增删改查和copy

切片是一个动态数组,可以使用append()函数添加元素,go语言中没有删除切片元素的专用方法,我们可以使用切片本身的特性来删除* 元素 。由于,切片是引用类型,通过赋值的方式,会修改原有的内容,go提供了copy()函数来拷贝切片( 但必须要用make()函数来创建切片)*

append()函数扩容后再赋值的数组或者切片是新切片和新数组

//1.单个增加
var sliceA []int
sliceA = append(sliceA, 12)
fmt.Println(sliceA) //12
sliceA = append(sliceA, 24)
fmt.Println(sliceA) //12,24
//2.
var sliceA = []int{1, 2, 3}
sliceA = append(sliceA, 4, 5, 6, 7, 8, 9, 10)
fmt.Println(sliceA)//1,2,3,4,5,6,7,8,9,10
//3.
var sliceA = make([]int, 3, 4)
sliceA = append(sliceA, 1, 2, 3)
fmt.Println(sliceA)//0 0 0 1 2 3
//4.多个增加
var sliceB []int
sliceB= append(sliceB,12,24,36,48,60)
fmt.Println(sliceB)//12,24,36,48,60
//5.append()函数实现两个切片之间的合并 即 把一个切片合并到另一个切片里并形成一个新的切片
sliceA := []string{"小明", "爱学习"}
sliceB := []string{"小刘", "爱玩耍"}
var sliceC []string
sliceC = append(sliceA, sliceB...)//...固定语法
fmt.Println(sliceC)

go语言中并没有删除切片的元素的专用方法,我们可以使用切片本身的特性来删除元素

sliceA := []int{1, 2, 3, 4, 5, 6}
//要删除索引为2的元素
sliceA = append(sliceA[:2], sliceA[3:]...)
fmt.Println(sliceA)//1 2 4 5 6

//正确写法(make)    指定长度 没有越界
var s1 = make([]int, 4, 8)
    s1[0] = 10
    s1[1] = 11
    s1[2] = 12
    s1[3] = 13
    fmt.Println(s1)
//正确写法(正常)    指定长度 没有越界
var s1 = []int{1,2}
    s1[0]=2
    s1[1]=3
    fmt.Println(s1)
//错误写法 长度不够 越界了
var s1 = []int{1, 2}
    s1[0] = 10
    s1[1] = 11
    s1[2] = 12
    s1[3] = 13
    fmt.Println(s1)

var s1 = []int{1, 2, 3, 4}
    fmt.Println(s1[0])
    fmt.Println(s1[1])
    fmt.Println(s1[2])
    fmt.Println(s1[3])

copy

copy()内置函数

sliceA := []int{1, 2, 3}
sliceB := make([]int, 3, 3)
copy(sliceB, sliceA) //右边复制给左边
sliceB[0] = 4
fmt.Println(sliceA)//1 2 3
fmt.Println(sliceB)//4 2 3

9.map

1.map是一种key:value键值对的数据结构容器。map内部实现是哈希表(hash)

2.map最重要的一点是通过key来快速检索数据,key类似与索引,指向数据的值

3.map是引用类型的 默认值是nil即map[]

map的语法格式

可以使用内置函数make()还可以使用map关键字来定义map

//声明变量,默认mapA是nil 即map[]
var mapA map [int] int
    变量名    key的数据类型 值的数据类型
//使用make函数声明变量 默认mapB是nil 即map[] 可以指定长度
var mapB = make(map[int]int)
//初始化
var mapC = map[int]int{1,2,3}
var mapC = make(map[int]int){1,2,3}
mapD := map[int]int{1,2,3}
mapD := make(map[int]int){1,2,3}

访问map

可以通过下标key获得值

mapA := map[string]string{
        "username": "小明",
        "age":      "18",
    }
    fmt.Printf("mapA["username"]: %v\n", mapA["username"])
    fmt.Printf("mapA["age"]: %v\n", mapA["age"])

判断某个键是否存在

go语言中有个判断map中键是否存在的特殊写法,格式如下:

value,ok:=map[key]

如果ok为true,存在;否则不存在

mapA := make(map[string]string)
mapA["username"] = "小明"
value, ok := mapA["username"]
fmt.Printf("value:%v--ok:%v", value, ok)//小明 true
value, ok = mapA["age"]
fmt.Printf("value:%v--ok:%v", value, ok)//无 
false

遍历map

可以用for range 循环进行map遍历,得到key和value值

//1.遍历key
mapA := make(map[string]string)
    mapA["username"] = "小明"
    mapA["age"] = "18"
    for key := range mapA {
        fmt.Printf("key:%v", key)
    }
//2.遍历key 和 value
mapA := make(map[string]string)
    mapA["username"] = "小明"
    mapA["age"] = "18"
    for key, value := range mapA {
        fmt.Println(key + ":" + value)
    }

map的增删改查

增改查很常规跟数组和切片一样操作这里就不展示了主要展示删(delete)

使用delete()内建函数从map中删除一组键值对,delete()函数的格式如下:

delete(map 对象,key)

其中:

  • map对象:表示要删除键值对的map对象
  • key:表示要删除的键值对的键
mapA := make(map[string]string)
    mapA["username"] = "小明"
    mapA["age"] = "18"
    delete(mapA, "age")
    for k, v := range mapA {
        fmt.Println(k + ":" + v)
    }

元素为map类型的切片

我们想在切片里面放一系列用户的信息,这时候我们就可以定义一个元素为map类型的切片

sliceA := make([]map[string]string, 2, 2) //map[string]string看作一个整体
    if sliceA[0] == nil {
        sliceA[0] = make(map[string]string)
        sliceA[0]["username"] = "小明" //这里的sliceA[0]看作map
        sliceA[0]["age"] = "18"
        sliceA[0]["weight"] = "80kg"
    }
    if sliceA[1] == nil {
        sliceA[1] = make(map[string]string)
        sliceA[1]["username"] = "小刘" //这里的sliceA[0]看作map
        sliceA[1]["age"] = "19"
        sliceA[1]["weight"] = "75kg"
    }
    for _, v := range sliceA {
        fmt.Println(v) //map[age:18 username:小明 weight:80kg]
                      // map[age:19 username:小刘 weight:75kg]
    }

遍历元素为map类型的切片

sliceA := make([]map[string]string, 2, 2) //map[string]string看作一个整体
    if sliceA[0] == nil {
        sliceA[0] = make(map[string]string)
        sliceA[0]["username"] = "小明" //这里的sliceA[0]看作map
        sliceA[0]["age"] = "18"
        sliceA[0]["weight"] = "80kg"
    }
    if sliceA[1] == nil {
        sliceA[1] = make(map[string]string)
        sliceA[1]["username"] = "小刘" //这里的sliceA[0]看作map
        sliceA[1]["age"] = "19"
        sliceA[1]["weight"] = "75kg"
    }
    for _, v1 := range sliceA {
        for i, v2 := range v1 {
            fmt.Printf("索引:%v--值:%v\n", i, v2)
            /*
            索引:username--值:小明
            索引:age--值:18
            索引:weight--值:80kg
            索引:username--值:小刘
            索引:age--值:19
            索引:weight--值:75kg
            */
        }
    }

值为切片类型的map

map的值可以是一个还可以是一系列(即值为切片类型)

var mapA = make(map[string][]string)//值是切片类型
    mapA["hobby"] = []string{
        "吃饭",
        "睡觉",
        "打豆豆",
    }
    mapA["work"] = []string{
        "java",
        "js",
        "go",
    }
    for _, v := range mapA {
        for index, value := range v {
            fmt.Printf("index:%v--value:%v\n", index, value)
        }
    }

map的排序

map的排序默认是随机的

//1.把map的key放在切片里面
var mapA = make(map[int]int)
mapA[1]=18
mapA[3]=20
mapA[2]=19
mapA[5]=22
mapA[4]=21
var keySlice [] int
for key,_:=range mapA{
    keySlice = append(keySlice,key)
}
//2.让key进行升序排序
sort.Ints(keySlice)
//3.循环遍历key输出map的值
for _,v:= range keySlice{
    fmt.Printf("key:%v--value:%v",v,map[v])
}
//写一个程序,统计一个字符串中每个单词出现的次数
    var str = "how do you do"
    var strSlice = strings.Split(str, " ")
    fmt.Println(strSlice)
    var strMap = make(map[string]int)
    for _, v := range strSlice {
        strMap[v]++
    }
    fmt.Printf("strMap: %v\n", strMap)

10.函数

函数在go语言中是一级公民所有的功能单元都定义在函数中,可以重复使用。函数包含函数的名称,参数列表

和返回值类型,这些构成了函数的签名(signature)

go语言中函数特性

  1. go语言中有3种函数:普通函数,匿名函数(没有名称的函数),方法(定义在struct上的函数)。
  2. go语言中不允许函数的重载(overload),也就是说不允许函数同名。
  3. go语言种的函数不能嵌套函数,但可以嵌套匿名函数。
  4. 函数是一个值,可以将函数复制给变量,使得这个变量也成为函数
  5. 函数可以作为参数传递给另一个函数
  6. 函数的返回值可以是一个函数
  7. 函数调用的时候,如果有参数传递给函数,则先拷贝参数的副本,再将副本传递给函数
  8. 函数参数可以没有名称

go语言函数定义语法

函数在使用之前必须先定义,可以调用函数来完成某个任务。函数可以重复调用,从而达到代码重用

func function_name([parameter list])[return_types]{
    函数体
}

说明:

  • func:函数由func开始声明

  • function_name:函数名称,函数名和参数列表一起构成了函数签名

  • [parameter list]:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。

    参数列表指定的是参数类型,顺序,及参数个数。参数是可选的,也就是说函数也可以不包括参数。

  • return_types:返回类型,函数返回一列值。return_types是该列值的数据类型。有些功能不需要返回值,这种情况下

    return_types不是必须的

  • 函数体:函数定义的代码集合

函数的返回值

函数可以有0或多个返回值,返回值需要指定数据类型,返回值通过return关键词来指定

return可以有参数,也可以没有参数,这些返回值可以有名称,也可以没有名称。go种的函数可以有多个返回值。

  1. return关键词中指定了参数时,返回值可以不用名称。如果return省略参数,则返回值部分必须带名称

  2. 当返回值有名称时,必须使用括号包围,逗号分割,即使只有一个返回值

  3. 但即使返回值命名了,return中也可以强制指定其他返回值名称,也就是说return的优先级更高

  4. 命名的返回值是预先声明好的,在函数内部可以直接使用,无需再次声明。命名返回值的名称不能和函数参数名称相同

    否则报错提示变量重复定义

  5. return中可以有表达式,但不能出现赋值表达式,这和其他语言可能有所不同。例如return a+b是正确的

    return c=a+b是错误的

go语言函数返回值实例

没有返回值

func f1(){
fmt.Printf("没有返回值,只是进行一些计算")
}

有一个返回值

func sum(a int,b int)(ret int){
    ret = a + b
    return ret//return
}
func sum(a int,b int)(int){
    ret:=a+b
    return ret
}

多个返回值,且在return中指定返回的内容

func f2()(name string,age int){
    name = "小明"
    age=30
    return name,age
}

多个返回值,返回值名称没有被使用

func f3()(name string,age int){
    name = "小明"
    age=30
    return //等价于 return name,age
}

总结:

  1. 返回值类型 声明了返回值变量如果和函数内用的一样 则函数体内可以直接使用该变量 且可以直接return结果

  2. 返回值类型那部分可以直接写数据类型不写变量名称 这样函数体内部需要重新声明 不能直接用return返回结果

  3. go中经常会使用其中一个返回值作为函数是否执行成功,是否有错误信息的判断条件。例如return value,exists

    return value,okreturn value,err

  4. 当函数的返回值过多时,例如有4个以上的返回值,应该将这些返回值收集到容器中,然后以返回容器的方式去返回。例如

    同类型的返回值可以放进slice中,不同类型的返回值可以放进map中

  5. 但函数有多个返回值时,如果其中某个或某几个返回值不想使用,可以通过下划线_来丢弃这些返回值。例如

    下面的f1函数两个返回值,调用该函数时,丢弃了第二个返回值b,只保留了第一个返回值a赋值给了变量a。

    func test()(a int,b int){
        a=2
        b=3
        return
    }
    func main(){
        a,_:=test()
    }
    

go函数的参数

go语言函数可以有0或多个参数,参数需要指定数据类型

声明函数的参数列表叫做形参,调用时传递的参数叫做实参。

go语言是通过传值的方式传参的,意味着传递给函数的是拷贝后的副本,所以函数内部访问,修改的也是这个副本

不修改原值

go语言可以使用边长参数,有时候不能确定参数的个数,可以使用变长参数,可以在函数定义语句的参数部分使用

ARGS...TYPE。这时会将...代表的参数全部保存到一个名为ARGS的slice中,注意这些参数的数据类型都是TYPE

演示参数传递,按值传递

func test2(a int, b int) {//形参列表
    temp := a
    a = b
    b = temp
​
}
func main() {
    a := 2
    b := 3
    fmt.Println(a, b)//实参列表
    test2(a, b)
    fmt.Println(a, b)
​
}

从运行结果可以看到,调用函数test后,a,b的值并没有被改变,说明参数传递是拷贝了一个副本,也就是拷贝了一份新的内容进行运算

map,slice,interface,channel这些数据类型本身就是指针类型的,所以就算是拷贝传值也是拷贝的指针,拷贝后的参数仍然指向

底层数据结构,所以修改它们可能会影响外部数据结构的值

演示参数传递,按址传递

func test3(a []int) {
    a[0] = 100
}
func main() {
    a := []int{1, 2, 3}
    test3(a)
    fmt.Println(a)
​
}

变长参数

func f1(args ...int){
    for_,v:=range args{
        fmt.Println(v)
    }
}
func f2(name string,age int,args ...int){
    fmt.Println(name)
    fmt.Println(age)
    for _,v:= range args{
        fmt.Println(v)
    }
}
func main(){
    f1(1,2,3)
    fmt.Println("-------")
    f1(1,2,3,4,5,6)
    fmt.Println("-------")
    f2("小明",20,1,2,3)
}

go语言函数类型和函数变量

可以使用type关键字来定义一个函数类型,语法格式如下:

type fun func(int,int)int

上面语句定义了一个fun函数类型,它是一种函数类型,这种函数接收两个int类型的参数并返回一个int类型的返回值

下面我们定义两个这样结构的两个函数,一个求和,一个比较大小:

func sum (a int,b int)int{
    return a+b
}
func max(a int, b int)int{
    if a>b {
        return a
    } else {
        return b
    }
}

下面定义一个fun函数类型,把summax赋值给它

type fun func(int, int) int//fun = func(int,int) intfunc sum(a int, b int) int {
    return a + b
}
func max(a int, b int) int {
    if a > b {
        return a
    } else {
        return b
    }
}
func main() {
    var f fun//f = fun = func(int,int)int
    f = sum
    s := f(1, 2)
    fmt.Println(s)
    f = max
    m := f(3, 4)
    fmt.Println(m)
​
}

go语言的高阶函数

go语言的函数,可以作为函数的参数,传递给另外一个函数,可以作为另外一个函数的返回值返回

go语言函数作为参数

func sayHello(name string){
    fmt.Printf("Hello,%s",name)
}
func f1(name string,f func(string)){
    f(name)
}
func main(){
    f1("tom",sayHello)//Hello,tom
}

go语言函数作为返回值

func add(a int, b int) int {
    return a + b
}
func sub(a int, b int) int {
    return a - b
}
func cal(operator string) func(int, int) int {
    switch operator {
    case "+":
        return add
    case "-":
        return sub
    default:
        return nil
    }
}
func main() {
    f1 := cal("+")
    add := f1(1, 2)
    f2 := cal("-")
    sub := f2(2, 1)
    fmt.Println(add)
    fmt.Println(sub)
}
​

go语言的匿名函数

go语言函数不能嵌套,但是在函数内部可以定义匿名函数,实现一下简单功能调用。

所谓匿名函数就是,没有名称的函数

语法格式如下:

func (参数列表)(返回值)

当然可以没有参数列表和返回值

匿名函数实例

func main(){
    max:=func(a,int,b int)int{
        if a>b{
            return a
        }else{
            return b
        }
    }
    i:=max(1,2)
    fmt.Println(i)
}

自己执行

func main(){
    func(a int,b int){
        max:=0
        if a>b{
            max=a
        }else{
            max=b
        }
        fmt.Println(max)
    }(1,2)//func(){}可以看作是一个函数名称如f1 f1里是函数的执行过程 即 func main{ f1(1,2)}自动执行
}
func test1(){
    name:="tom"
    age:="18"
    f1:=func()string{
        return name+age
    }//f1是变量,变量内容是匿名函数,不是返回值。f1调用之后f1()才有返回值
    msg:=f1()
    fmt.Println(msg)
}

闭包

闭包可以理解成定义在一个函数内部的函数。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。或者说

是函数和其引用环境的组合体。

闭包指的是一个函数和与其相关的引用环境组合而成的实体。简单来说,闭包=函数+引用环境

注意:

  1. 全局变量特点:常驻内存 污染全局

  2. 局部变量特点:不常驻内存 不污染全局

  3. 闭包:可以让一个变量常驻内存 可以让一个变量不污染全局

  4. 闭包是指有权访问另一个函数作用域中的变量的函数

    创建闭包的常见的方式就是在一个函数内部创建另一个函数,通过另一个函数访问这个函数的局部变量

    由于闭包里作用域返回的局部变量资源不会被立刻销毁回收,所以可能会占用更多的内存。过度使用闭包会导致

    性能下降,建议在非常有必要的时候才使用闭包

func add() func(int) int{
    var x int
    return func(y int) int{
        x+=y
        return x
    }
}
func main(){
    var f = add()
    fmt.Println(f(10))//10
    fmt.Println(f(20))//30
    fmt.Println(f(30))//60
    fmt.Println("-------")
    f1:=add()
    fmt.Println(f1(50))//50
    fmt.Println(f1(60))//110
}

递归

函数内部调用函数自身的函数称为递归函数

使用递归函数最重要的三点:

  1. 递归就是自己调用自己
  2. 必须先定义函数的退出条件,没有退出条件,递归将成为死循环
  3. go语言递归函数可能会产生一大堆的goroutine,也很可能会出现栈空间内存溢出的问题

go语言递归实例

阶乘

func f1(n int)int{
    //返回条件
    if n==1{
        return 1
    }else{
        //自己调用自己
        return n*f1(n-1)
    }
}
func main(){
    n:=5
    r:=f1(n)
    fmt.Println(r)
}

斐波那契数列

它的计算公式为f(n)=f(n-1)+f(n-2)且f(2)=f(1)=1

func f2(n int)int{
    //返回条件
    if n==1||n==2{
        return 1
    }else{
        return f(n-1)+f(n-2)
    }
}
func main(){
    r:=f2(5)
    fmt.Println(r)
}

defer panic recover

go语言中的defer语句会将其后面跟随的语句进行延迟处理。在defer归属的函数即将返回时,将延迟处理的语句

defer定义的逆序进行执行,也就是说,先被defer的语句最后被执行,最后被defer的语句,最先被执行。

defer特性

  1. 关键字defer用于注册延迟调用
  2. 这些调用直到return前才被执行。因此,可以用来做资源清理
  3. 多个defer语句,按先进后出的方式执行
  4. defer语句中的变量,在defer声明就决定了

defer用途

  1. 关闭文件句柄
  2. 锁资源释放
  3. 数据库连接释放

go语言defer语句实例

//查看执行顺序
func main(){
fmt.Println("start")//1
    defer fmt.Println("step1")//5
    defer fmt.Println("step2")//4
    defer fmt.Println("step3")//3
fmt.Println("end")//2
    
}

panic和recover

go语言目前是没有异常机制,但是使用panic/recover模式来处理错误。

panic可以在任何地方引发,但recover只有在defer调用的函数有效

func fn1(a int,b int){
    defer func(){
        err:=recover()
        if err!=nil{
            fmt.Println("error",err)
        }
    }()
    fmt.Println(a/b)
    panic("这里有异常")
}
func main(){
    fn1(10,0)
    fmt.Println("end...")
}

init函数

go语言有一个特殊的函数init函数,优先于main函数执行,实现包级别的一些初始化操作

init函数的主要特点

  • init函数陷于main函数自动执行,不能被其他函数调用
  • init函数没有参数和返回值
  • 每个包包含有多个init函数
  • 包的每个源文件也可以有多个init函数,这点比较特殊
  • 同一个包的init执行顺序,go没有明确定义,编程时要注意程序不要依赖这个执行顺序
  • 不同包的init函数按照包导入的依赖关系决定执行顺序

go初始化顺序

初始化顺序:变量初始化->init()->main()

var i int = initVar()
​
func init() {
    fmt.Println("init...")
}
func initVar() int {
    fmt.Println("初始化变量...")
    return 200
}
func main() {
    fmt.Println("main...")
}