GO函数的特殊性

1 函数作为实参

Go 语言中,函数是一等公民(First-class citizen),可以像普通变量一样被赋值、传递和使用。将函数作为实参传递给另一个函数,是 Go 中常见的编程模式,常用于回调、策略模式等场景。

1.1 函数赋值给变量

函数可以赋值给一个变量,通过该变量来调用函数:

go 复制代码
package main

import (
    "fmt"
    "math"
)

func main() {
    /* 将匿名函数赋值给变量 getSquareRoot */
    getSquareRoot := func(x float64) float64 {
        return math.Sqrt(x)
    }

    /* 通过变量调用函数 */
    fmt.Println(getSquareRoot(9))   // 输出:3
    fmt.Println(getSquareRoot(16))  // 输出:4
}

以上代码执行结果为:

3

4

1.2 函数作为实参

将函数作为实参传递给另一个函数时,接收方需要声明对应的函数类型参数。以下示例定义了一个 apply 函数,接收一个操作函数和两个操作数,对两个数执行任意指定的操作:

go 复制代码
package main

import (
    "fmt"
    "math"
)

/* 定义一个函数,接收一个函数类型的参数 op */
/* op 的类型为 func(float64, float64) float64,表示接受两个 float64 参数、返回 float64 的函数 */
func apply(op func(float64, float64) float64, a, b float64) float64 {
    return op(a, b)
}

func main() {
    /* 定义加法函数 */
    add := func(a, b float64) float64 {
        return a + b
    }

    /* 定义乘法函数 */
    multiply := func(a, b float64) float64 {
        return a * b
    }

    /* 将 add 函数作为实参传入 apply */
    fmt.Println(apply(add, 3, 4))       // 输出:7

    /* 将 multiply 函数作为实参传入 apply */
    fmt.Println(apply(multiply, 3, 4))  // 输出:12

    /* 也可以直接传入匿名函数 */
    fmt.Println(apply(func(a, b float64) float64 {
        return math.Pow(a, b) // a 的 b 次方
    }, 2, 10))                          // 输出:1024
}

以上代码执行结果为:

go 复制代码
7
12
1024

1.3 函数作为实参的优势

(1)实现回调:这是最直接的好处,让你可以把某个行为"注入"到另一个函数中。

go 复制代码
// 通用的排序函数,排序逻辑由调用方决定
func sort(numbers []int, compare func(int, int) bool) {
    for i := 0; i < len(numbers); i++ {
        for j := i + 1; j < len(numbers); j++ {
            if compare(numbers[i], numbers[j]) {
                numbers[i], numbers[j] = numbers[j], numbers[i]
            }
        }
    }
}

// 调用时可以传入不同的排序策略
numbers := []int{5, 2, 8, 1, 9}
sort(numbers, func(a, b int) bool { return a > b })  // 降序
sort(numbers, func(a, b int) bool { return a < b })  // 升序
sort(numbers, func(a, b int) bool { return a%2 > b%2 }) // 自定义

(2)高阶函数(编程范式):函数式编程的核心特性,让你可以组合、变换行为。

go 复制代码
// 返回一个函数(闭包),实现了"配置"的效果
func multiplier(factor int) func(int) int {
    return func(x int) int {
        return x * factor
    }
}

// 使用
double := multiplier(2)
triple := multiplier(3)

fmt.Println(double(5))  // 10
fmt.Println(triple(5))  // 15

// 可以继续组合
apply := func(f func(int) int, val int) int {
    return f(val)
}
fmt.Println(apply(double, 10))  // 20

(3)策略模式(设计模式)不同的策略作为函数传递,而不是定义接口和多个实现类。

go 复制代码
type PaymentStrategy func(amount float64) error

// 不同的支付策略
func creditCardPay(amount float64) error {
    fmt.Printf("Pay %.2f via Credit Card\n", amount)
    return nil
}

func paypalPay(amount float64) error {
    fmt.Printf("Pay %.2f via PayPal\n", amount)
    return nil
}

// 执行支付,策略可以随时切换
func processOrder(amount float64, strategy PaymentStrategy) {
    if err := strategy(amount); err != nil {
        fmt.Println("Payment failed:", err)
    }
}

// 使用
processOrder(100.50, creditCardPay)  // 信用卡支付
processOrder(50.25, paypalPay)       // PayPal支付

(4)中间件模式(Web框架):这是 Go Web 开发中最常见的模式

go 复制代码
// 中间件:接受一个 Handler,返回一个新的 Handler
type Handler func(req string) string

func loggerMiddleware(next Handler) Handler {
    return func(req string) string {
        fmt.Println("Request:", req)
        result := next(req)
        fmt.Println("Response:", result)
        return result
    }
}

func authMiddleware(next Handler) Handler {
    return func(req string) string {
        if req == "" {
            return "unauthorized"
        }
        return next(req)
    }
}

// 业务逻辑
func helloHandler(req string) string {
    return "Hello, " + req
}

