Golang函数相关
本文主要记录了Golang的函数定义,可变参数,函数返回值,函数类型,匿名函数和闭包, 延迟调用,变量的作用域以及如何获取命令行参数,Golang是可以有多个返回值的,这一点能干很多事情,函数类型的主要功能在于回调和多态的实现,就像函数指针一样,用起来也很方便!
函数定义
函数定义说明:
- func: 函数由关键字func开始声明
- FumcName: 函数名称,根据约定,函数名首字母小写即为private,大写即为public
- 参数列表: 函数可以有0个或多个参数,参数格式为:变量名类型,如果有多个参数通过逗号分隔,不支持默认参数
- 返回类型: ①上面返回值声明了两个变量名o1和o2(命名返回参数),这个不是必须,可以只有类型没有变量名 ②如果只有一个返回值且不声明返回值变量,那么你可以省略,包括返回值的括号 ③如果没有返回值, 那么就直接省略最后的返回信息 ④如果有返回值, 那么 必须在函数的内部添加retun语句
1package main
2
3import "fmt"
4
5//无参无返回值函数定义
6func MyFunc(){
7 fmt.Println("MyFunc")
8}
9
10//带参无返回值
11func MyFunc3(a int){
12 a = 999
13 fmt.Println(a)
14}
15
16//带多个参数无返回值
17func MyFunc4(a int, b int) {
18 fmt.Printf("a=%d, b=%d\n", a, b)
19}
20
21//参数一样时可以简写
22func MyFunc5(a, b int) {
23 fmt.Printf("a=%d, b=%d\n", a, b)
24}
25
26//参数一样时可以简写,a、b都是int、e是float64、d、f都是string
27func MyFunc6(a, b int, e float64, d, f string) {
28 fmt.Printf("a=%d, b=%d\n", a, b)
29}
30
31func main() {
32 MyFunc()
33 MyFunc2()
34 MyFunc3(666)
35 MyFunc4(666, 777)
36 MyFunc5(666, 777)
37}
38
39//无参无返回值函数定义,定义在main之后也OK
40func MyFunc2(){
41 fmt.Println("MyFunc2")
42}
可变参数
和C语言一样,可变参数放最后,可传可不传
可变参数的参数传递方式,参考MyFunc04,如果是只传一部分,参考MyFunc05、MyFunc06
1package main
2
3import "fmt"
4
5//固定参数
6func MyFunc01(a, b int) {
7
8}
9
10//可变参数
11func MyFunc02(args ... int) {
12 for i, data := range args {
13 fmt.Printf("args[%d]=%d\n", i, data)
14 }
15}
16
17//固定参数+可变参数
18//固定参数必传、可变参数可以为空
19//可变参数一定要放在最后
20func MyFunc03(a int, b string, args ... int) {
21 fmt.Println("a =", a)
22 fmt.Println("b =", b)
23 for i, data := range args{
24 fmt.Printf("args[%d]=%d\n", i, data)
25 }
26}
27
28//可变参数的传递
29func MyFunc04(args ... int) {
30 MyFunc02(args ...)
31}
32
33//可变参数的传递
34//特殊情况:比如只想把后面两个参数传递给另外一个函数使用
35func MyFunc05(args ... int) {
36 //从args[2]开始(包括本身),把后面所有元素传过去
37 MyFunc02(args[2:] ...)
38}
39
40func MyFunc06(args ... int) {
41 //从args[2]开始(不包括本身),把前面所有元素传过去
42 MyFunc02(args[:2] ...)
43}
44
45func main() {
46 //MyFunc02(11, 22, 33, 44) // OK
47 //MyFunc02() // OK
48 //MyFunc03(11, "hello", 22, 33)
49 MyFunc04(11, 22, 33, 44)
50 fmt.Printf("-----------------------------\n")
51 MyFunc05(11, 22, 33, 44)
52 fmt.Printf("-----------------------------\n")
53 MyFunc06(11, 22, 33, 44)
54}
返回值
1package main
2
3import "fmt"
4
5//无参有一个返回值
6func MyFunc01() int {
7 return 100
8}
9
10//给返回值起一个变量名(golang的推荐写法)
11func MyFunc02() (result int) {
12 return 200
13}
14
15//给返回值起一个变量名(golang的推荐写法)
16func MyFunc03() (result int) {
17 result = 300
18 return
19}
20
21func main() {
22 a := MyFunc01()
23 fmt.Printf("a=%d\n", a)
24 b := MyFunc02()
25 fmt.Printf("b=%d\n", b)
26 c := MyFunc03()
27 fmt.Printf("c=%d\n", c)
28}
多个返回值的情况
1package main
2
3import "fmt"
4
5//多个返回值
6func MyFunc01() (int, int, int) {
7 return 11, 22, 33
8}
9
10//官方推荐写法
11func MyFunc02() (a int, b int, c int) {
12 a, b, c = 11, 22, 33
13 return
14}
15
16func main() {
17 _, _, c := MyFunc01()
18 fmt.Printf("c=%d\n", c)
19 _, b, _ := MyFunc02()
20 fmt.Printf("b=%d\n", b)
21}
多参数多个返回值
1package main
2
3import "fmt"
4
5func MaxValue(a, b int) (max, min int) {
6 if a > b {
7 max = a
8 min = b
9 }else{
10 min = a
11 max = b
12 }
13 return
14}
15
16func main() {
17 max, _ := MaxValue(11, 22)
18 fmt.Printf("max=%d\n", max)
19}
递归的例子
1package main
2
3import "fmt"
4
5func MyFunc01(num int) (ret int) {
6 if num == 1 {
7 return 1
8 }else {
9 return num + MyFunc01(num - 1)
10 }
11}
12
13func main() {
14
15 num := MyFunc01(100)
16 fmt.Printf("num=%d\n", num)
17}
函数类型
函数也是一种数据类型,通过type可以定义它,它的类型就是所有拥有的相同的参数,相同的返回值的一种类型
1package main
2
3import "fmt"
4
5func Add(a, b int) (ret int) {
6 return a + b
7}
8
9func Sub(a, b int) (ret int) {
10 return a - b
11}
12
13//声明一个函数类型
14type FuncType func(int, int) int
15
16//多态:多种形态,调用一个接口不同的表现形态,这里相当于把函数类型当作参数传递了
17func MyCalc(calc FuncType, a, b int) int {
18 return calc(a, b)
19}
20
21func main() {
22 //传统使用方式
23 ret := Add(10, 20)
24 fmt.Printf("ret=%d\n", ret)
25
26 //函数类型调用方式
27 var myFunc FuncType
28 myFunc = Add
29 ret = myFunc(20, 30) //等价于Add(20, 30)
30 fmt.Printf("ret=%d\n", ret)
31
32
33 fmt.Printf("Add ret=%d\n", MyCalc(Add, 10, 5)) //15
34 fmt.Printf("Sub ret=%d\n", MyCalc(Sub, 10, 5)) //5
35}
函数类型的应用:回调函数,也就是通过这种传递函数参数的方式实现的
其实底层实现就是函数指针数组
匿名函数和闭包
所谓闭包就是一个函数“捕获”了和它在同一作用域的其它常量和变量。这就意味着当闭包被调用的时候,不管在程序什么地方调用,闭包能够使用这些常量或者变量。它不关心这些捕获了的变量和常量是否已经超出了作用域,所以只有闭包还在使用它,这些变量就还会存在。
在Go语言里,所有的匿名函数 (Go语言规范中称之为函数字面量) 都是闭包。匿名函数是指不需要定义函数名的一种函数实现方式,它并不是一个新概念,最早可以回溯到1958年的Lisp语言
1package main
2
3import (
4 "fmt"
5)
6
7func main() {
8 //匿名函数,无函数名
9 myFunc01 := func() {
10 fmt.Printf("hello\n")
11 }
12
13 myFunc01()
14
15 //给一个函数类型起别名
16 type FuncType func() //无参数无返回值
17
18 var myFunc02 FuncType = myFunc01
19 myFunc02()
20
21 //定义匿名函数并调用
22 func() {
23 fmt.Printf("hello\n")
24 }() //()代表调用此匿名函数
25
26 //带参数的匿名函数
27 myFunc03 := func(a, b int) {
28 fmt.Printf("a=%d, b=%d\n", a, b)
29 }
30 myFunc03(10, 20)
31
32 //定义带参数的匿名函数并调用
33 func(a, b int) {
34 fmt.Printf("a=%d, b=%d\n", a, b)
35 }(10, 20)
36
37 //定义匿名函数带参带返回值
38 x, y := func(i, j int)(max, min int){
39 if i > j {
40 max, min = i, j
41 }else {
42 max, min = j, i
43 }
44 return
45 }(10, 20)
46 fmt.Printf("x=%d, y=%d\n", x, y)
47}
闭包以引用方式捕获外部变量
1package main
2
3import "fmt"
4
5func main() {
6 a := 10
7 s := "hello"
8
9 func(){
10 //闭包以引用方式捕获外部变量
11 a = 666
12 s = "world"
13 //内部:a=666, s=world
14 fmt.Printf("内部:a=%d, s=%s\n", a, s)
15 }()
16 //内部:a=666, s=world
17 fmt.Printf("内部:a=%d, s=%s\n", a, s)
18}
闭包的特点
1package main
2
3import "fmt"
4//函数的返回值是一个匿名函数,返回一个函数类型
5func test02() func()int {
6 var x = 0
7 return func() int {
8 x++
9 return x * x
10 }
11}
12
13func main() {
14 //函数的返回值是一个匿名函数,返回一个函数类型
15 //通过f来调用返回的匿名函数,即调用闭包
16 f := test02()
17 fmt.Printf("%d\n", f()) // 1
18 fmt.Printf("%d\n", f()) // 4
19 fmt.Printf("%d\n", f()) // 9
20 fmt.Printf("%d\n", f()) // 12
21}
22
23
24func test01() int {
25 //函数调用的时候,x才分配空间
26 var x = 0
27 x++
28 return x * x
29 //调用完毕 x释放
30}
31
32func main01() {
33 fmt.Printf("%d\n", test01()) // 1
34 fmt.Printf("%d\n", test01()) // 1
35 fmt.Printf("%d\n", test01()) // 1
36 fmt.Printf("%d\n", test01()) // 1
37}
所以可以看出变量的声明周期不是由它的作用域决定的,如果有闭包在使用它,那么它的声明周期是由闭包是否还在继续调用决定的!
延迟调用 defer
关键字defer用于延迟一个函数或者方法(或者当前所创建的匿名函数)的执行,注意:defer语句只能出现在函数或者方法的内部, 文件操作时比较常用
下面看看多个defer的执行顺序
如果一个函数中有多个defer语句,它们会以LIFO (后进先出)的顺序执行。哪怕函数或某个延迟调用发生错误,这些调用依旧会被执行。
defer和匿名函数结合使用
与上面的例子做一个对比
所以很明显可以看出来,defer就是延迟调用,但是不代表延时传参数
获取命令行参数
作用域
定义在{ }
里面的变量就是局部变量,注意if或者for语句里定义的变量也是属于if或者for语句独有的,执行到变量那句话的时候才分配内存空间,离开作用域内存自动释放
1package main
2
3import "fmt"
4
5//定义全局变量
6var num = 20
7
8//定义全局变量不能使用类型推导
9//num := 30 error
10
11func main() {
12 fmt.Println("num =", num)
13}
不同作用域允许定义同名变量,使用变量的原则,就近原则