1. 函数的定义

Go语言中支持函数、匿名函数和闭包,并且函数在Go语言中属于“一等公民”。与Python不同,Go语言的函数定义格式变种比较多,且对形参和返回值要求严格,需要指定其类型,因此产生了很多不同形式的变种!所有语言中函数的都是三要素构成,分别是 函数名称、参数、返回值!

1.1. 函数的通用格式

  1. func funcName(args)(res) {
  2. funcBody
  3. }
  • func: 函数声明的关键词,类似于Python中def
  • funcName: 函数名称,由字母、数字、下划线组成,不以数字开头,通常采用驼峰结构。在同一个包中函数名称不能重复。main函数为程序的入口函数。
  • args: 形参,可选项。支持一个或者多个参数,也支持不定长参数,但是不支持默认参数。参数需要指定数据类型
  • res: 返回值,可选项。支持一个或者多个返回值,需要指定返回值类型。
  • funcBody: 函数体,即函数内部代码块。

    1.2. 函数的各种形式

    1.2.1. 不带参数和返回值

    ```go package main

import “fmt”

func f1() { fmt.Println(100 + 200) }

func main() { f1() }

  1. <a name="Kpz3r"></a>
  2. #### 1.2.1. 带形参的函数
  3. ```go
  4. package main
  5. import "fmt"
  6. func f1(a int, b int, c string) { // 位置实参类型与形参不一致会报错
  7. fmt.Println(c, "=", a+b)
  8. }
  9. func main() {
  10. f1(100, 200, "100+200")
  11. }

1.2.2. 形参简写

  1. package main
  2. import "fmt"
  3. func f1(a, b int, c string) { // 参数a 省略类型表示与后一个参数类型一致
  4. fmt.Println(c, "=", a+b)
  5. }
  6. func main() {
  7. f1(100, 200, "100+200")
  8. }

1.2.3. 不定长传参

  • 不定长传参的变量需要作为最后的形参
  • 不定长参数接收参数后,转换为切片,不能与前一个形参采用简写形式
  • 不定长传参可以接收0个实参 ```go package main

import “fmt”

func f1(a int, b …int) { // 不能用 a, b …int, 因为b为切片 fmt.Printf(“a:%#v\tb:%#v\n”, a, b) // var res int for _, v := range b { res += v } fmt.Println(res) }

func main() { f1(100, 200, 300, 400, 500) f1(1000) }

e:\OneDrive\Projects\Go\src\gitee.com\studygo\day03\02-define_func>go run main.go a:100 b:[]int{200, 300, 400, 500} 1400 a:1000 b:[]int(nil) 0

  1. <a name="ntzmy"></a>
  2. #### 1.2.4. 一个返回值
  3. ```go
  4. package main
  5. import "fmt"
  6. func f1(n ...int) int {
  7. var res int
  8. for _, v := range n {
  9. res += v
  10. }
  11. return res
  12. }
  13. func main() {
  14. fmt.Println(f1(100, 200, 300, 400, 500))
  15. }

1.2.5. 多个返回值

当需要返回多个值的时候,可以放到一个序列中,比如数组或者切片中,也可以返回多个值!

  1. package main
  2. import "fmt"
  3. func f1(n int) (int, int) {
  4. var (
  5. ret1 int = 0
  6. ret2 int = 1
  7. )
  8. for i := 1; i <= n; i++ {
  9. ret1 += i
  10. ret2 *= i
  11. }
  12. return ret1, ret2
  13. }
  14. func f2(n int) [2]int {
  15. var (
  16. ret1 int = 0
  17. ret2 int = 1
  18. )
  19. for i := 1; i <= n; i++ {
  20. ret1 += i
  21. ret2 *= i
  22. }
  23. return [2]int{ret1, ret2}
  24. }
  25. func main() {
  26. ret1, ret2 := f1(15)
  27. ret3 := f2(15)
  28. fmt.Printf("f1: ret1:%d\tret2:%d\n", ret1, ret2)
  29. fmt.Printf("f2: res:%#v\n", ret3)
  30. }
  1. e:\OneDrive\Projects\Go\src\gitee.com\studygo\day03\02-define_func>go run main.go
  2. f1: ret1:120 ret2:1307674368000
  3. f2: res:[2]int{120, 1307674368000}

1.2.6. 命名返回值

