函数

Go语言中支持函数,匿名函数,闭包。

函数的定义

使用func关键字进行函数定义

1
2
3
func(参数)(返回值){
函数体
}
  • 函数名:由字母、数字、下划线组成。但函数名的第一个字母不能是数字。在同一个包内,函数名也称不能重名(包的概念详见后文)。
  • 参数:参数由参数变量和参数变量的类型组成,多个参数之间使用,分隔。
  • 返回值:返回值由返回值变量和其变量类型组成,也可以只写返回值的类型,多个返回值必须用()包裹,并用,分隔。
  • 函数体:实现指定功能的代码块。

Example:

1
2
3
4
//求两个数之和的函数:
func sum(x int, y int)(s int){
return x + y
}
1
2
3
4
//一个既不需要参数也没有返回值的函数
func f1(){
fmt.Println("一个既不需要参数也没有返回值的函数")
}

函数的调用

通过函数名()的方式调用

1
2
s := sum(1, 2)
fmt.Println(s)

调用有返回值的函数时,可以不接收其返回值

参数

类型简写

函数的参数中如果相邻变量的类型相同时,则可以省略类型。

1
2
3
4
5
//参数类型简写
func f2(a, b int, c, d bool){
fmt.Println(a+b)
fmt.Println(c&&d)
}

可变参数

可变参数是指函数的参数数量不固定。Go语言中的可变参数通过在参数名后加...来标识。

注意:可变参数通常作为函数的最后一个参数。

1
2
3
4
5
6
7
8
9
10
//可变参数
func f3(x string, b ...int) int {
fmt.Println("一个可变参数的函数")
fmt.Println(x)
var sum int
for a := range b {
sum += a
}
return sum
}

返回值

Go语言通过return关键字向外输出返回值。

多返回值

Go语言中支持多返回值,函数如果有多个返回值时必须用()将所有返回值包裹起来。

1
2
3
4
5
6
//函数拥有多个返回值
func f4(x , y int)(sum , mit int){
mit = x - y
sum = x + y
return sum, mit
}

返回值命名

函数定义时可以给返回值命名,并在函数体中直接使用这些变量,最后使用return关键字即可返回,与函数体中的得到结果的顺序无关,它会自动对应返回值变量名。

1
2
3
4
5
6
//函数拥有多个返回值
func f4(x , y int)(sum , mit int){
mit = x - y
sum = x + y
return
}

变量作用域

  • 全局变量

    全局变量是在函数外部定义的变量,它在程序整个运行周期内都有效。在函数中也能访问到全局变量。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    package main

    import "fmt"

    var x = 100 //定义一个全局变量

    func main() {
    f1()
    }

    func f1() {
    //x := 10
    //函数中查找变量的顺序
    //1、先在函数内部查找
    //2、找不到就在全局变量查找
    fmt.Println(x)
    }
  • 局部变量

    • 函数局部变量

      函数内部定义的变量无法在函数外部使用。

      局部变量和全局变量重名时优先使用局部按量。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      func f2() {
      //定义一个变量
      var b = 200
      fmt.Println(b)
      }

      func f3() {
      //无法在此函数中使用
      //fmt.Println(b)
      }
    • 语句块局部变量

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      if a := 10; a < 19 {
      fmt.Println(a)
      }
      //无法在语句块外部使用
      //fmt.Println(a)

      for i := 0; i< 10; i++{
      fmt.Println(i)
      }
      //无法在语句块外部使用
      //fmt.Println(i)

defer

Go语言中的defer语句会将其后面跟随的语句进行延迟处理。在defer归属的函数即将返回时,将延迟处理的语句按defer定义的逆序进行执行,也就是说,先被defer的语句最后执行,最后被defer的语句,最先被执行。

1
2
3
4
5
6
7
func main() {
fmt.Println("Start")
defer fmt.Println(1)
defer fmt.Println(2)
defer fmt.Println(3)
fmt.Println("End")
}

输出:

1
2
3
4
5
Start
End
3
2
1

由于defer语句的延迟特性,所以defer语句能非常方便的处理资源释放的问题。

比如:资源清理、文件关闭、解锁及记录时间等。

defer执行的时机

在Go语言的函数中return语句在底层并不是原子操作,他分为给返回值赋值何和RET指令两步。而defer语句执行的时机就在返回赋值操作后,RET指令执行前。

defer

经典案例:

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
//返回值5
func f1() int{
x := 5
defer func() {
x++ //修改的是x不是返回值
}()
return x
}

//返回值6
func f2()(x int) {
defer func() {
x++ //1、将5赋值给返回的参数x,再将x++
}()
return 5
}

//返回值5
func f3()(y int){
x :=5
defer func() {
x++ //1、将y = x = 5 2、改变x的值,但y值不会被改变
}()
return x
}

