跳至主要內容

函数

Mr.Liu大约 7 分钟Go

函数

函数定义

函数是组织好的、可重复使用的、用于执行指定任务的代码块

Go语言支持:函数、匿名函数和闭包

Go语言中定义函数使用func关键字,具体格式如下:

func 函数名(参数)(返回值) {
    函数体
}

其中:

  • 函数名:由字母、数字、下划线组成。但函数名的第一个字母不能是数字。在同一个包内,函数名不能重名

  • 可变参数:是指函数的参数数量不固定。可变参数通过在参数名后面加... 来标识。可变参数通常要作为函数的最后一个参数

  • 返回值:

    • 支持多返回值
    • 支持返回值命名,可以在函数体中直接使用这些返回值变量,并且不需要显式地在 return​ 语句中指定返回值
package main

import (
    "fmt"
)

func sumFn(x ...int) (n int, sum int) {
    for _, num := range x {
        sum = sum + num
        n++
    }
    // return n, sum  
    return  // 已经定义过返回值变量,因此是否返回数据均可
}

func main() {
    var nums = []int{1, 2, 3, 4, 5}
    n, result := sumFn(nums...) // 将nums切片拆包
    fmt.Printf("%d个数之和是%d", n, result)
}

函数类型和变量

普通函数

我们可以使用type关键字来定义一个函数类型,具体格式如下

type MathFunc func(int, int) int

上面语句定义了一个MathFunc​类型,它是一种函数类型,这种函数接收两个int​类型的参数并且返回一个int​类型的返回值。

简单来说,凡是满足这两个条件的函数都是MathFunc​类型的函数

package main

import "fmt"

// 普通函数类型
type MathFunc func(int, int) int

func add(x, y int) int {
    return x + y
}

func subtract(x, y int) int {
    return x - y
}

func main() {
    // 函数类型变量
    var operation MathFunc

    operation = add
    result := operation(5, 3)
    fmt.Println("Add Result:", result)

    operation = subtract
    result = operation(10, 4)
    fmt.Println("Subtract Result:", result)
}

匿名函数

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

package main

import "fmt"

func main() {
    // 匿名函数类型
    add := func(x, y int) int {
        return x + y
    }

    subtract := func(x, y int) int {
        return x - y
    }

    result := add(5, 3)
    fmt.Println("Add Result:", result)

    result = subtract(10, 4)
    fmt.Println("Subtract Result:", result)

    func () {
	fmt.Println("匿名自执行函数")
    }()
}

函数类型作为参数

package main

import "fmt"

// 普通函数类型
type MathFunc func(int, int) int

// 函数类型作为参数
func processFunc(x, y int, op MathFunc) int {
    return op(x, y)
}

func add(x, y int) int {
    return x + y
}

func subtract(x, y int) int {
    return x - y
}

func main() {
    result := processFunc(5, 3, add)
    fmt.Println("Add Result:", result)

    result = processFunc(10, 4, subtract)
    fmt.Println("Subtract Result:", result)
}

函数类型作为返回值

package main

import (
	"fmt"
	"errors"
)

// 定义运算函数类型
type Operator func(int, int) (int, error)

// 根据操作符返回对应的运算函数
func getOperator(op string) Operator {
	switch op {
	case "+":
		return func(a, b int) (int, error) {
			return a + b, nil
		}
	case "-":
		return func(a, b int) (int, error) {
			return a - b, nil
		}
	case "*":
		return func(a, b int) (int, error) {
			return a * b, nil
		}
	case "/":
		return func(a, b int) (int, error) {
			if b == 0 {
				return 0, errors.New("division by zero")
			}
			return a / b, nil
		}
	default:
		return nil
	}
}

func main() {
	// 获取运算函数
	add := getOperator("+")
	subtract := getOperator("-")
	multiply := getOperator("*")
	divide := getOperator("/")

	// 进行四则运算
	a, b := 10, 2
	fmt.Printf("%d + %d = %d\n", a, b, calculate(add, a, b))
	fmt.Printf("%d - %d = %d\n", a, b, calculate(subtract, a, b))
	fmt.Printf("%d * %d = %d\n", a, b, calculate(multiply, a, b))
	fmt.Printf("%d / %d = %d\n", a, b, calculate(divide, a, b))
}

// 使用运算函数进行计算
func calculate(op Operator, a, b int) (int, error) {
	if op == nil {
		return 0, errors.New("invalid operator")
	}
	return op(a, b)
}

