如何在Golang中掌握函数指针_Golang函数变量与调用示例

Go函数类型需显式声明完整签名,参数与返回值类型必须完全匹配;支持赋值、传参、返回及闭包,但比较仅限nil或同一匿名函数实例,类型别名可提升可读性但不改变兼容规则。

函数类型声明必须显式写出参数和返回值

Go 里没有“函数指针”这个独立类型,只有函数类型(func(int) string 这种),它本身就可以赋值给变量、作为参数传入、从函数返回。关键在于:类型签名必须完全匹配,包括参数顺序、类型、数量,以及返回值个数和类型。

常见错误是以为 func()func() int 可以互相赋值——它们是完全不同的类型,编译直接报错:cannot use ... (type func()) as type func() int in assignment

  • 声明函数变量时,类型要写全:var f func(string, int) bool
  • 用类型推导时,右边必须是具名函数或字面量,且签名一致:f := myFunc // myFunc 必须是 func(string, int) bool
  • 匿名函数赋值需显式标注类型或确保上下文可推导:f = func(s string, n int) bool { return len(s) > n }

把函数当参数传给另一个函数的写法

这是最常遇到的场景,比如回调、策略模式。接收方函数签名里要明确写出函数类型的形参,不能只写 func 或漏掉括号。

func apply(op func(int, int) int, a, b int) int {
    return op(a, b)
}

func main() {
    add := func(x, y int) int { return x + y }
    result := apply(add, 10, 5) // ✅ 正确
}

容易踩的坑:

  • 传的是函数值(add),不是调用结果(add(1,2)
  • 如果函数有多个返回值,接收方参数类型也必须匹配全部返回值,哪怕你只关心第一个
  • 闭包捕获外部变量时要注意生命周期——只要函数变量还活着,被捕获的变量就不会被 GC

函数变量支持比较但仅限于 nil 和相同函数字面量

Go 允许用 == 比较两个函数变量,但结果非常受限:只有都为 nil,或指向**同一个函数字面量**(注意:不是同一类签名,而是内存中同一个匿名函数实例)时才为 true

fn1 := func() {}
fn2 := func() {}
fmt.Println(fn1 == fn2) // ❌ false,即使内容一样,也是不同实例

var f1, f2 func()
fmt.Println(f1 == f2) // ✅ true,都是 nil

f1 = func() {}
f2 = f1
fmt.Println(f1 == f2) // ✅ true,同一变量赋值

这意味着你不能靠函数相等来判断“逻辑相同”,也不能拿它做 map key(除非是 nil 或固定变量引用)。真要区分行为,得靠额外标识或接口封装。

函数类型别名能提升可读性但不解决签名兼容问题

当函数签名复杂时(比如带多个 error 返回、context 参数),定义类型别名能让代码更清晰,但它只是别名,不是新类型——和原函数类型完全等价,可以自由赋值。

type HandlerFunc func(context.Context, *http.Request) error
type Middleware func(HandlerFunc) HandlerFunc

func logging(h HandlerFunc) HandlerFunc {
    return func(ctx context.Context, r *http.Request) error {
        log.Println("before")
        return h(ctx, r)
    }
}

注意点:

  • 别名不能绕过签名检查:把 func(string) int 赋给 type MyFn func(int) string 依然编译失败
  • 别名在反射中仍显示为原始函数类型,不影响运行时行为
  • 如果函数签名涉及泛型(Go 1.18+),类型别名必须包含完整类型参数,例如 type Mapper[T, U any] func(T) U

真正难的不是语法,是设计时想清楚:这个函数变量该持有哪些上下文?要不要支持取消?错误是否需要统一包装?这些决定了签名里要不要加 context.Context...error 或自定义错误类型。