认识函数

  1. func funcName(input1 type1,input2 type2)(output1 type1,output2 type2){
  2. return value1,value2
  3. }
  4. /*
  5. func 用来声明函数
  6. funcName 指函数名称(匿名函数和lambda函数除外)
  7. 函数名称如果小写开头,它的作用域只属于所声明的包,不能被其它包调用,如果大写开头,则函数公开,可被其它包调用。
  8. 这个规则适用于所有变量、函数等实体对象的声明,类似Java中的作用域关键字 private protect public
  9. Go语言不支持嵌套(nested)、重载(overload)、默认参数(default parameter)
  10. */

函数的基础

多返回值

  1. package main
  2. import "fmt"
  3. func SumAndProduct(A, B int) (int, int) {
  4. return A + B, A * B
  5. }
  6. func main() {
  7. x := 3
  8. y := 4
  9. xPLUSy, xTIMESy := SumAndProduct(x, y)
  10. fmt.Printf("%d + %d = %d\n", x, y, xPLUSy)
  11. fmt.Printf("%d * %d = %d\n", x, y, xTIMESy)
  12. }
  1. package main
  2. import "fmt"
  3. func SumAndProduct(A, B int) (add int, Multiplied int) {
  4. add = A + B
  5. Multiplied = A * B
  6. return
  7. }
  8. func main() {
  9. x := 3
  10. y := 4
  11. xPLUSy, xTIMESy := SumAndProduct(x, y)
  12. fmt.Printf("%d + %d = %d\n", x, y, xPLUSy)
  13. fmt.Printf("%d * %d = %d\n", x, y, xTIMESy)
  14. }

函数作为参数

  1. package main
  2. import "fmt"
  3. // 直接将函数作为另一个函数的入参,入参函数可以未实现,在调用传参的时候再实现
  4. func pipe(ff func() int) int {
  5. return ff()
  6. }
  7. // 先将一个函数定义为一个类型,再作为另一个函数的入参,入参函数可以未实现,在调用传参的时候再实现
  8. type FormatFunc func(s string, x, y int) string
  9. func format(ff FormatFunc, s string, x, y int) string {
  10. return ff(s, x, y)
  11. }
  12. func main() {
  13. s1 := pipe(func() int { return 100 })
  14. s2 := format(func(s string, x, y int) string {
  15. return fmt.Sprintf(s, x, y)
  16. }, "%d,%d", 10, 20)
  17. fmt.Println(s1, s2) // 100 10,20
  18. }

函数作为类型

  1. package main
  2. import "fmt"
  3. func isOdd(v int) bool {
  4. return v%2 != 0
  5. }
  6. func isEven(v int) bool {
  7. return v%2 == 0
  8. }
  9. func getAll(v int) bool {
  10. return true
  11. }
  12. // 将入参和出参类型相同的函数抽象定义为一个类型(关联关系仅依赖入参和出参类型)
  13. type boolFunc func(int) bool
  14. // 类型(函数)作为一个参数使用
  15. func filter(slice []int, f boolFunc) []int {
  16. var result []int
  17. for _, value := range slice {
  18. if f(value) {
  19. result = append(result, value)
  20. }
  21. }
  22. return result
  23. }
  24. func main() {
  25. slice := []int{3, 1, 4, 5, 9, 2}
  26. fmt.Printf("slice: %v\n", slice) // slice: [3 1 4 5 9 2]
  27. odd := filter(slice, isOdd)
  28. fmt.Printf("odd: %v\n", odd) // odd: [3 1 5 9]
  29. even := filter(slice, isEven)
  30. fmt.Printf("even: %v\n", even) // even: [4 2]
  31. all := filter(slice, getAll)
  32. fmt.Printf("all: %v\n", all) // all: [3 1 4 5 9 2]
  33. }

可变参数

  1. package main
  2. import "fmt"
  3. func main() {
  4. // 手动动态添加参数
  5. age := ageMinOrMax("min", 1, 3, 2, 0)
  6. fmt.Printf("最小年龄%d岁\n", age) // 最小年龄0岁
  7. // 数字提前定义参数
  8. ageArr := []int{7, 9, 3, 5, 1}
  9. age = ageMinOrMax("max", ageArr...)
  10. fmt.Printf("最大年龄%d岁\n", age) // 最大年龄9岁
  11. }
  12. func ageMinOrMax(m string, a ...int) int {
  13. if len(a) == 0 {
  14. return 0
  15. }
  16. if m == "max" {
  17. max := a[0]
  18. for _, v := range a {
  19. if v > max {
  20. max = v
  21. }
  22. }
  23. return max
  24. } else if m == "min" {
  25. min := a[0]
  26. for _, v := range a {
  27. if v < min {
  28. min = v
  29. }
  30. }
  31. return min
  32. } else {
  33. e := -1
  34. return e
  35. }
  36. }
  1. package main
  2. import "fmt"
  3. func main() {
  4. ageArr := []int{7, 9, 3, 5, 1}
  5. f1(ageArr...)
  6. }
  7. // 传递格式为arr...还是arr取决于调用函数接收类型是否可变
  8. func f1(arr ...int) {
  9. f2(arr...)
  10. fmt.Println("")
  11. f3(arr)
  12. }
  13. func f2(arr ...int) {
  14. for _, char := range arr {
  15. fmt.Printf("%d ", char) // 7 9 3 5 1
  16. }
  17. }
  18. func f3(arr []int) {
  19. for _, char := range arr {
  20. fmt.Printf("%d ", char) // 7 9 3 5 1
  21. }
  22. }
  1. // builtin.go
  2. type any = interface{}
  3. // print.go
  4. func Printf(format string, a ...any) (n int, err error) {
  5. return Fprintf(os.Stdout, format, a...)
  6. }

