为完成某一功能的程序指令(语句)的集合,称为函数
。
在 Go 中,函数分为:自定义函数、系统函数。
基本语法
func 函数名 (形参) (返回值类型列表) {
执行语句
return 返回值列表
}
- Golang 中定义函数的关键字是:func。
- 形参列表:表示函数的输入。
- 函数中的语句:表示为了实现某一功能代码块。
- 函数可以有返回值,也可以没有。返回值也可以有多个。
- 如果返回多个值,在接收时,希望忽略某个返回值,则使用
_
符号表示站位忽略。 - 如果返回值只有一个,(返回值类型列表) 可以不写小括号,直接写返回值类型即可。
函数的递归调用
一个函数在函数体内有调用了本身,我们成为递归调用。
func test(n int) {
if n > 2 {
n--
test(n)
}
fmt.Println(n)
}
上图中,test(4) 调用结果,终端输出为:n=2 n=2 n=3。test2(4) 调用结果,终端输出为:n=2。
函数调用需要遵守的重要原则:
- 执行一个函数时,就创建一个新的受保护的独立空间(新函数栈)。
- 函数的局部变量是独立的,不会相互影响。
- 递归必须向退出递归的条件逼近,否则就是无限递归。
- 当一个函数执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁。
用地鬼的方式,实现斐波那契数:
init 函数
- 每一个源文件都可以包含一个init函数,该函数会在main函数执行前,被Go运行框架调用,也就是说init函数会在main函数前被调用。
- 如果一个文件同时包含
全局变量定义
、init函数和main函数,则执行的流程是:全局变量定义->init函数->main函数。 - init函数最主要的任务就是完成一些初始化工作。
面试题:如果main.go和utils.go都含有变量定义,init函数时,执行的流程是什么样的?
匿名函数
Go支持匿名函数,如果我们某个函数只是希望使用一次,可以考虑使用匿名函数,匿名函数也可以实现多次调用。
使用方式一:
在定义匿名函数时,直接调用,这种方式匿名函数只能调用一次。使用方式二:
将匿名函数赋给一个变量(函数变量),再通过该变量来调用匿名函数。
全局匿名函数:
如果将匿名函数赋给一个全局变量,那么这个匿名函数,就成为一个全局匿名函数,可以在程序中有效。
闭包
闭包就是一个函数和与其相关的引用环境组合的一个整体(实体)。
- addUpper() 是一个函数,返回的数据类型是 func (int) int
- 闭包的说明:
返回的是一个匿名函数,但是这个匿名函数引用到函数外的 n,因此这个匿名函数就和 n 形成一个整体,构成闭包。
- 闭包是类,函数时操作,n 是字段。函数和它使用到n构成闭包。
- 当我们反复调用 f 函数时,因为 n 是初始化一次,因此每调用一次就进行一次累计。
- 我们要搞清楚闭包的关键,就是要分析出返回的函数它使用(引用)到哪些变量,因为函数和它引用到的变量共同构成闭包。
闭包最佳实践,要求如下:
1、编写一个函数 makeSuffix(suffix string) 可以接收一个文件后缀名(比如:.jpg),并返回一个闭包。
2、调用闭包,可以传入一个文件名,如果该文件名没有指定的后缀(比如:.jpg),则返回 文件名.jpg,如果已经有 .jpg 后缀,则返回原文件名。
3、要求使用闭包的方式完成。
4、strings.HasSuffix,该函数可以判断某个字符串是否有指定的后缀。
函数中的 defer
在函数中,经常需要创建资源(比如:数据库连接、文件句柄、锁等),为了在函数执行完毕后,及时的释放资源
,Go的设计者提供 defer (延时机制)。
上面案例的执行输出结果是:
- ok3 res = 30
- ok2 n2 = 20
- ok1 n1 = 10
- res = 30
defer 的细节说明
函数参数传递的方式
- 值传递:int系列、float系列、bool、string、数组和结构体。
- 引用传递:指针、slice、map、channel、interface等。
其实不管是值传递还是引用传递,传给函数的都是变量的副本,不同的是,值传递的是值的拷贝,引用传递的是地址的拷贝,一般来说,地址拷贝效率高,因为数据量小,而值拷贝决定拷贝的数据大小,数据越大,效率越低。