Go(Golang)_05_函数

@

函数

函数(func):供调用完成特定功能的代码段

1)Go中不存在函数重载,每个源文件的函数名需保持唯一性;

2)Go中函数不存在参数默认值,实参顺序需与形参顺序相对应;

3)函数可作为其他函数的参数(回调)或返回值;



函数的定义格式:

func  函数名(参数列表) (返回值列表) {
    程序段
}

1)若函数无返回值时,可省略返回值列表;

2)若参数列表中多个参数的数据类型相同,则可共用该数据类型;

3)声明返回值列表时,函数中必须有以return显示结束的语句;

4)若函数仅返回未命名值时,则仅指定其返回值的数据类型;

5) “…数据类型”,代表可接收任意个该数据类型的参数;

//返回几个值,返回值列表就需指定几个值的数据类型



Go中参数传递分为:值传递、引用传递

1)值传递:int、float、string、bool、struct和数组

2)引用传递:slice、map、chan和值类型对应的指针

//值传递在传递值时,仅传递该值的副本(引用传递是传递指针)


如:编写并调用两个函数赋值给两个变量,并输出变量值

1)编写程序;
在这里插入图片描述
2)运行结果;
在这里插入图片描述
//不需要函数的多个返回值时,可使用空白标识符“_“丢失该值


变长函数

变长函数:函数拥有可变的参数个数

1)调用变长函数时,可传递给函数任意多个指定数据类型的参数;

2)变长函数通常用于格式化字符串;


变长函数的定义格式(除参数列表外,其他等同普通函数的定义):

func  函数名(参数1  数据类型, 参数2  ...数据类型) (返回值列表) {
    程序段
}

1)变长函数中需将可接收任意函数的参数放在后面;

2)切片被当参数传入时,变长函数内部与原切片共享存储空间;

3)可变参数在函数内部作为切片类型处理(无参数时,为nil切片);

4)变长参数必须是相同数据类型(...interface{}可接收任意数据类型);


如:定义变长函数计算总和

1)编写程序;
在这里插入图片描述
2)运行结果;
在这里插入图片描述


匿名函数

匿名函数:程序中特定作用域中由编译器调用的函数

1)命名函数仅能在包级别作用域中定义(匿名函数只能在其他作用域);

2)匿名函数进行递归:基于该匿名函数创建函数变量,再递归调用该变量;

3)匿名函数在被初始化后,其内部变量会一直存在(可引入内部变量解决);

//每执行一次匿名函数,其变量值都是上次执行后的变量值



匿名函数的定义格式(除函数名外,其他等同普通函数的定义):

func (参数列表) (返回值列表) {
    程序段
}(传入参数列表)

1)若直接运行匿名函数,末尾处需添加传入参数列表;

2)若搭配函数变量定义匿名函数,则可省略末尾处的传入参数列表;

//匿名函数会继承外部调用调函的全部变量(可覆盖)


如:通过匿名函数输出数字

1)编写程序;
在这里插入图片描述
2)运行结果;
在这里插入图片描述


函数变量

函数变量(闭包):将函数当作数据类型定义变量

1)本质:给函数起别名;

2)调用该变量就等同于调用函数,但可当成变量使用;

2)函数变量无参数时可省略其参数,但不可省略变量名后的();

3)函数变量不可比较(函数变量的零值是nil,所以可和nil比较);



函数变量定义的2种格式:基于已定义函数、基于匿名函数

(1)函数变量定义(基于已定义函数):

变量名 := 函数名

1)调用格式:变量名(参数)


(2)函数变量定义(基于匿名函数):

var 变量名 func(形参列表)(返回值列表){
	程序段
}

1)调用格式:变量名(参数)

2)也可省略“var 变量名”,仅执行一个匿名函数(需末尾指定传入参数);


如:通过两种格式创建函数变量,并调用

1)编写程序;
在这里插入图片描述
2)运行结果;
在这里插入图片描述


初始函数

init():包中最先执行的函数(编译器自动调用)

1)功能:初始化程序运行环境;

2)init()函数不能通过任何方式调用;

3)包中的源文件可写任意多个init()函数;

4)不同包的init函数按照包导入的依赖关系决定执行顺序;

//建议在包中每个源文件只写一个init函数


init()函数定义格式:

func  init()  {
    程序段
}

1)int()函数无参数和返回值;


延迟函数

延迟函数(defer):函数中特定语句在函数结束时才执行

1)函数中的defer关键词可无限使用,但执行时的顺序为倒序(栈实现);

2)若defer修饰的语句存在多次调用,则仅最后一次调用是延迟的;

3)延迟执行的语句可在函数返回真正结果前,对其做修改;


延迟函数的定义格式:

defer 程序语句

1)程序语句为普通程序段时,其变量值为当前变量的快照;

2)程序语句为函数调用时,其变量值为函数结束时变量值;


如:当程序语句分别为普通程序段和函数调用时,其变量值的不同输出

1)编写程序;