当在指定返回值的变量名称时,即完成了对该变量声明,如对数字来说,初始值为0,这个可能会导致计算结果异常!

  • 命名返回值会自动完成变量的声明
  • 必须要使用return语句,否则语法错误。可以仅仅使用一个return
  • 命名返回值可以采用简写
  • 不支持命名返回值和不命名返回值混用 ```go package main

import “fmt”

func f1(n int) (ret1 int, ret2 int) { ret2 = 1 // ret2 初始值为0,会导致计算异常 for i := 1; i <= n; i++ { ret1 += i ret2 *= i } return // 等同于 return ret1,ret2 }

func f2(n int) (ret1, ret2 int) { ret2 = 1 // ret2 初始值为0,会导致计算异常 for i := 1; i <= n; i++ { ret1 += i ret2 *= i } return ret1, ret2 }

func main() { ret1, ret2 := f1(15) ret3, ret4 := f2(15) fmt.Printf(“f1: ret1:%d\tret2:%d\n”, ret1, ret2) fmt.Printf(“f2: ret3:%d\tret4:%d\n”, ret3, ret4) }

e:\OneDrive\Projects\Go\src\gitee.com\studygo\day03\02-define_func>go run main.go f1: ret1:120 ret2:1307674368000 f2: ret3:120 ret4:1307674368000

  1. <a name="XVUgm"></a>
  2. ### 1.3. 参数类型
  3. 在Go中,无论传递的参数是值类型还是引用类型([参考](https://www.yuque.com/duduniao/go/icbfqw#rn0H4)),都是拷贝一份参数的值到函数内部,但是在使用中,引用类型和值类型的变量是有区别的,引用类型只是拷贝一个内存地址,资源开销小,较为常用。
  4. <a name="pmzfM"></a>
  5. #### 1.3.1. 值类型的参数
  6. 函数内部接收到的参数 s 和 传递的s0 是两个值相同,但是内存地址不同的变量。因此,修改s并不会影响s0。
  7. ```go
  8. package main
  9. import "fmt"
  10. func f0(s string) {
  11. fmt.Printf("s: type:%T value:%v addr:%p\n", s, s, &s)
  12. }
  13. func main() {
  14. s0 := "hello world!"
  15. fmt.Printf("s0: type:%T value:%v addr:%p\n", s0, s0, &s0)
  16. f0(s0)
  17. }
  1. [root@heyingsheng src]# go run studygo/day03/func14/main.go
  2. s0: type:string value:hello world! addr:0xc0000461f0
  3. s: type:string value:hello world! addr:0xc000046220

1.3.2. 引用类型参数

下面案例中,s1为引用类型,它的内存地址为 0xc000006028 ,存储的是s0的地址 0xc0000461f0 。函数f0中,s的内存地址为 0xc000006038 ,存储的也是s0的地址 0xc0000461f0
通过这个案例可以发现,引用类型作为参数时,函数参数接收的也是实参的副本,只不过s1和函数内s指向的都是s0,因此可以通过s去修改s0的值罢了。

  1. package main
  2. import "fmt"
  3. func f0(s *string) {
  4. fmt.Printf("s: type:%T value:%v addr:%p\n", s, s, &s)
  5. }
  6. func main() {
  7. s0 := "hello world!"
  8. s1 := &s0
  9. fmt.Printf("s1: type:%T value:%v addr:%p\n", s1, s1, &s1)
  10. f0(s1)
  11. }
  1. [root@heyingsheng src]# go run studygo/day03/func14/main.go
  2. s1: type:*string value:0xc0000461f0 addr:0xc000006028
  3. s: type:*string value:0xc0000461f0 addr:0xc000006038

1.3.3. 案例

  1. package main
  2. import "fmt"
  3. func f0(s *string) {
  4. *s = "HELLO WORLD!"
  5. }
  6. func main() {
  7. s0 := "hello world!"
  8. f0(&s0) // 修改s0的值
  9. fmt.Println(s0)
  10. }
  1. [root@heyingsheng src]# go run studygo/day03/func14/main.go
  2. HELLO WORLD!

2. 变量的作用域

Go语言中变量的作用域分为三种,全局变量、函数内的局部变量、代码块中的局部变量。变量的检索方式与Python类似,都是从当前代码向上寻找,就近匹配!

  1. package main
  2. import "fmt"
  3. var (
  4. a string = "global a"
  5. b string = "global b"
  6. c string = "global c"
  7. )
  8. func main() {
  9. a := "func a"
  10. b := "func b"
  11. if a := "if a"; true {
  12. fmt.Printf("if 语句块内部: %#v\t%#v\t%#v\n", a, b, c)
  13. }
  14. fmt.Printf("main 函数内部: %#v\t%#v\t%#v\n", a, b, c)
  15. }
  1. e:\OneDrive\Projects\Go\src\gitee.com\studygo\day03\03-vars>go run main.go
  2. if 语句块内部: "if a" "func b" "global c"
  3. main 函数内部: "func a" "func b" "global c"