//返回值5
func f4()(x int) {
defer func(x int) {
x++ //1、改变的的是内部函数形参x的值 2、函数之间基本类型是值传递,改变的是x的副本
}(x)
return 5
}

面试题:

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
42
43
44
45
46
package main

import "fmt"

//输出结果是什么
func main() {
x := 1
y := 2
defer calc("AA", x, calc("A", x, y)) //A 1 2 //AA 1 3
x = 10
defer calc("BB", x, calc("B", x, y)) //B 10 2 //BB 10 12
y = 20
}

func calc(index string, a int, b int) int {
ret := a + b
fmt.Println(index, a, b, ret)
return ret
}

//自己做的,错误结果
//B 10 2 12
//BB 10 12 22
//A 1 2 3
//AA 1 3 4

//正确结果
//A 1 2 3
//B 10 2 12
//BB 10 12 22
//AA 1 3 4


//题目分析
//1. x := 1
//2. y := 2
//3. defer calc("AA", 1, calc("A", 1, 2)) //此时已经将值确定下来,并且这个方法里的值先计算出来,放到最后执行。
//4. calc("A", 1, 2) //所以先执行了第一个defer里的calc("A", 1, 2) ==》 A 1 2 3
//5. defer calc("AA", 1, 3) //延迟执行
//6. x = 10
//7. defer calc("BB", 10, calc("B", 10, 2))//此时已经将值确定下来,并且这个方法里的值先计算出来,放到最后执行。
//8. calc("B", 10, 2) //所以先执行了第二个defer里的calc("B", 10, 2) ==》B 10 2 12
//9. defer calc("BB", 10, 12)
//10. y = 10
//11. 倒过来执行第二个defer calc("BB", 10, 12) ==》 BB 10 12 22
//12. 倒过来执行第一个defer calc("AA", 1, 3) ==> AA 1 3 4

函数类型与变量

定义函数类型

Go语言中使用type关键字来定义函数类型

1
type calculation func(int, int)int //定义一个入参为intint,出参为int,类型的变量

定义之后就可以像内置类型一样使用string,bool,int等类型一样去声明变量

函数类型变量

使用自定义函数类型去声明变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type calculation func(int, int)int
var a calculation
a = add
i := a(1, 3)
fmt.Println(i) //4
a = sub
j := a(8, 3)
fmt.Println(j) //5

//计算两数之和
func add(x, y int) int {
return x + y
}

//计算两数之差
func sub(x, y int) int {
return x - y
}

高阶函数

函数作为参数

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
package main

import "fmt"

func main() {
//计算3,5的和
sum := calc(3, 5, add)
fmt.Println(sum)
//计算3,5的差
c := calc(9, 3, sub)
fmt.Println(c)
}

//计算两数之和
func add(x, y int) int {
return x + y
}

//计算两数之差
func sub(x, y int) int {
return x - y
}

//使用某种计算方式计算两个数的值
func calc(x, y int, op func(int, int) int)int {
return op(x, y)
}

函数作为返回值

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
func main() {
//获取计算数值的函数
type op func(int,int)int //定义函数类型变量
var d op
d = do("+")
s1 := d(2, 4)
fmt.Println(s1) // 6
d = do("-")
s2 := d(24, 7)
fmt.Println(s2) //17
}

//通过符号获取计算方式
func do(s string)func(int, int) int {
switch s {
case "+":
return add
case "-":
return sub
default:
return nil
}
}

//计算两数之和
func add(x, y int) int {
return x + y
}

//计算两数之差
func sub(x, y int) int {
return x - y
}

匿名函数

函数还可以作为返回值,但只能定义匿名函数,即没有函数名的函数。

1
2
3
func(参数)(返回值){
函数体
}

匿名函数没有函数名,所以不能像普通函数那样调用,所以匿名函数需要保存到某个变量或者作为立即执行函数。

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
package main

import "fmt"

//匿名函数一
var f1 = func(x, y int) (sum int) {
sum = x + y
fmt.Println("全局匿名函数")
return
}

func main() {
sum := f1(2, 5)
fmt.Println(sum)

//函数内部没办法声明带名字的函数
//匿名函数二
f2 := func(x ...int) {
var sum int
for _, v := range x {
sum += v
}
fmt.Println("局部匿名函数")
fmt.Println(sum)
}
f2(1, 2, 3, 4, 5)

//如果只调用一次的函数可以写成立即执行函数
func(x, y int) {
fmt.Println(x + y)
fmt.Println("立即执行函数")
}(5,6)
}

匿名函数多用于实现回调函数和闭包。

闭包

闭包指的是一个函数和其他相关的引用环境组合而成的实体。

