首页 > 解决方案 > 函数调用的 Go 类型

问题描述

关键字 likegodeferexpect 将函数调用作为参数。是否有可以以相同方式使用的类型?(例如,编写一个期望函数调用的函数 - 与函数相反 - 作为参数)。

标签: go

解决方案


不,那里没有。你不能对你的函数做同样的事情。

godefer受到语言规范的支持,并且规则由编译器强制执行。

您可以做的是使用函数类型的变量/值,您可以稍后/随时调用它,就好像它是一个函数一样。

例如:

func myFunc() {
    fmt.Println("hi")
}

func main() {
    var f func()
    f = myFunc
    f() // This calls the function value stored in f: myFunc in this example
}

编辑:要拥有您在评论中提到的功能:只需将函数调用及其参数包装在 a 中func(),然后使用 / 传递它。

例如:

func launch(f func()) {
    fmt.Println("Before launch")
    go func() {
        defer fmt.Println("After completion")
        f()
    }()
}

使用它:

func main() {
    launch(func() {
        fmt.Println("Hello, playground")
    })

    time.Sleep(time.Second)
}

哪些输出(在Go Playground上尝试):

Before launch
Hello, playground
After completion

是的,这不是一个确切的解决方法。如果参数可能更改,您必须在调用之前制作它们的副本launch(),并在函数文字(闭包)中使用副本,如下例所示:

s := "Hello, playground"

s2 := s // make a copy
launch(func() {
    fmt.Println(s2) // Use the copy
})
s = "changed"

模拟自动参数保存

对于具体的函数类型,我们可以构造一个帮助函数,它为我们提供自动参数保存。此辅助函数必须具有相同的签名,并且返回一个不带参数的函数。返回的函数是一个闭包,它使用参数调用原始函数。调用这个辅助函数的行为是保存参数的机制,所以用法和defer.

例如,助手fmt.Println(s)是:

func wrapPrintln(s string) func() {
    return func() {
        fmt.Println(s)
    }
}

并使用它:

launch(wrapPrintln(s))

int具有 2 个参数的函数示例:

func Sum(a, b int) {
    fmt.Println("Sum:", a+b)
}

func WrapSum(a, b int) func() {
    return func() {
        Sum(a, b)
    }
}

launch(WrapSum(a, b))

上面WrapPrintln()包裹WrapSum()了一个具体的函数,它不能用于其他函数(被包裹的函数是“连线的”)。我们也可以将包装函数作为参数:

func WrapFuncIntInt(f func(a, b int), a, b int) func() {
    return func() {
        f(a, b)
    }
}

我们可以这样使用它:

launch(WrapFuncIntInt(Sum, a, b))

在Go Playground上试试这个。

使用反射来避免手动复制

您可以使用反射来避免手动复制,但在这个解决方案中,我们实际上并没有调用函数,只是传递它。同样由于使用反射,它会变慢。另一个优点是这种“感觉”是通用的(我们可以使用具有不同签名的函数),但是我们失去了编译时的安全性。

func launch(f interface{}, params ...interface{}) {
    fmt.Println("Before launch")
    go func() {
        defer fmt.Println("After completion")
        pv := make([]reflect.Value, len(params))
        for i, v := range params {
            pv[i] = reflect.ValueOf(v)
        }
        reflect.ValueOf(f).Call(pv)
    }()
}

调用它的示例:

func main() {
    i, s := 1, "Hello, playground"

    launch(fmt.Printf, "%d %q\n", i, s)
    i, s = 2, "changed"

    time.Sleep(time.Second)
}

哪些输出(在Go Playground上尝试):

Before launch
1 "Hello, playground"
After completion

您可以利用自动参数保存的单一例外

我们可以使用一个例外。这是方法值。如果x具有静态类型T并且T方法集包含该方法M,我们可以使用x.M(不调用它)。

表达式x.M是一个方法值,它保存了一个副本,x当调用表达式的结果(它是一个函数值)时,该副本将用作接收者。

例子:

type myParams struct {
    format string
    i int
    s string
}

func (mp myParams) Call() {
    fmt.Printf(mp.format, mp.i, mp.s)
}

func main() {
    p := myParams{format: "%d %q\n", i: 1, s: "Hello, playground"}
    launch(p.Call) // p is saved here
    p.i, p.s = 2, "changed"

    time.Sleep(time.Second)
}

func launch(f func()) {
    fmt.Println("Before launch")
    go func() {
        defer fmt.Println("After completion")
        f()
    }()
}

它输出相同。在Go Playground上尝试一下。


推荐阅读