在Lua中,我应该如何处理来自C的从零开始的数组索引?
在 C 代码中,我有一个数组和一个从0开始的索引用于查找数组中的元素,例如:
char * names[] = {"Apple", "Banana", "Carrot"};
char * name = names[index];
从嵌入式 Lua 脚本中,我可以通过 getIndex() 函数访问到 index,并希望能够复制相同的数组查找。考虑到 Lua 的数组索引从1开始,是否有共识的“最佳”方法来解决这个问题呢?
例如,我可以创建一个与 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 代码的编写都符合惯例。
第二种情况涉及具有含义的索引和可能始终相同的数组。一个极端的例子是 names 包含 "Zero","One","Two"。在这种情况下,每个索引的预期值是众所周知的,我认为使 Lua 端的索引从1开始是不直观的。我相信应该首选其他方法之一。
使用基于1的Lua表,并将 + 1 嵌入 getIndex 函数中。
首先,这种情况不仅仅适用于混合使用 Lua 和 C 的应用程序;即使使用仅 Lua 应用程序也可能面临同样的问题。例如,我正在使用的编辑器组件从 0 开始索引行(是的,它是基于 C 的,但我仅使用其 Lua 接口),但我编辑的脚本行从 1 开始。因此,如果用户在第 3 行上设置了断点(从编辑器中开始为 0),我需要向调试器发送命令在脚本中的第 4 行上设置它(并在触发断点时进行转换)。
现在提供建议。
(1)我个人不喜欢使用 [0] 将数组强制从 0 开始索引,因为它会导致太多问题。你和 Egor 已经列出了许多问题;对我来说,最重要的是它破坏了 # 和 ipairs。
(2)在使用从 1 开始索引的数组时,我会尽量避免对它们进行索引,并尽可能使用迭代器:for i, v in ipairs(...) do 而不是 for i = 1, #array do)。
(3)我也尝试隔离处理这些转换的代码;例如,如果你正在转换编辑器中的行与脚本中的行,则编写 marker2script 和 script2marker 函数以执行转换(即使是简单的 +1 和 -1 操作)。无论是否进行 +1/-1 调整,你都需要类似的函数,只是函数的用途不同。这样做的好处是可以隐蔽这个细节。
(4)如果无法隐藏转换(我同意,+1 可能看起来很丑),那么就更明显地表示它:使用 c2l 和 l2c 调用进行转换。在我看来,它不像 +1/-1 那么丑陋,但具有传达意图的优点,并且为你查找所有发生转换的地方提供了一种简单的方法。当你正在寻找错误或者 API 发生变化时需要更新转换逻辑时,这非常有用。
总的来说,我不会过于担心这些方面。我正在开发一个相当复杂的 Lua 应用程序,包装了多个从 0 开始索引的 C 组件,但我并没有记得因为不同的索引会导致任何问题。
这就是 Lua 元方法和元表派上用场的地方。使用一个表代理和几个元方法,你就可以以适合你的方式修改对表的访问。
local names = {"Apple", "Banana", "Carrot"} -- 原始表
local _names = names -- 保留访问原始表的私有权限
local names = {} -- 代理表,用于捕获对原始表的所有访问
local mt = {
__index = function (t,k)
return _names[k+1] -- 访问原始表
end,
__newindex = function (t,k,v)
_names[k+1] = v -- 更新原始表
end
}
setmetatable(names, mt)
那么这里发生了什么,原始表有一个代理,在代理里捕捉到每次对原始表的访问。当访问表时,它将以被访问的值为基础递增,模拟一个从 0 开始的数组。这是打印结果:
print(names[0]) --> Apple
print(names[1]) --> Banana
print(names[2]) --> Carrot
print(names[3]) --> nil
names[3] = "Orange" --在表中加入一个新字段
print(names[3]) --> Orange
所有表操作都像平常一样效果。通过这种方法,你不用担心与表有任何不寻常的访问。
编辑:我想指出,新的“names”表仅仅是访问原始names表的代理。所以如果查询 #names 的结果将是 nil ,因为该表本身没有任何值。您需要查询 #_names 获取原始表的大小。
编辑 2:如下评论中的 Charles Stewart 指出,您可以在 mt 表中添加 __len 元方法来确保 #names 调用给出正确的结果。
为什么不在 C 数组中也将其转换为从 1 开始的数组呢?
char * names[] = {NULL, "Apple", "Banana", "Carrot"};
char * name = names[index];
坦白地说,这将在 C 端导致一些不直观的代码,但如果您坚持认为在两边都必须有“众所周知”的索引,则这似乎是最好的选择。
一种更清洁的解决方案当然是不将这些“众所周知”的索引作为接口的一部分。例如,您可以使用命名标识符而不是纯数字。枚举在 C 端非常适合此用途,而在 Lua 中,甚至可以使用字符串作为表键。
另一个可能性是将表封装在接口后面,以便用户永远不会直接访问数组,而是通过 C 函数调用来访问,该函数可以执行任意复杂的索引转换。然后您只需要在 Lua 中公开该 C 函数,就可以得到一个干净且易于维护的解决方案。
为什么不将C数组呈现为Lua中的userdata呢?该技术在PiL,'Userdata'一节中有所介绍代码; 您可以设置__index,__newindex和__len元表方法,并且可以继承自类来提供其他序列操作功能作为常规方法(例如,使用array.remove,array.sort,array.pairs函数定义一个数组,这些函数可以通过对__index进行进一步调整来定义为对象方法)。以这种方式执行操作意味着Lua和C之间没有“同步”问题,并且避免了“数组”表被视为普通表导致的差一错误的风险。
你可以通过使用一个意识到不同索引基础的迭代器来修复这个 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
-- 对元素 e 进行处理
end
- 如何将两个不同的lua文件合成一个 东西有点长 大佬请耐心看完 我是小白研究几天了都没搞定
- 如何在roblox studio中1:1导入真实世界的地形?
- 求解,lua_resume的第二次调用继续执行协程问题。
- 【上海普陀区】内向猫网络招募【Skynet游戏框架Lua后端程序员】
- SF爱好求教:如何用lua实现游戏内调用数据库函数实现账号密码注册?
- Lua实现网站后台开发
- LUA错误显式返回,社区常见的规约是怎么样的
- lua5.3下载库失败
- 请问如何实现文本框内容和某个网页搜索框内容连接,并把网页输出来的结果反馈到另外一个文本框上
- lua lanes多线程使用
- 一个kv数据库
- openresty 有没有比较轻量的 docker 镜像
- 想问一下,有大佬用过luacurl吗
- 在Lua执行过程中使用Load函数出现问题
- 为什么 neovim 里没有显示一些特殊字符?
- Lua比较两个表的值(不考虑键的顺序)
- 有个lua简单的项目,外包,有意者加微信 liuheng600456详谈,最好在成都
- 如何在 Visual Studio 2022 中运行 Lua 代码?
- addEventListener 返回 nil Lua
- Lua中获取用户配置主目录的跨平台方法
我更喜欢
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个元素来说是完全可以的。