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(根据该变量进行插入执行代码)