Go 零基础教程

Go 函数类型和函数值

在 Go 语言中,函数拥有至高无上的地位——它们是一等公民 (First-class citizens)

这句话的实际意义是:函数并不只是一段静态的、等待被调用的代码块。函数可以像普通的整数 (int) 或字符串 (string) 一样被当成数据(值)来对待。 你可以把函数赋值给变量,可以把它作为参数传给另一个函数,甚至可以让一个函数返回另一个函数。

1. 深入理解函数类型 (Function Types)

就像 int 代表整数类型一样,函数也有自己的“类型”。函数类型 (Function Type) 由函数的签名 (Signature) 决定,即:它接收什么类型的参数,以及返回什么类型的值。函数的名字并不包含在函数类型中。

1.1 函数类型的基本语法

定义函数类型的通用格式如下:

func(参数类型列表) (返回值类型列表)

让我们看几个具体的例子:

场景 1:基础的加法运算类型

一个接收两个 int 并返回一个 int 的函数,它的类型是:func(int, int) int

package main

import "fmt"

// 定义一个普通的加法函数
func add(x int, y int) int {
	return x + y
}

func main() {
	// 1. 声明一个变量 'operation',它的类型是 func(int, int) int
	var operation func(int, int) int
	
	// 2. 将 'add' 函数本身(不带括号,不是调用)赋值给这个变量
	operation = add
	
	// 3. 通过变量名来调用这层“皮”下面的真实函数
	result := operation(5, 3)
	fmt.Println(result) // 输出: 8
}

场景 2:无参数无返回值的类型

一个什么都不接收,也什么都不返回的函数(比如仅仅用来打印一句话),它的类型是:func()

package main

import "fmt"

func greet() {
	fmt.Println("Hello, world!")
}

func main() {
	var greeting func() // 声明一个无参无返回值的函数变量
	greeting = greet    // 赋值
	greeting()          // 执行,输出: Hello, world!
}

场景 3:带错误处理的多返回值类型

比如上节课讲过的除法函数,接收两个 int,返回一个 int 和一个 error。它的类型是:func(int, int) (int, error)

2. 函数值 (Function Values) 的奇妙用法

既然函数可以作为值赋给变量,那么它们自然就可以在程序中自由穿梭。这催生了几种极其强大的编程模式。

2.1 将函数作为参数传递 (回调函数 Callback)

你可以将一个函数传给另一个函数,让接收方在特定的时机去执行它。这在实现自定义排序、事件监听或处理异步任务时非常常见。

package main

import "fmt"

// calculate 函数接收三个参数:两个数字,以及一个“定义了计算规则”的函数 op
func calculate(x int, y int, op func(int, int) int) int {
	return op(x, y) // 在内部执行传进来的逻辑
}

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

func main() {
    // 动态注入不同的计算逻辑
	sum := calculate(5, 3, add)
	difference := calculate(5, 3, subtract)
	
	fmt.Println("加法结果:", sum)       // 输出: 加法结果: 8
	fmt.Println("减法结果:", difference) // 输出: 减法结果: 2
}

2.2 让函数返回函数 (生成器/闭包工厂)

我们在上一章的闭包中已经见识过这种用法。这就像是一个“制造函数的工厂”。

package main

import "fmt"

// multiplier 接收一个倍数 factor,返回一个专门做乘法的函数
func multiplier(factor int) func(int) int {
	return func(x int) int {
		return x * factor
	}
}

func main() {
	double := multiplier(2) // 制造一个“翻倍”函数
	triple := multiplier(3) // 制造一个“三倍”函数
	
	fmt.Println("5 的两倍:", double(5)) // 输出: 10
	fmt.Println("5 的三倍:", triple(5)) // 输出: 15
}

3. 实战案例演示

让我们看三个更贴近真实工程的例子,感受函数类型的威力。

3.1 案例 1:使用 type 关键字简化函数签名 (策略模式)

当一个函数签名很长(比如 func(int, int) int)并且经常被使用时,代码会变得很乱。我们可以用 type 关键字为这个函数签名取一个简短的别名。这在实现“策略模式”时非常清爽。

package main

import "fmt"

// 为这种特定签名的函数取个别名,叫 Operation
type Operation func(int, int) int

// 现在的函数签名看起来干净多了
func calculate(x int, y int, op Operation) int {
	return op(x, y)
}

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

func main() {
	// 把不同的函数塞进一个 Map 里,实现动态路由
	operations := map[string]Operation{
		"add":      add,
		"multiply": multiply,
	}

	x, y := 10, 5
	fmt.Printf("%d + %d = %d\n", x, y, calculate(x, y, operations["add"]))      
	fmt.Printf("%d * %d = %d\n", x, y, calculate(x, y, operations["multiply"])) 
}

3.2 案例 2:自定义排序逻辑

Go 标准库的 sort.Slice 完美诠释了函数参数的威力。它不需要知道你要怎么排,它只要你传给它一个“比较规则”的函数。

package main

import (
	"fmt"
	"sort"
)

func main() {
	numbers := []int{5, 2, 8, 1, 9, 4}

	// 传入一个匿名函数,定义升序规则 (如果前面的数小于后面的数,返回 true)
	sort.Slice(numbers, func(i, j int) bool {
		return numbers[i] < numbers[j]
	})
	fmt.Println("升序排列:", numbers) // 输出: [1 2 4 5 8 9]

	// 换一个匿名函数,瞬间变成降序规则
	sort.Slice(numbers, func(i, j int) bool {
		return numbers[i] > numbers[j]
	})
	fmt.Println("降序排列:", numbers) // 输出: [9 8 5 4 2 1]
}

3.3 案例 3:Web 开发中的中间件 (Middleware) 模式

在 Go 语言的 Web 开发框架中,“中间件”几乎全是用高阶函数实现的。它的核心思想是:接收一个核心处理函数,在它的外层包上一层逻辑(比如记录日志、验证权限),然后再把包装好的新函数返回出去。

package main

import "fmt"

// 定义 HTTP 核心处理函数的类型
type HttpHandler func(request string) string

// 定义中间件的类型:接收一个 Handler,返回一个新的被包装过的 Handler
type Middleware func(HttpHandler) HttpHandler

// 日志中间件
func logger(next HttpHandler) HttpHandler {
	return func(request string) string {
		fmt.Println(">>> 收到请求:", request) // 请求到达时的前置逻辑
		response := next(request)            // 调用核心处理逻辑
		fmt.Println("<<< 发送响应:", response) // 核心处理完后的后置逻辑
		return response
	}
}

// 最终的核心业务逻辑
func mainHandler(request string) string {
	return "Hello, 你的请求内容是 " + request
}

func main() {
	// 将 logger 中间件套在 mainHandler 外面
	wrappedHandler := logger(mainHandler)

	// 执行被包装后的函数
	wrappedHandler("查询用户数据")
}

运行结果将完美展示中间件在核心逻辑前后拦截处理的能力。