3. 匿名函数

匿名函数就是在定义的时候没有指定名字的函数,在Go语言中,函内部是无法使用func关键词定义新的命名函数,如果需要定义则必须使用匿名函数的方式。匿名函数的使用方式有两种:

  • 定义匿名函数的时候直接调用,即一次性使用,在函数的defer语句中常用
  • 将函数赋值给变量,实现多次调用

    3.1. 函数定义时直接使用

    ```go package main

import “fmt”

func main() { // 定义匿名函数并直接使用 res := func (n1, n2 int) int { return n1 + n2 }(100,200)

  1. fmt.Println("res=",res) // 300

}

  1. <a name="o9pf1"></a>
  2. ### 3.2. 匿名函数赋值给变量
  3. ```go
  4. package main
  5. import "fmt"
  6. func main() {
  7. // 定义匿名函数并赋值给变量
  8. add := func (n1, n2 int) int {
  9. return n1 + n2
  10. }
  11. res := add(100, 200)
  12. fmt.Println("res=",res) // 300
  13. res = add(300,400)
  14. fmt.Println("res=",res) // 700
  15. }

3.3. 全局匿名函数

  1. package main
  2. import "fmt"
  3. // 定义匿名函数并赋值给变量, 几乎不用
  4. var add = func (n1, n2 int) int {
  5. return n1 + n2
  6. }
  7. func main() {
  8. fmt.Printf("%T %v\n", add, add) // func(int, int) int 0x49fb10
  9. res := add(100, 200)
  10. fmt.Println("res=",res) // 300
  11. res = add(300,400)
  12. fmt.Println("res=",res) // 700
  13. }

4. 小练习

  1. package main
  2. import (
  3. "fmt"
  4. "strings"
  5. )
  6. /*
  7. 你有50枚金币,需要分配给以下几个人:Matthew,Sarah,Augustus,Heidi,Emilie,Peter,Giana,Adriano,Aaron,Elizabeth。
  8. 分配规则如下:
  9. a. 名字中每包含1个'e'或'E'分1枚金币
  10. b. 名字中每包含1个'i'或'I'分2枚金币
  11. c. 名字中每包含1个'o'或'O'分3枚金币
  12. d: 名字中每包含1个'u'或'U'分4枚金币
  13. 写一个程序,计算每个用户分到多少金币,以及最后剩余多少金币?
  14. */
  15. var (
  16. coins = 50
  17. users = [...]string{"Matthew","Sarah","Augustus","Heidi","Emilie","Peter","Giana","Adriano","Aaron","Elizabeth"}
  18. distribution = make(map[string]int,len(users))
  19. )
  20. func dispatchCoin() int {
  21. for _, name := range users{
  22. ret := strings.Count(name, "e") + strings.Count(name, "E") +
  23. (strings.Count(name, "i")+ strings.Count(name, "I")) * 2 +
  24. (strings.Count(name, "o")+ strings.Count(name, "O")) * 3 +
  25. (strings.Count(name, "u")+ strings.Count(name, "U")) * 4
  26. distribution[name] = ret
  27. coins -= ret
  28. }
  29. return coins
  30. }
  31. //func dispatchCoin() int {
  32. // for _, name := range users{
  33. // distribution[name] = 0
  34. // for _, char := range name { // 循环遍历
  35. // switch char {
  36. // case 'e','E':
  37. // distribution[name] += 1
  38. // coins -= 1
  39. // case 'i','I':
  40. // distribution[name] += 2
  41. // coins -= 2
  42. // case 'o','O':
  43. // distribution[name] += 3
  44. // coins -= 3
  45. // case 'u', 'U':
  46. // distribution[name] += 4
  47. // coins -= 4
  48. // }
  49. // }
  50. // }
  51. // return coins
  52. //}
  53. func main() {
  54. left := dispatchCoin()
  55. fmt.Println("剩下:", left)
  56. fmt.Printf("%#v\n", distribution)
  57. }
  1. [root@heyingsheng day03]# go run 10-homework/main.go
  2. 剩下: 10
  3. map[string]int{"Aaron":3, "Adriano":5, "Augustus":12, "Elizabeth":4, "Emilie":6, "Giana":2, "Heidi":5, "Matthew":1, "Peter":2, "Sarah":0}