闭包=函数+引用环境

基础:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func adder() func(int) int {
var x int
return func(y int) int {
x += y
return x
}
}

func main() {
f := adder()
fmt.Println(f(10)) //10
fmt.Println(f(20)) //30
fmt.Println(f(30)) //60
}

进阶1:

1
2
3
4
5
6
7
8
9
10
11
12
13
func adder(x int) func(int) int {
return func(y int) int {
x += y
return x
}
}

func main() {
f := adder(10)
fmt.Println(f(10)) //20
fmt.Println(f(20)) //40
fmt.Println(f(30)) //70
}

进阶2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func makeSuffixFunc(suffix string) func(string) (string) {
return func(name string) string {
if !strings.Contains(name, suffix) {
return name + suffix
}
return name
}
}

func main() {
jpgFunc := makeSuffixFunc(".jpg")
txtFunc := makeSuffixFunc(".txt")
fmt.Println(jpgFunc("女神"))
fmt.Println(txtFunc("武陵外传"))
}

进阶3:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func cale(base int) (func(int) int, func(int) int) {
add := func(i int) int {
base += i
return base
}

sub := func(i int) int {
base -= i
return base
}
return add, sub
}

func main() {
f1, f2 := cale(10)
fmt.Println(f1(1), f2(2)) //11 9
fmt.Println(f1(3), f2(4)) //12 8
fmt.Println(f1(5), f2(6)) //13 7
}

进阶4:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func f1(f func()) {
fmt.Println("this is f1")
f()
}

func f2(x, y int) {
fmt.Println("this is f2")
fmt.Println(x + y)
}

func f3(f func(x, y int), x, y int) func() {
return func(){
f(x, y)
}
}

func main() {
//在f1里面执行f2
f1(f3(f2, 100, 200))
}

panic()/recover()

Go语言目前是没有异常机制的,但是使用panic/recover模式来处理错误。panic()可以在任何地方引发,但是recover只有在defer调用的函数中有效。

程序报错:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func main() {
funcA()
funcB()
funcC()
}

func funcA(){
fmt.Println("A")
}

func funcB(){
panic("B执行出错了")
fmt.Println("B") //永远不可达,
}

func funcC(){
fmt.Println("C")
}

输出:

1
2
3
4
5
6
7
8
9
10
A
panic: B执行出错了

goroutine 1 [running]:
main.funcB(...)
D:/Develop/Sources/GoWorkSpace/src/github.com/DurianLollipop/StudyGo/day03/painc_recover/main.go:16
main.main()
D:/Develop/Sources/GoWorkSpace/src/github.com/DurianLollipop/StudyGo/day03/painc_recover/main.go:7 +0x9d

Process finished with exit code 2

程序运行期间引发panic导致程序崩溃,异常退出了。这个时候我们就可以通过recover将程序恢复回来继续往后执行。

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
func main() {
funcA()
funcB()
funcC()
}

func funcA(){
fmt.Println("A")
}

func funcB(){
defer func() {
//recover()必须搭配defer来使用,并且defer要定义在fanic()之前
err := recover()
//如果程序执行出错,可以通过recover()恢复过来
if err != nil {
fmt.Println("recover in B")
}
}()
panic("B执行出错了")
fmt.Println("B") //永远不可达,
}

func funcC(){
fmt.Println("C")
}

分金币

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
42
43
44
45
46
47
48
49
50
51
package main

import "fmt"

/*
你有50枚金币,需要分配给以下几个人:Matthew,Sarah,Augustus,Heidi,Emilie,Peter,Giana,Adriano,Aaron,Elizabeth。
分配规则如下:
a. 名字中每包含1个'e'或'E'分1枚金币
b. 名字中每包含1个'i'或'I'分2枚金币
c. 名字中每包含1个'o'或'O'分3枚金币
d: 名字中每包含1个'u'或'U'分4枚金币
写一个程序,计算每个用户分到多少金币,以及最后剩余多少金币?
程序结构如下,请实现 ‘dispatchCoin’ 函数
*/
var (
coins = 50
users = []string{
"Matthew", "Sarah", "Augustus", "Heidi", "Emilie", "Peter", "Giana", "Adriano", "Aaron", "Elizabeth",
}
distribution = make(map[string]int, len(users))
)

func main() {
left := dispatchCoin()
fmt.Printf("金币分配情况:%v, 剩下:%d", distribution, left)
}

func dispatchCoin() int {
for _, name := range users {
var count int
for _, c := range name {
switch c {
case 'e', 'E':
count++
break
case 'i', 'I':
count += 2
break
case 'o', 'O':
count += 3
break
case 'u', 'U':
count += 4
break
}
}
distribution[name] = count
coins -= count
}
return coins
}