在 Golang 中用名字调用函数

mikespook · 2014-10-09 16:17:11 · 7036 次点击 · 预计阅读时间 2 分钟 · 大约8小时之前 开始浏览    
这是一个创建于 2014-10-09 16:17:11 的文章,其中的信息可能已经有所发展或是发生改变。

上个星期,我写了篇《Function call by name in Golang》。由于是英文的,所以被人诟病(说谁,谁知道!)。好吧,现在用中文重新写一遍。

Golang 中的函数跟 C 的一样,是个代码块,不过它可以像其他类型那样赋值给一个变量。

如果你对函数不熟悉,《Codewalk: First-Class Functions in Go》应该是个不错的起点。已经有所了解?那么继续吧!

首先,来看看这段 PHP 代码:

function foobar() {
    echo "Hello Golang\n";
}
$funcs = array(
    "foobar" => "foobar",
    "hello"  => "foobar",
);
$funcs["foobar"]();
$funcs["hello"]();

它会输出:

mikespook@mikespook-laptop:~/Desktop$ php foobar.php 
Hello Golang
Hello Golang

用这个方法调用匹配名字的函数,非常有效。

那么,在 Golang 中是否可能用函数的名字来调用某个函数呢?

作为一个静态、编译型语言,答案是否定的……又是肯定的

在 Golang 中,你不能这样做:

func foobar() {
    // bla...bla...bla...
}
funcname := "foobar"
funcname()

不过可以:

func foobar() {
    // bla...bla...bla...
}
funcs := map[string]func() {"foobar":foobar}
funcs["foobar"]()

但这里有一个限制:这个 map 仅仅可以用原型是“func()”的没有输入参数或返回值的函数。
如果想要用这个方法实现调用不同函数原型的函数,需要用到 interface{}。

是啊!interface{},跟 C 中的 void 指针类似。还记得这个东西吗?不记得了?没事,看看这个吧:《The Go Programming Language Specification:Interface types》。

这样,就可以添加有着不同函数原型的函数到一个 map 中:

func foo() {
    // bla...bla...bla...
}
func bar(a, b, c int) {
    // bla...bla...bla...
}
funcs := map[string]interface{}{"foo":foo, "bar":bar}

那么如何调用 map 中的函数呢?像这样吗:

funcs["foo"]()

绝对不行!这无法工作!你不能直接调用存储在空接口中的函数。

反射走进我们的生活!在 Golang 中有着叫做“reflect”的包。你是否了解反射了呢?
如果还没有,那么阅读一下这个:《Laws of reflection》吧。哦,这里有个中文版本:《反射的规则》。

func Call(m map[string]interface{}, name string, params ... interface{}) (result []reflect.Value, err error) {
    f = reflect.ValueOf(m[name])
    if len(params) != f.Type().NumIn() {
        err = errors.New("The number of params is not adapted.")
        return
    }
    in := make([]reflect.Value, len(params))
    for k, param := range params {
        in[k] = reflect.ValueOf(param)
    }
    result = f.Call(in)
    return
}
Call(funcs, "foo")
Call(funcs, "bar", 1, 2, 3)

将函数的值从空接口中反射出来,然后使用 reflect.Call 来传递参数并调用它。
没有什么是很难理解的。

我已经完成了一个包来实现这个功能,在这里:https://bitbucket.org/mikespook/golib/src/27c65cdf8a77/funcmap.

希望这有些帮助。好运,地鼠们!


有疑问加站长微信联系(非本文作者)

本文来自:mikespook 的博客

感谢作者:mikespook

查看原文:在 Golang 中用名字调用函数

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

7036 次点击  ∙  1 赞  
加入收藏 微博
8 回复  |  直到 2023-04-19 16:38:29
GuoYuefei
GuoYuefei · #1 · 6年之前

首先感谢下作者,这里提供的想法对我非常有用。

但这里有一个限制:这个 map 仅仅可以用原型是“func()”的没有输入参数或返回值的函数。

关于这点,其实是有解决方法的

type NewCipherModel func(b cipher.Block, iv []byte) cipher.BlockMode
type ModeFunc string

const (
    CBCDecrypter ModeFunc = "NewCBCDecrypter"
    CBCEncrypter ModeFunc = "NewCBCEncrypter"
)

funcs := map[ModeFunc] NewCipherModel {
        CBCEncrypter:cipher.NewCBCEncrypter,
        CBCDecrypter:cipher.NewCBCDecrypter,
}

直接将需要放入map中类型的type一个类型,当然也可以不用别名

嗯。。。当然这只可以运用在相同原型的函数。
还有一种方式,我是在看了下面反射的过程中想到的。。。事实上我现在还没学反射。所以我相当的方式其实和上面我想到的方式是相似的。因为没有用代码实现,所以不给出代码了。额额额,哇哈哈,发现不用代码说不来。。。其实终结下就是运用类型检查

import "fmt"

func main() {
        funcs := map[string] interface{}{
                "fun1":fun1,
                "fun2":fun2,
        }
        switch fun := funcs["fun2"].(type) {
        case func():fun()
        case func(i int):fun(1)
        }
}

func fun1(){
        fmt.Println("fun1")
}

func fun2(i int) {
        fmt.Println("fun2")
}
输出是:
$ go run test.go
fun2
chjiyun
chjiyun · #2 · 3年之前

不用map 可以做到吗,我要根据文件名调用某目录下的函数,文件数量未知,所以你这 map 有局限性,相当于写死了,有米有更动态的做法

chaleaoch
chaleaoch · #3 · 3年之前
chjiyunchjiyun #2 回复

不用map 可以做到吗,我要根据文件名调用某目录下的函数,文件数量未知,所以你这 map 有局限性,相当于写死了,有米有更动态的做法

找到解决办法了吗 我也遇到同样的问题。。。

zachary
zachary · #4 · 3年之前
chjiyunchjiyun #2 回复

不用map 可以做到吗,我要根据文件名调用某目录下的函数,文件数量未知,所以你这 map 有局限性,相当于写死了,有米有更动态的做法

不用map还有个方法就是用反射MethodByName再call,但是得func是对象的func,

karma617
karma617 · #5 · 2年之前

请问你解决了吗?我现在也遇到这个问题了

karma617
karma617 · #6 · 2年之前
chaleaochchaleaoch #3 回复

#2楼 @chjiyun 找到解决办法了吗 我也遇到同样的问题。。。

请问你解决了吗?我现在也遇到这个问题了

chjiyun
chjiyun · #7 · 2年之前
chaleaochchaleaoch #3 回复

#2楼 @chjiyun 找到解决办法了吗 我也遇到同样的问题。。。

把方法挂在结构体下面,利用反射方法 MethodByName,再call

chaleaoch
chaleaoch · #8 · 2年之前
karma617karma617 #6 回复

#3楼 @chaleaoch 请问你解决了吗?我现在也遇到这个问题了

没有. golang 不像python 和 java有这个能力. 可以通过golang plugin 也就是RPC 实现. 不过我没那么用.

添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传