package main

import (
    "fmt"
)

func func1() func() {
    fmt.Println("Before return")

    return func() {
        defer fmt.Println("In the return")
    }
}

func main() {
    m := 10
    defer fmt.Println("First defer:", m)		//当前m变量值为10

    m = 100
    defer func() {
        fmt.Println("Second defer:", m)	//当外层函数结束时,才获取变量值
    }()

    m *= 10
    defer fmt.Println("Third defer", m)		//当前变量值为1000

    funcVal := func1()
    funcVal()
}

2)运行结果;
在这里插入图片描述


使用延迟函数须知:

1)延迟函数会被依次压入栈,执行时再依次取出;

2)压入栈之前,会先计算该函数地址、参数和返回值所需内存;

3)若延迟函数的参数为函数或变量,会先调用该函数或先计算该变量;


如:延迟函数压入栈和被调出栈的执行流程
在这里插入图片描述


如:调用延迟函数并输出延迟函数中的内容

1)编写程序;

package main

import (
    "fmt"
)

func test1(num1, num2 int) int {
    fmt.Println(num1 + num2)
    return num1 + num2
}

func test2() (t int) {
    defer func(i int) {
        fmt.Println(i)
        fmt.Println(t)
    }(t) //t默认初始值为0,则压入栈时传0

    t = 10
    return 20
}

func main() {
    test2()

    defer test1(1, test1(2, 3)) //先计算参数值再入栈,会输出5
    defer test1(4, test1(5, 6)) //先计算参数值再入栈,会输出11
}

2)运行结果;
在这里插入图片描述


延迟函数可改变外层函数的返回值(但必须是命名返回值),原因:

1)命名返回值函数都有一个返回值表,表中记录返回值的内容;

2)命名返回值函数在执行函数前就在返回值表上初始化对应零值;

3)函数返回值则会在函数结束之后,才将返回值拷贝到返回值表;

4)而返回值拷贝到返回值表的操作发生在defer语句之后;

//编译器会为未命名返回值隐式命名(也会记入表)



如:在函数中定义两个匿名延迟函数,观察其影响结果

1)编写程序;

package main

import (
    "fmt"
)

func test1() (str string) {
    str = "normal value"

    defer func() {
        str = "defer value"
    }()

    return
}

func test2() string {
    str := "normal value"

    defer func() {
        str = "defer value"
    }()

    return str
}
func main() {
    fmt.Println(test1())
    fmt.Println(test2())

2)运行结果;
在这里插入图片描述


实现原理

runtime/runtime2.go中defer的数据结构定义:

type _defer struct {
    siz       int32    // 参数和返回值共占字节数
    started   bool     // 是否已执行
    heap      bool     // 是否为存储于堆中的defer
    openDefer bool     // 是否为开发编码类型的defer

    sp        uintptr  // 被调用函数的栈指针
    pc        uintptr  // 返回地址
    fn        *funcval // 函数地址
    _panic    *_panic  // 消费该defer语句的_panic实例的地址
    link      *_defer  // 指向自身结构的指针(用于链接多个defer)

    fd   unsafe.Pointer 
    varp uintptr        
    framepc uintptr
}

用于创建和执行_defer实例的2个函数:

(1)deferproc()

1)说明:编译器根据defer语句出现的时间,将函数插在函数开头处;

2)作用:将defer语句转换成_defer实例,并存入goroutine的链表;

3)每个defer语句对应个deferproc()函数(_defer实例);

4)stack-allocated使用deferprocStack()函数;


(2)deferreturn()

1)说明:编译器根据defer语句出现的时间,将函数插在函数结尾处;

2)作用:将_defer实例从goroutine链表中取出并执行;

3)每个defer语句对应个deferreturn()函数;


三种实现

defer语句实现的3种类型(共存):

(1)heap-allocated

1)说明:_defer实例将被分配到堆上;

2)缺陷:频繁的堆内存分配和释放导致defer语句的性能较差;

3)引入时间:Go 1.13之前;


(2)stack-allocated

1)说明:_defer实例将被分配到栈上;

2)缺陷:函数的栈空间有限,并不能将所有defer语句分配到栈中;

3)引入时间:Go 1.13;


(3)open-coded

1)说明:编译器将defer语句翻译为执行代码插入到函数结尾处;

2)缺陷:较多限制条件(违反则使用heap-allocated);

3)引入时间:Go 1.14;



defer语句违反以下条件,就不可使用open-coded

1)代码编译时禁用编译器优化:无法翻译defer语句;

2)defer语句出现在循环中:无法确定最终生成多少个defer语句;

3)函数中return语句个数和defer语句个数的乘积大于15:出口多;

4)函数中出现8个以上的defer语句(编译器内部根据8bit变量进行翻译);

//编译器每翻译个defer语句就会将该变量置1(根据该变量进行插入执行代码)

posted @ 2022-05-02 13:46  爱和可乐的w  阅读(43)  评论(0编辑  收藏  举报