Lua - 将具有参数的函数调用添加到堆栈中并以后调用它们

我知道我可以像在这里描述的那样,在表格中存储函数引用并带参数调用它们。但我需要为每次调用在表格中存储参数。我该怎么做?

为了解释我想干什么,我想写一个控制行为类。如果您正在计算控制力,您可以调用不同的函数,例如寻找(target)或追逐(target)。

我希望能够“收集”所有的函数调用并在结束时执行它们(遍历表并执行存储的每个函数与参数)或取消一切。

这是可能的吗?

点赞
用户2988
用户2988

如果你想在一个表中存储一个带有参数的函数,并在以后使用那些参数调用该函数,那么,你只需将参数与函数一起存储在表中,然后将它们作为参数传递:

functions_with_parameters = {
  {
    f = function (a, b) return a + b end,
    args = { 1, 2 }
  },
  {
    f = function (a, b) return a - b end,
    args = { 100, 90 }
  }
}

for _, a in pairs(functions_with_parameters) do
  print(a.f(a.args[1], a.args[2]))
end
// 3
// 10
2017-05-01 13:20:32
用户3735873
用户3735873

另一种(可能更简洁)的替代方案:

function xxx(s1,s2,s3)
  print(s1,s2,s3)
end

t = {}
t[#t+1] = { xxx, {'a','b','c'}}
t[#t+1] = { xxx, {'x','y','z'}}

for _,f in ipairs(t) do
  f[1](table.unpack(f[2]))
end
2017-05-01 15:38:57
用户805875
用户805875

下面是我的实现方式:

-- 把一个函数和参数打包成一个表
function record(f, ...)
    return {func = f, n = select('#', ...), ...}
end

-- 执行一个经过记录的函数列表
function run(list)
    for _, callinfo in ipairs(list) do
        callinfo.func(table.unpack(callinfo, 1, callinfo.n))
    end
end

使用示例:

-- 创建一个待执行的函数列表
todo = {}
todo[#todo+1] = record(print, "foo", "blah", nil, 23)
todo[#todo+1] = record(print, "baz")
-- 执行这个列表
run(todo)
-->  foo    blah    nil 23
-->  baz

还可以在 run 中使用 pcall,遇到错误不会中断执行;或者添加另一个函数,接受 (list, f, ...) 形式的参数,把调用信息打包进列表并附加到列表末尾。


如果可以保证参数列表中没有 nil,可以简化成以下形式:

-- 把一个函数和参数打包成一个表
function record(f, ...)
    return {func = f, ...}
end

-- 执行一个经过记录的函数列表
function run(list)
    for _, callinfo in ipairs(list) do
        callinfo.func(table.unpack(callinfo))
    end
end

不过我强烈建议只在最后面进行这种优化,确保其他代码都没有问题,并且经过测量发现这种方式确实慢。如果程序有 bug,会在参数列表中引入意外的 nil,导致这个版本修改了参数列表(删掉了一些元素),而前面那个版本则无论出现什么情况都不会修改参数列表,更加便于调试。


如果代码空间太小,可以使用以下方式节省一个值的空间。不过需要注意,这种方式会改变参数个数,所以可能会浪费空间。

附注: 除了 Lua 5.2,这种方式似乎比前面两种方式快一些,见下面的评论。)

--(你实际上可以就这样写)
function record(f, ...)  return {f, ...}  end

-- 执行一个经过记录的函数列表
function run(list)
    for _, callinfo in ipairs(list) do
        callinfo[1](table.unpack(callinfo, 2))
    end
end

取决于参数个数的不同,这种方式可能会浪费空间,甚至比前面的方法更费空间。(对于 0、1、3 或 7 个参数,Vanilla(PUC-Rio)Lua 在 x86_64 上可以节省 16 个字节(1 个 TValue);对于 2 或 6 个参数则不会节省;对于 4 个参数则浪费 32 个字节,对于 5 个参数则浪费 16 个字节,对于 8 个参数则浪费 112 个字节(这个 2 的整数次幂模式会不断增长/重复)。)

2017-05-01 17:08:46
用户415823
用户415823

如果知道可能的参数的最大数量,且很少,可以使用闭包进行简单的操作。

local function bind(f, p1, p2, p3, p4, p5)
  return function()
    return f(p1, p2, p3, p4, p5)
  end
end

在绑定后,值类型将是不可变的,所有参数都将被传递(即使是 nil)。

local hello = bind(print, 'hello', 123)
hello()  --> hello   123     nil     nil     nil

当然,您也可以绑定到引用类型。

local coords = { x=0, y=0 }

local t1 = bind(function(t, n) t.x = t.x + n end, coords, 20)
local t2 = bind(function(t, n) t.y = t.y + n end, coords, 50)

t1(); print('transform1', coords.x, coords.y)   --> 20   0
t2(); print('transform2', coords.x, coords.y)   --> 20   50
t1(); print('transform1', coords.x, coords.y)   --> 40   50

而且,您仍然可以将所有内容存储在一个表中。

local t = {
  bind(function(a, b) return 'add', a + b end, 5, 5),
  bind(function(a, b) return 'sub', a - b end, 5, 5),
  bind(function(a, b) return 'mul', a * b end, 5, 5),
  bind(function(a, b) return 'div', a // b end, 5, 5),
}

for _, f in ipairs(t) do
  print(f())
end

--> add     10
--> sub     0
--> mul     25
--> div     1
2017-05-02 01:40:08