匿名函数与闭包

  1. package main
  2. import "fmt"
  3. func main() {
  4. // 匿名函数地址保存到变量中
  5. fplus := func(x, y int) int { return x + y }
  6. result := fplus(3, 4)
  7. fmt.Println(result)
  8. // 匿名函数定义时立即调用1
  9. result = func(x, y int) int { return x + y }(3, 4)
  10. fmt.Println(result)
  11. // 匿名函数定义时立即调用2
  12. func(num int) int {
  13. sum := 0
  14. for i := 1; i <= num; i++ {
  15. sum += i
  16. }
  17. fmt.Println(sum)
  18. return sum
  19. }(100)
  20. }
  1. package main
  2. import "fmt"
  3. /*
  4. 概念
  5. 匿名函数同样被成为闭包(函数式语言的术语)
  6. 闭包允许调用定义在其它环境下的变量,使函数可以捕捉到外部状态
  7. 用法
  8. 1. 闭包经常被用作包装函数,预先定义好一个或多个参数以用于包装
  9. 2. 使用闭包完成更加简洁的错误检查
  10. */
  11. func main() {
  12. fmt.Println(Add()(3)) // 5
  13. fmt.Println(Add2(6)(3)) // 9
  14. }
  15. // Add 无参函数,返回值是一个匿名函数,匿名函数返回一个int类型的值
  16. func Add() func(b int) int {
  17. return func(b int) int {
  18. return b + 2
  19. }
  20. }
  21. // Add2 无参数函数,返回值是一个匿名函数,匿名函数返回一个int类型的值
  22. func Add2(a int) func(b int) int {
  23. return func(b int) int {
  24. return a + b
  25. }
  26. }
  1. package main
  2. import "fmt"
  3. /*
  4. 1. 前后两次调用,外部变量j的值发生了变化
  5. 2. 闭包函数中,只有内部匿名函数才能访问变量i,而无法通过其它途径访问,保证了i的线程安全
  6. */
  7. func main() {
  8. j := 5
  9. a := func() func() {
  10. i := 10
  11. return func() {
  12. fmt.Printf("i = %d j = %d \n", i, j)
  13. }
  14. }()
  15. a() // i = 10 j = 5
  16. j = 10
  17. a() // i = 10 j = 10
  18. }
  1. package main
  2. import "fmt"
  3. func main() {
  4. var f = adder()
  5. fmt.Println(f(1)) // [x = 0 d = 1] [1]
  6. fmt.Println(f(2)) // [x = 1 d = 2] [3]
  7. fmt.Println(f(3)) // [x = 3 d = 3] [6]
  8. }
  9. /*
  10. 1. 闭包中的变量可以在闭包函数体内声明,也可以在外部函数声明
  11. 2. 不管外部函数是否退出,它都能够继续操作外部函数中的局部变量
  12. */
  13. func adder() func(int) int {
  14. var x int
  15. return func(d int) int {
  16. fmt.Println("x = ", x, "d = ", d)
  17. x += d
  18. return x
  19. }
  20. }
  1. package main
  2. import "fmt"
  3. func getSequence() func() int {
  4. i := 0
  5. return func() int {
  6. i++
  7. return i
  8. }
  9. }
  10. func main() {
  11. // nextNumber 为一个函数,函数i为0
  12. nextNumber := getSequence()
  13. // 调用nextNumber函数,i变量自增1并返回
  14. fmt.Printf("%d ", nextNumber()) // 1
  15. fmt.Printf("%d ", nextNumber()) // 2
  16. fmt.Printf("%d ", nextNumber()) // 3
  17. // 创建新的函数nextNumber1,并查看结果
  18. nextNumber1 := getSequence()
  19. fmt.Printf("%d ", nextNumber1()) // 1
  20. fmt.Printf("%d ", nextNumber1()) // 2
  21. }

递归函数

大量递归调用容易导致程序栈内存分配耗尽造成栈溢出。好在通过懒惰求值可以解决这个问题,在Go中,可以用过管道(channel)和 goroutine 来解决这个问题。

内置函数

