青训营后端Go语言基础(四) | 豆包MarsCode AI 刷题

3 阅读7分钟

闭包

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

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

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

注意:

  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...")
}

11.指针

go语言中的函数传参都是值拷贝,当我们想要修改某个变量的时候,我们可以创建一个指向该变量地址的指针变量。传递数据使用指针

无需拷贝数据。

类型指针不能进行偏移和运算。

go语言中的指针操作非常简单,只需要记住两个符号:&(取地址)和*(根据地址取值,解引用)

指针地址和指针类型

每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置。go语言中使用&字符放在变量签名对变量进行取地址操作。

go语言中的值类型(int,float,bool,string,array,struct)都有对应的指针类型,如:*int,*int64,*string等等。

指针语法

一个指针变量指向了一个值的内存地址。(也就是说我们声明了一个指针后,可以像变量赋值一样,把一个值的内存地址放入到指针当中)。

类似于变量和常量,在使用指针前你需要声明指针。指针声明格式如下:

var var_name *var-type
//指针变量名  用于指定变量是作为一个指针    指针类型

指针声明实例

var ip *int//指向整型
var fp *float32//指向浮点型

指针使用实例

var ip *int//指针变量
    var sp *string
    var i int = 24//正常变量
    var s string = "小A"
    ip = &i//把正常变量的地址存到指针变量里
    sp = &s
    fmt.Println(*ip)//解引用输出指针变量里存的正常变量的地址的值
    fmt.Println(*sp)

指向数组的指针

定义语法

var ptr [MAX]*int//表示数组里面的元素类型是指针类型

注意:

  1. 不能直接取数组的地址给数组指针变量
  2. 不能直接解引用存储数组地址的数组指针变量
  3. 应该循环存储数组每一个元素的地址到数组指针变量
  4. 循环的从数组指针变量里解引用得到的数组地址

实例演示

const MAX int = 6
    a := [MAX]int{1, 2, 3, 4, 5, 6}
    var ap [MAX]*int
    //ap=&a这样写是错误的
    for i := 0; i < MAX; i++ {
        ap[i] = &a[i]
    }
    fmt.Println(ap)
    //fmt.Println(*ap)这样写是错误的
    for _, v := range ap {
        fmt.Println(*v)
    }

12.结构体

go语言没有面向对象的概念了,但是可以使用结构体来实现,面向对象编程的一些特性,例如:继承组合等特性。

go语言结构体的定义

结构体的定义和类型定义类似,只不过多了个struct关键词,语法结构:

type struct_variable_type struct{
    member definition
    member definition
    ...
}

type:结构体定义关键字

struct_variable_type:结构体类型名称

struct:结构体定义关键字

member definition:成员定义

go语言结构体定义实例

下面我们定义一个人的结构体Person

type Person struct{
    id int
    name string
    age int
    email string
}

以上我们定义一个Person结构体,有四个成员,来描述一个Person的信息

type Person struct{
    id,age int
    name,email string
}

声明一个结构体变量

声明一个结构体变量和声明一个普通变量相同,例如:

var tom Person
fmt.Println(小A){0,0}
kite:=Person{}
fmt.Println(Alex)//{0,0}

访问结构体成员

可以使用点运算符(.),来访问结构体成员,例如:

func main(){
    type Person struct{
        id,age int
        name,email string
    }
    var sk Person
    sk.id=1
    sk.name="sk"
    sk.age=20
    sk.email="sk@elysium.org.cn"
    fmt.Println(tom)//{1 20 sk sk@elysium.org.cn}
}

匿名结构体

如果结构体是临时使用,可以不用起名字,直接使用,例如:

func main(){
    var cat struct{
        id int
        name string
        }
    dog.id=1
    dog.name="哈基米"
    fmt.Println(cat)
}

今天就写到这里了,欢迎大家指正!