闭包函数

闭包是指一个函数与其相关的引用环境一起构成一个封闭的活动单元,从而可以访问引用环境中的变量。

闭包可以理解成 “定义在一个函数内部的函数”。在本质上,闭包就是将函数内部 和 函数外部连接起来的桥梁。或者说是函数和其引用环境的组合体。

  • 闭包是指有权访问另一个函数作用域中的变量的函数
  • 创建闭包的常见的方式就是在一个函数内部创建另一个函数,通过另一个函数访问这个函数的局部变量

注意:由于闭包里作用域返回的局部变量资源不会被立刻销毁,所以可能会占用更多的内存,过度使用闭包会导致性能下降,建议在非常有必要的时候才使用闭包。

package main

import "fmt"

func counter() func() int {
	count := 0
	return func() int {
		count++
		return count
	}
}

func main() {
	// 创建闭包函数
	c := counter()

	// 调用闭包函数多次
	fmt.Println(c()) // 输出: 1
	fmt.Println(c()) // 输出: 2
	fmt.Println(c()) // 输出: 3
}

装饰器函数

在 Go 语言中,虽然没有直接的装饰器语法,但我们可以使用函数作为参数和返回值的特性,实现类似装饰器的效果。

装饰器函数是一种高阶函数,用于在不改变原函数代码的情况下,为函数添加额外的功能。

package main

import (
	"fmt"
	"time"
)

// 原始函数
func greet(name string) {
	fmt.Printf("Hello, %s!\n", name)
}

// 装饰器函数
func withTimeLogging(fn func(string)) func(string) {
	return func(name string) {
		start := time.Now()
		fn(name)
		fmt.Println("Time taken:", time.Since(start))
	}
}

func main() {
	// 使用装饰器函数包装原始函数
	greetWithLogging := withTimeLogging(greet)

	// 调用包装后的函数
	greetWithLogging("Alice")
}

defer语句

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

// defer函数
fmt.Println("1")
defer fmt.Println("2")
fmt.Println("3")
fmt.Println("4")

defer将会延迟执行

1
3
4
2

如果有多个defer修饰的语句,将会逆序进行执行

// defer函数
fmt.Println("1")
defer fmt.Println("2")
defer fmt.Println("3")
fmt.Println("4")

运行结果

1
4
3
2

如果需要用defer运行一系列的语句,那么就可以使用匿名函数

func main() {
	fmt.Println("开始")
	defer func() {
		fmt.Println("1")
		fmt.Println("2")
	}()
	fmt.Println("结束")
}

运行结果

开始
结束
1
2

在Go语言的函数中return语句在底层并不是原子操作,它分为返回值赋值和RET指令两步。而defer语句执行的时机就在返回值赋值操作后,RET指令执行前,具体如下图所示

image-20200720220700249
image-20200720220700249

panic/revocer处理异常

在 Go 语言中,panic​ 和 recover​ 是用于处理程序异常和恢复的两个关键字。它们用于在程序运行时处理错误情况,以便程序能够更加优雅地处理异常并继续执行。以下是对 panic​ 和 recover​ 的详细解释以及示例应用情况:

  1. panicpanic​ 是一个内建函数,用于触发程序的紧急错误情况。当程序出现无法继续执行的错误时,可以使用 panic​ 中断程序的正常流程。一旦触发了 panic​,程序将会立即停止执行当前函数以及在调用堆栈中的其他函数,并开始执行一些清理操作(如果有的话),然后退出程序。
  2. recoverrecover​ 是一个内建函数,用于在 defer​ 函数中捕获 panic​ 引发的错误。它可以将程序从 panic​ 的状态中恢复过来,以便程序可以继续执行后续操作。通常在 defer​ 函数中使用 recover​ 来捕获 panic​,并进行一些错误处理或恢复操作,以确保程序不会崩溃。
package main

import (
    "fmt"
)

func recoverFromPanic() {
    if r := recover(); r != nil {
        fmt.Println("Recovered:", r)
    }
}

func divide(a, b int) int {
    defer recoverFromPanic()

    if b == 0 {
        panic("division by zero")
    }
    return a / b
}

func main() {
    result := divide(10, 2)
    fmt.Println("Result:", result)

    result = divide(10, 0)
    fmt.Println("Result:", result) // 输出: Result: 0
}