16

在 C 代码中,我有一个数组和一个从零开始的索引,用于在其中查找,例如:

char * names[] = {"Apple", "Banana", "Carrot"};
char * name = names[index];

从嵌入式 Lua 脚本中,我可以index通过getIndex()函数访问并希望复制数组查找。鉴于 Lua 的 one-based 数组,是否有一致的“最佳”方法来执行此操作?

例如,我可以创建一个与我的 C 数组内容相同的 Lua 数组,但这需要在索引时添加 1:

names = {"Apple", "Banana", "Carrot"}
name = names[getIndex() + 1]

或者,我可以通过使用更复杂的表来避免添加 1,但这会破坏以下内容#names

names = {[0] = "Apple", "Banana", "Carrot"}
name = names[getIndex()]

推荐什么方法?

编辑:谢谢你到目前为止的答案。不幸的是,在 getIndex 函数中将索引加 1 的解决方案并不总是适用。这是因为在某些情况下索引是“众所周知的”——也就是说,可以记录索引 0 表示“Apple”等等。在这种情况下,是否应该首选上述解决方案中的一个或另一个,还是有更好的选择?

编辑2:再次感谢您的回答和评论,他们确实帮助我思考了这个问题。我已经意识到可能有两种不同的情况会出现问题,并且每种情况的理想解决方案都可能不同。

例如,在第一种情况下,考虑一个可能不时不同的数组和一个仅相对于当前数组的索引。索引在代码之外没有任何意义。Doug Currie 和 RBerteig 是绝对正确的:数组应该是从 1 开始的并且getIndex应该包含一个+1. 如前所述,这允许 C 和 Lua 端的代码是惯用的。

第二种情况涉及有意义的索引,并且可能是一个始终相同的数组。一个极端的例子是 where namescontains "Zero", "One", "Two"。在这种情况下,每个索引的期望值是众所周知的,我觉得让Lua端的索引基于one是不直观的。我相信应该首选其他方法之一。

4

7 回答 7

16

使用从 1 开始的 Lua 表,并把函数埋在+ 1里面getIndex

于 2013-04-23T21:53:33.317 回答
6

这就是 Lua 方法和元表派上用场的地方。使用表代理和几个元方法,您可以以适合您需要的方式修改对表的访问。

local names = {"Apple", "Banana", "Carrot"} -- Original Table
local _names = names -- Keep private access to the table
local names = {}    -- Proxy table, used to capture all accesses to the original table

local mt = {
  __index = function (t,k)
    return _names[k+1]   -- Access the original table
  end,

  __newindex = function (t,k,v)
    _names[k+1] = v   -- Update original table
  end
}

setmetatable(names, mt)  

所以这里发生的事情是,原始表有自己的代理,然后代理会捕获对表的每次访问尝试。当访问表时,它会增加访问它的值,模拟一个从 0 开始的数组。以下是打印结果:

print(names[0]) --> Apple
print(names[1]) --> Banana
print(names[2]) --> Carrot
print(names[3]) --> nil

names[3] = "Orange" --Add a new field to the table
print(names[3]) --> Orange

所有的表操作都像往常一样。使用这种方法,您不必担心会弄乱对表的任何非正常访问。

编辑:我想指出新的“名称”表只是访问原始名称表的代理。因此,如果您查询#names结果将为 nil,因为该表本身没有值。您需要查询#_names以访问原始表的大小。

编辑 2:正如查尔斯斯图尔特在下面的评论中指出的那样,您可以将 __len 元方法添加到 mt 表中,以确保 #names 调用为您提供正确的结果。

于 2013-04-26T16:22:26.170 回答
6

First of all, this situation is not unique to applications that mix Lua and C; you can face the same question even when using Lua only apps. To provide an example, I'm using an editor component that indexes lines starting from 0 (yes, it's C-based, but I only use its Lua interface), but the lines in the script that I edit in the editor are 1-based. So, if the user sets a breakpoint on line 3 (starting from 0 in the editor), I need to send a command to the debugger to set it on line 4 in the script (and convert back when the breakpoint is hit).

Now the suggestions.