close 用于管道通信
len 用于返回某个类型的长度或数量(字符串、数组、切片、map和管道)
cap 容量,用于返回某个类型的最大容量(仅用于切片和map)
new、make 均用于分配内存
new用于值类型和用户自定义类型,如自定义结构
make用于内置引用类型,如切片、map 和 管道
它们的用法类似函数,但是是将类型作为参数:new(type)、make(type)
new(T)分配类型T的零值并返回其地址,也就是指向类型T的指针,它也可以用于基本类型: v:=new(int)
make(T)返回类型T的初始化之后的值,因此它比new做更多的工作。
new()是一个函数,不要忘记它的括号
copy、append 用于复制和连接切片
panic、recover 用于错误处理机制
print、println 底层打印函数(部署环境中建议使用fmt包)
complex、real imag 用于创建和操作复数

函数进阶

参数传递机制

  1. package main
  2. import "fmt"
  3. /*
  4. 传指针的好处:
  5. 1. 传指针使得多个函数能操作同一个对象
  6. 2. 传指针比较轻量级(8B),毕竟只是传内存地址,可以用指针传递体积大的结构体
  7. 函数调用时,像切片slice、字典map、接口interface、通道channel这样的引用类型都是默认使用引用传递的(即使没有显式地指出指针)
  8. channel、slice、map这三种类型的实现机制类似指针可直接传递而不用传地址。不过若函数需要改变slice的长度,则仍需要取址传递指针
  9. 3. 传指针赋予了函数直接修改外部变量的能力,所以被修改的变量不再需要使用return返回
  10. */
  11. func main() {
  12. x := 3
  13. fmt.Println("x =", x, "&x =", &x) // x = 3 &x = 0xc000014098
  14. y := add(x)
  15. fmt.Println("x =", x, "&y =", &y) // x = 3 &y = 0xc0000140b8
  16. z := addP(&x)
  17. fmt.Println("x =", x, "&z =", &z) // x = 4 &z = 0xc0000140e0
  18. fmt.Println("&x =", &x) // &x = 0xc000014098
  19. }
  20. func add(a int) int {
  21. a++
  22. return a
  23. }
  24. func addP(a *int) int {
  25. *a++
  26. return *a
  27. }
  1. package main
  2. import "fmt"
  3. // 传指针直接修改外部变量,不需要return
  4. func main() {
  5. n := 0
  6. res := &n
  7. multiply(2, 4, res)
  8. fmt.Println("Result:", *res) // Result: 8
  9. }
  10. func multiply(a, b int, res *int) {
  11. *res = a * b
  12. }

defer与跟踪

用途:IO操作、错误处理

  1. package main
  2. import "fmt"
  3. func main() {
  4. fmt.Println("return:", a())
  5. }
  6. func a() int {
  7. var i int
  8. defer func() {
  9. i++
  10. fmt.Println("defer2:", i) // 顺序 2
  11. }()
  12. defer func() {
  13. i++
  14. fmt.Println("defer1:", i) // 顺序 1
  15. }()
  16. return i // 准备返回i(0),但是需要等到defer执行完 顺序3
  17. }
  18. /*
  19. defer1: 1
  20. defer2: 2
  21. return: 0
  22. 解释:返回0而不是2,这是因为返回值没有被声明,所以函数a()的返回值还是0
  23. */
  1. package main
  2. import "fmt"
  3. func main() {
  4. fmt.Println("return:", a())
  5. }
  6. func a() (i int) {
  7. defer func() {
  8. i++
  9. fmt.Println("defer2:", i) // 顺序 2
  10. }()
  11. defer func() {
  12. i++
  13. fmt.Println("defer1:", i) // 顺序 1
  14. }()
  15. return i // 顺序 3
  16. }
  17. /*
  18. defer1: 1
  19. defer2: 2
  20. return: 2
  21. 解释:返回2而不是0,这是因为返回值已被声明,即defer可以调用到真实的返回值,
  22. 因此defer在return赋值返回值i之后,再一次修改了i的值,最终函数退出后的返回值才是defer修改过的值
  23. */
  1. /*
  2. defer后的表达式不能执行操作语句,必须是函数调用
  3. return的实现逻辑:
  4. 1. 给返回值赋值(若为有名返回值则直接赋值,若为匿名返回值则先声明再赋值)
  5. 2. 调用RET返回指令并传入返回值,而RET则会检查defer是否存在,若存在就先逆序插播defer语句
  6. 3. 最后RET携带返回值退出函数
  7. */

可以看出,return并不是一个原子操作,函数返回值与return返回值并不一定一致。

因此,defer、return、返回值三者的执行顺序应该是:

  1. return最先给返回值赋值;
  2. defer开始执行收尾工作;
  3. RET指令携带返回值退出函数

defer声明时会先计算确定参数的值,defer推迟执行的仅是其函数体,因此defer语句位置并非随意,defer的初始化还是受到外部影响的。

错误与恢复