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