// 链式调用
handler := loggerMiddleware(authMiddleware(helloHandler))
fmt.Println(handler("Alice"))
// 输出:
// Request: Alice
// Response: Hello, Alice
// Hello, Alice

2 闭包

Go 语言支持匿名函数,可作为闭包。匿名函数是一个"内联"语句或表达式。匿名函数的优越性在于可以直接使用函数内的变量,不必申明。

匿名函数是一种没有函数名的函数,通常用于在函数内部定义函数,或者作为函数参数进行传递。

闭包的特性:

  • 函数可以"记住"它被创建时的环境(变量 i)
  • 每次调用外层函数,都会创建独立的闭包(各自有自己的 i)
  • 闭包中的变量可以在多次调用之间保持状态(累加效果)
  • 不同的闭包之间完全隔离(互不影响)

以下实例中,我们创建了函数 getSequence() ,返回另外一个函数。该函数的目的是在闭包中递增 i 变量,代码如下:

go 复制代码
package main

import "fmt"

func getSequence() func() int {
   i:=0
   return func() int {
      i+=1
     return i  
   }
}

func main(){
   /* nextNumber 为一个函数,函数 i 为 0 */
   nextNumber := getSequence()  

   /* 调用 nextNumber 函数,i 变量自增 1 并返回 */
   fmt.Println(nextNumber())
   fmt.Println(nextNumber())
   fmt.Println(nextNumber())
   
   /* 创建新的函数 nextNumber1,并查看结果 */
   nextNumber1 := getSequence()  
   fmt.Println(nextNumber1())
   fmt.Println(nextNumber1())
}

代码结果:

go 复制代码
1
2
3
1
2

跟我理解的函数不太一样啊,这个闭包明明在函数里面,生命周期为什么不随函数的关闭而结束?

你这个疑问非常精准,触及了编程语言设计中一个核心的概念:变量的生命周期(Scope)与内存分配(Variable capture)。

你直觉上认为"函数内的变量应该随函数结束而销毁",这是 C、Java、Python 等语言中局部变量(Variable on

Stack)的标准行为。 但 Go 语言在这里做了一个非常重要的优化和设计决策:变量逃逸( Escape Analysis )。

简单直接的回答是:因为编译器检测到 i

在函数返回后仍然会被使用,所以没有把它分配到栈上,而是分配到了堆上。它的生命周期就不再绑定在函数调用上了,而是绑定在了闭包这个对象上。

以下实例我们定义了多个匿名函数,并展示了如何将匿名函数赋值给变量、在函数内部使用匿名函数以及将匿名函数作为参数传递给其他函数。

复制代码
  package main

  import "fmt"

  func main() {
// 定义一个匿名函数并将其赋值给变量add
add := func(a, b int) int {
    return a + b
}

// 调用匿名函数
result := add(3, 5)
fmt.Println("3 + 5 =", result)

// 在函数内部使用匿名函数
multiply := func(x, y int) int {
    return x * y
}

product := multiply(4, 6)
fmt.Println("4 * 6 =", product)

// 将匿名函数作为参数传递给其他函数
calculate := func(operation func(int, int) int, x, y int) int {
    return operation(x, y)
}

sum := calculate(add, 2, 8)
fmt.Println("2 + 8 =", sum)

// 也可以直接在函数调用中定义匿名函数
difference := calculate(func(a, b int) int {
    return a - b
}, 10, 4)
fmt.Println("10 - 4 =", difference) }

3 函数方法

GO语言没有面向对象,它用结构体代表对象的属性,用函数方法代表对象的方法。

go 复制代码
package main

import (
   "fmt"  
)

/* 定义结构体 */
type Circle struct {
  radius float64
}

func main() {
  var c1 Circle
  c1.radius = 10.00
  fmt.Println("圆的面积 = ", c1.getArea())
}

//该 method 属于 Circle 类型对象中的方法
func (c Circle) getArea() float64 {
  //c.radius 即为 Circle 类型对象中的属性
  return 3.14 * c.radius * c.radius
}

// 执行结果:圆的面积 =  314
相关推荐
AI科技星1 小时前
《全域数学》第三卷:代数原本 · 全书详述【乖乖数学】
开发语言·人工智能·机器学习·数学建模
时空系1 小时前
第10篇:归属权与借用——Rust的安全保障 Rust中文编程
开发语言·安全·rust
AI进化营-智能译站1 小时前
ROS2 C++开发系列13-运算符重载让ROS2消息处理更自然
java·开发语言·c++·ai
时空系1 小时前
第6篇:数据容器——管理大量数据 Rust中文编程
开发语言·后端·rust
eLIN TECE2 小时前
Go基础之环境搭建
开发语言·后端·golang
念何架构之路2 小时前
Go反射应用技巧
开发语言·后端·golang
shjita2 小时前
java根据键值对中值的大小进行排序的手法。
java·开发语言·servlet
司南-70492 小时前
Dense结构下的 大模型系统架构研究
服务器·人工智能·后端
GISer_Jing2 小时前
AI全栈转型_TS后端学习路线
前端·人工智能·后端·学习