函数是组织好的、可重复使用的、用于执行指定任务的代码块。Go语言中支持函数、匿名函数和闭包,并且函数在Go语言中属于“一等公民”。
- go语言不支持函数嵌套,在声明了函数里,不能再声明有名字的函数了。一般在其他语言中也不这么用,只是会调用函数而已。函数里声明函数,这种用法其实在其他被允许的语言里也不常见。
- go语言同一个包里,是不能有同名函数的!公开性原则。
- 在同一个src目录下,不同的代码是可以调用相互的函数的。
1. 函数定义
Go语言中定义函数使用func
关键字,具体格式如下:
1
2
3
| func 函数名(参数)(返回值){
函数体
}
|
其中:
- 函数名:由字母、数字、下划线组成。但函数名的第一个字母不能是数字。在同一个包内,函数名也不能重名(包的概念详见后文)。
- 参数:参数由参数变量和参数变量的类型组成,多个参数之间使用
,
分隔。 - 返回值:返回值由返回值变量和其变量类型组成,也可以只写返回值的类型,多个返回值必须用
()
包裹,并用,
分隔。 - 函数体:实现指定功能的代码块。
2. 函数类型
2.1 没有参数,也没有返回值
1
2
3
4
5
6
| func f() {
fmt.Println("加油!")
}
func main() {
f() // 加油!
}
|
2.2 有参数无返回值
1
2
3
4
5
6
| func sum(a int, b int) {
fmt.Println(a + b)
}
func main() {
sum(1, 2) // 直接输出3
}
|
2.3 没有参数,有返回值
注意有返回值时函数的写法
1
2
3
4
5
6
7
| func f() string {
return "加油!"
}
func main() {
s := f()
fmt.Println(s) // 输出加油
}
|
2.4 命名函数返回值
返回值可以命名,也可以不命名, 命名的函数返回值相当于在函数中的一个变量
需要注意的是,这里如果是命名返回值,是可以省略的。
1
2
3
4
5
6
7
| func f(a int, b int) (ret int) {
ret = a + b
return
}
func main() {
fmt.Println(f(1, 2)) //3
}
|
2.5 多个返回值
1
2
3
4
5
6
| func f() (string, int) {
return "haha", 1
}
func main() {
fmt.Println(f()) // haha 1
}
|
当函数有多个返回值,而我们只要其中一个或者几个时,可以使用匿名变量来接受不需要的
1
2
| _, n := f()
fmt.Println(n) // 1
|
2.6 参数类型简写
多个参数类型相同时,可以使用下面方式简写,省略部分传参的类型
1
2
3
4
5
6
| func f(a, b int, m, n string, b1 bool) int {
return a + b
}
func main() {
fmt.Println(f(1, 2, "aaa", "bbb", true)) // 3
}
|
2.7 可变长参数
1
2
3
4
5
6
7
| func f(x string, y ...int) {
fmt.Println(x,y)
}
func main() {
f("下雨咯")
f("你好", 1, 2, 3, 4, 5, 6)
}
|
out
1
2
| 下雨咯 []
你好 [1 2 3 4 5 6]
|
可以看到,可变长参数其实是slice
3. 示例1
1
2
3
4
5
6
| func f(name string) {
fmt.Println("你好", name)
}
func main() {
f("bowen")
}
|
4. 示例2
1
2
3
4
5
6
7
8
| func cal(x, y int) (sum, sub int) {
sum = x + y
sub = x - y
return sum, sub
}
func main() {
fmt.Println(cal(3, 1)) // 4,2
}
|
函数在Go语言中用的非常之多,要学会灵活使用函数。
5 函数名也是变量
1
2
3
4
5
6
7
8
9
10
11
12
13
| func f1() {
fmt.Println("你好呀")
}
func f2() int {
return 10
}
func main() {
a := f1
fmt.Printf("%T %v\n", a, a)
b := f2
fmt.Printf("%T %v\n", b, b())
fmt.Println(b())
}
|
out
1
2
3
| func() 0x132dd40
func() int 10
10
|
可以看到,函数名其实也是变量,它也是有内存地址的!也是有类型的。比如func() int 就是这个变量的类型。在实际编码中是会用到的。
6. 函数作为参数
这种用法也会用到的,要记住。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| // 接上面代码
func f3(x func() int) {
ret := x()
fmt.Println(ret)
}
func main() {
a := f1
fmt.Printf("%T %v\n", a, a)
b := f2
fmt.Printf("%T %v\n", b, b())
fmt.Println(b())
f3(f2) //10
f3(b) //10
}
|
7. 函数也可以作为函数的返回值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| // 接上面代码
func f5(a, b int) int {
return a + b
}
func f4(z func() int) func(x, y int) int {
return f5
}
func main() {
a := f1
fmt.Printf("%T %v\n", a, a)
b := f2
fmt.Printf("%T %v\n", b, b())
fmt.Println(b())
f3(f2)
f3(b)
n := f4(f2)
fmt.Println(n) // 0xd3de60 返回函数的内存地址
}
|
8. 匿名函数
函数当然还可以作为返回值,但是在Go语言中函数内部不能再像之前那样定义函数了,只能定义匿名函数。匿名函数就是没有函数名的函数,匿名函数的定义格式如下:
1
2
3
| func(参数)(返回值){
函数体
}
|
匿名函数因为没有函数名,没办法像普通函数那样调用,所以匿名函数需要保存到某个变量或者作为立即执行函数:
1
2
3
4
5
6
7
8
9
10
11
12
| func main() {
// 函数内部没有办法声明带名字的函数
add := func(a, b int) int {
sum := a + b
return sum
}
fmt.Println(add(1, 2))
// 如果只是调用一次的函数,可以简写成立即执行函数
func(a, b int) {
fmt.Println(a + b)
}(1, 2)
}
|
out
匿名函数多用于实现回调函数和闭包。
9. defer
Go语言中的defer
语句会将其后面跟随的语句进行延迟处理。在defer
归属的函数即将返回时,将延迟处理的语句按defer
定义的逆序进行执行,也就是说,先被defer
的语句最后被执行,最后被defer
的语句,最先被执行。
1
2
3
4
5
6
7
8
9
10
| func deferdemo() {
fmt.Println("start...")
defer fmt.Println("1")
defer fmt.Println("2")
defer fmt.Println("3")
fmt.Println("end...")
}
func main() {
deferdemo()
}
|
out
1
2
3
4
5
| start...
end...
3
2
1
|
由于defer
语句延迟调用的特性,所以defer
语句能非常方便的处理资源释放问题。比如:资源清理
、文件关闭
、解锁及记录时间
等。
在Go语言的函数中return
语句在底层并不是原子操作,它分为给返回值赋值和RET指令两步。而defer
语句执行的时机就在返回值赋值操作后
,RETURN指令执行前。具体如下图所示:
1
2
3
4
5
6
7
8
9
10
11
| func f1() int {
x := 5 // 这个时候已经赋值完成,返回了x
defer func() {
x++ //再修改的x不是返回值了,而是x本身,x已经赋值完成返回回去了
fmt.Println(x)
}()
return x // 5
}
func main() {
fmt.Println(f1()) //5
}
|
1
2
3
4
5
6
7
8
9
| func f2() (x int) { // 命名返回值
defer func() {
x++ // 返回了5,x,修改的x就是返回值了,5++就是6
}()
return 5
}
func main() {
fmt.Println(f2()) // 6
}
|
1
2
3
4
5
6
7
8
9
10
| func f3() (y int) {
x := 5
defer func() {
x++ // 这题改的是x,不是返回值,返回值是y。
}()
return x //返回值=y=x=5
}
func main() {
fmt.Println(f3()) // 5
}
|
1
2
3
4
5
6
7
8
9
| func f4() (x int) {
defer func(x int) {
x++ //改变的是函数中的副本
}(x)
return 5 // 返回值 = x = 5
}
func main() {
fmt.Println(f5()) // 5
}
|
看上面四个代码片段,可以说稍微有点绕,不好理解,又回去翻了一遍视频才理解。长时间不看确实会生疏。
10. 闭包
闭包的概念还是很绕的,一开始看的时候并不理解,现在勉强理解,据说在实际编程中并不怎么用,后面随着逐步深入再理解吧。
Go 语言支持匿名函数,可作为闭包。匿名函数是一个"内联"语句或表达式。匿名函数的优越性在于可以直接使用函数内的变量,不必申明.
闭包的描述
函数体内嵌套另外一个函数,并且返回的是一个函数, 主函数temp 体内的 匿名函数func, 被主函数体外 te引用的时候, 就形成了一个闭包 特性
不同变量引用的调用之间没有关系,公用一个主函数体内 变量i. 闭包函数不会被GC(垃圾回收机制)回收所占用资源,因此会出现外部调用时会接着上次调用的结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
| // FunOne 定义一个FunOne
// 这就是一个普通的函数,只不过传参是函数,在函数里调了函数
func FunOne(f func()) {
f()
}
// FunTwo 定义了带参数的FunTwo,这个函数也是普通的有两个参数的函数
func FunTwo(x, y int) {
test := x + y
fmt.Printf("函数2的 x+y=%d\n", test)
}
// 怎么做不改变FunOne()的条件下,使用funOne() 调用funTwo()
func FunThree(f func(x, y int), left, right int) func() {
temp := func() {
f(left, right)
}
return temp
}
// 函数体内嵌另外一个函数,并且返回的是一个函数
func temp() func(int) {
var i int
return func(ind int) {
i = i + ind
fmt.Println(i)
}
}
func main() {
ret := FunThree(FunTwo, 10, 100)
FunOne(ret) // 这样就在函数One中成功调用了函数Two,函数2的 x+y=110
// 2. 主函数temp体内的匿名函数func,被主函数体外的te引用的时候就形成了一个闭包
te := temp()
// 3. 不同变量引用的调用之间没有关系,公用一个主函数体内的变量i
// 4.闭包函数不会被GC(垃圾回收机制)回收所占用的资源,因此会出现外部调用时会接着上次调用的结果
te(5) // 调用函数之后i=5
te(-10) // 调用函数之后i=-5
te(1) // 调用函数之后i=-4
te(-2) // 调用函数之后i=-6
}
|
这个例子还是不错的,解释的比较明白,我看了李文周老师的视频讲解这一块的时候,基本上被绕晕了。