(1) I personally dislike using [0] hack for arrays as it breaks too many things. You and Egor already listed many of them; most importantly for me it breaks # and ipairs.

(2) When using 1-based arrays I try to avoid indexing them and to use iterators as much as possible: for i, v in ipairs(...) do instead of for i = 1, #array do).

(3) I also try to isolate my code that deals with these conversions; for example, if you are converting between lines in the editor to manage markers and lines in the script, then have marker2script and script2marker functions that do the conversion (even if it's simple +1 and -1 operations). You'd have something like this anyway even without +1/-1 adjustments, it would just be implicit.

(4) If you can't hide the conversion (and I agree, +1 may look ugly), then make it even more noticeable: use c2l and l2c calls that do the conversion. In my opinion it's not as ugly as +1/-1, but has the advantage of communicating the intent and also gives you an easy way to search for all the places where the conversion happens. It's very useful when you are looking for off-one bugs or when API changes cause updates to this logic.

Overall, I wouldn't worry about these aspects too much. I'm working on a fairly complex Lua app that wraps several 0-based C components and don't remember any issues caused by different indexing...

于 2013-04-26T04:52:03.247 回答
6

我更喜欢

names = {[0] = "Apple", "Banana", "Carrot"}
name = names[getIndex()]

某些表格操作功能 - #, insert, remove, sort- 已损坏。
其他 - concat(t, sep, 0), unpack(t, 0)- 需要明确的起始索引才能正确运行:

print(table.concat(names, ',', 0))  --> Apple,Banana,Carrot
print(unpack(names, 0))             --> Apple   Banana  Carrot

我讨厌不断地记住这一点,+1以迎合 Lua 默认的基于 1 的索引样式。
您的代码应该反映您的领域特定索引,以便更具可读性。
如果基于 0 的索引非常适合您的任务,则应该在 Lua 中使用基于 0 的索引。

我喜欢 Pascal 中数组索引的实现方式:您完全可以自由选择所需的任何范围,例如,array[-10..-5]of byte对于 6 个元素的数组来说绝对可以。

于 2013-04-23T09:27:51.320 回答
4

为什么不把 C 数组也变成一个基于 1 的数组呢?

char * names[] = {NULL, "Apple", "Banana", "Carrot"};
char * name = names[index];

坦率地说,这会导致 C 端出现一些不直观的代码,但如果你坚持必须有在双方都有效的“知名”索引,这似乎是最好的选择。

一个更干净的解决方案当然是不让那些“众所周知的”索引成为界面的一部分。例如,您可以使用命名标识符而不是普通数字。在 C 端,枚举是一个很好的匹配,而在 Lua 中,您甚至可以使用字符串作为表键。

另一种可能性是将表封装在接口后面,这样用户就不会直接访问数组,而只能通过 C 函数调用,然后可以执行任意复杂的索引转换。然后你只需要在 Lua 中公开那个 C 函数,你就有了一个干净且可维护的解决方案。

于 2013-04-30T15:19:14.200 回答
3

为什么不将你的 C 数组作为用户数据呈现给 Lua?该技术在PiL 的“用户数据”部分中使用代码进行了描述;您可以设置__index, __newindex, 和metatable 方法,并且您可以从一个类继承以提供其他序列操作函数作为常规方法(例如,使用, ,函数__len定义一个数组,可以通过进一步调整将其定义为对象方法) . 以这种方式做事意味着你在 Lua 和 C 之间没有“同步”问题,并且它避免了“数组”表被视为普通表而导致错误的风险。array.removearray.sortarray.pairs__index

于 2013-05-02T06:43:17.463 回答
0

您可以通过使用了解不同索引基数的迭代器来修复此 lua 缺陷:

function iarray(a)
  local n = 0
  local s = #a
  if a[0] ~= nil then
    n = -1
  end
  return function()
    n = n + 1
    if n <= s then return n,a[n] end
  end
end

但是,您仍然必须手动添加第零个元素:

使用示例:

myArray = {1,2,3,4,5}
myArray[0] = 0
for _,e in iarray(myArray) do
  -- do something with element e
end
于 2017-11-05T20:59:01.123 回答