第一题: pairs和ipair的区别?
答:在 Lua 中,pairs 和 ipairs 都用于遍历表(table),但它们的遍历方式和适用场景有显著区别:
ipairs 的特点:
- 仅遍历连续数字索引:从 1 开始,按顺序递增遍历,直到遇到第一个 nil 值停止。
- 忽略非数字键和稀疏键:例如跳过字符串键或中间存在空洞的键
- 有序且可预测:严格按 1, 2, 3... 的顺序遍历
local t = {"a", "b", c = "skip", [3] = "c", [5] = "e"}for i, v in ipairs(t) doprint(i, v)
end
-- 输出:
-- 1 a
-- 2 b
-- 3 c
pairs 的特点:
- 遍历所有键值对:包括数字键、字符串键、稀疏键等。
- 无序且不可预测:遍历顺序由表的哈希结构决定,可能每次运行结果不同。
- 不跳过任何有效键:即使中间存在 nil,也会继续遍历其他存在的键。
local t = {"a", "b", c = "value", [5] = "e"}for k, v in pairs(t) doprint(k, v)
end
-- 可能的输出(顺序不固定):
-- 1 a
-- 2 b
-- 5 e
-- c value
第二题:解释一下Lua的__index
答:在 Lua 中,__index 是元表(metatable)中的一个核心元方法(metamethod),用于定义当访问表中不存在的键(key)时的行为。它是实现 Lua 面向对象编程(OOP)、继承、默认值等特性的关键。
- 当尝试从表(table)中读取一个不存在的键时,Lua 会检查该表的元表中是否有 __index 元方法。
- 若存在 __index,Lua 会根据 __index 的定义去寻找替代值,而不是直接返回 nil
_index 可以是 函数 或 另一个表,具体行为如下:
- __index 是一个表(Table)
local parent = { value = 10 }
local child = {}-- 设置 child 的元表为 { __index = parent }
setmetatable(child, { __index = parent })print(child.value) -- 输出 10(实际访问的是 parent.value)
- __index 是一个函数(Function)
local table_with_default = {}
setmetatable(table_with_default, {__index = function(t, key)return "默认值"end
})print(table_with_default.name)
第三题:解释一下Lua的__newindex
答:在 Lua 中,__newindex 是元表(metatable)中的另一个核心元方法(metamethod),它控制当尝试向表中写入一个不存在键值时的行为。
当尝试向一个表写入一个不存在的键时,Lua 会检查该表的元表中是否有 __newindex 元方法:
- 如果 __newindex 是函数,则调用该函数处理赋值。
- 如果 __newindex 是另一个表,则实际写入该表。
- 如果 __newindex 不存在,则直接在原表中创建新键值。
- __newindex 是函数
local t = {}
local proxy = {} -- 代理表,用于记录操作setmetatable(t, {__newindex = function(raw_table, key, value)print("拦截写入:", key, "=", value)-- 将实际数据存储到 proxy 表中proxy[key] = valueend
})t.name = "Alice" -- 输出:拦截写入: name = Alice
print(t.name) -- 输出 nil(实际未写入 t,而是写入了 proxy)
print(proxy.name) -- 输出 Alice
- __newindex 是另一个表
local t = {}
local hidden = {}setmetatable(t, {__newindex = hidden -- 写入不存在的键时,实际写入 hidden 表
})t.name = "Bob"
print(t.name) -- 输出 nil(未写入 t)
print(hidden.name) -- 输出 Bob
第四题:介绍一下Lua的Module是什么
答:一个 Lua 模块本质上是一个返回表的 Lua 文件。这个表中包含了模块对外暴露的函数、变量或其他数据。当其他代码通过 require 加载模块时,会获得这个表,从而访问模块的功能。
module("mymodule", package.seeall)function add(a, b)return a + b
end
- Lua 会创建一个新表 mymodule,并将其赋值给全局变量 _G.mymodule。
- 设置当前模块的环境为 mymodule,即后续代码默认在 mymodule 表中定义变量和函数。
- package.seeall 让模块的环境继承 _G 的全局变量(通过元表 __index = _G)。
所以,add方法的完整路径是_G.mymodule.add。再强调下package.seeall的作用:package.seeall 允许模块代码读取全局变量(例如访问全局的 math、table 等),但不会将模块内的定义泄露到全局。模块内定义的变量和函数仍然属于模块自己的表。
第五题:介绍一下Lua的require方法
答:当调用require("module.name")时,Lua执行以下步骤:
- 检查缓存:查找package.loaded["module.name"],若已存在则直接返回。
- 查找文件:
- 将模块名中的.转换为路径分隔符(如/)
- 按package.path中的模式搜索Lua文件(如module/name.lua)。
- 若无结果,按package.cpath搜索C模块(如module/name.so或name.dll)。
- 加载模块:
- 对于Lua文件,执行文件并获取返回值。
- 对于C模块,通过loadlib加载符号。
- 缓存结果:将模块返回值存入package.loaded["module.name"]
- 返回结果:若模块未返回值,默认返回true
对于 Lua 而言,每个 Lua 文件都需要通过 require 加载一次后才能被其他代码使用。
第六题:介绍下Lua的:语法糖
答:在 Lua 中,冒号 : 是专门为面向对象编程设计的一种语法糖,主要用于简化对象方法的定义和调用。它的核心作用是隐式传递 self 参数,使得代码更简洁。
- 定义方法时用冒号
-- 用 : 定义的方法会自动接收一个隐式的 self 参数,指向调用该方法的表(对象)本身。
-- 定义一个表(类似对象)
local obj = {value = 10
}-- 用冒号定义方法(自动添加 self)
function obj:printValue()print(self.value) -- 通过 self 访问对象的属性
end-- 等价于
function obj.printValue(self)print(self.value)
end
- 调用方法时用冒号
-- 用 : 调用方法时,会自动将调用者作为 self 参数传入。
obj:printValue() -- 输出 10(隐式传递 obj 作为 self)-- 等价于用 . 显式传递 self:
obj.printValue(obj) -- 显式传递 obj 作为 self
第七题:介绍一下Lua的rawget
答:在 Lua 中,rawget 是一个用于直接访问表的原始字段的函数,它会绕过元表(metatable)的 __index 元方法。这意味着无论表是否有元表,rawget 都只会访问表中实际存储的键值对,而不会触发任何元方法的逻辑。
local t = {}
local mt = {__index = function(table, key)return "Default Value"end
}
setmetatable(t, mt)-- 普通访问:触发 __index 元方法
print(t.foo) -- 输出 "Default Value"-- 使用 rawget:绕过元表,直接访问表的字段
print(rawget(t, "foo")) -- 输出 nil(因为 t.foo 不存在)
第八题:介绍一下Lua的__call
答:Lua 中的 __call 元方法允许将一个表或用户数据(userdata)像函数一样调用。
当尝试调用一个 table 时(例如 my_table()),Lua 会执行以下操作:
- 检查该 table 的元表(metatable)是否定义了 __call 元方法。
- 如果定义了 __call,则调用该元方法,并将 table 本身作为第一个参数,后续参数为调用时的参数。
- 如果未定义 __call,直接调用会抛出错误:attempt to call a table value。
-- 定义一个 table 和元表
local my_table = {}
local mt = {__call = function(table_arg, ...) -- __call 方法的第一个参数是 table 自身print("调用了 table!参数为:", ...)return "返回值"end
}-- 为 table 设置元表
setmetatable(my_table, mt)-- 调用 table,像函数一样使用
local result = my_table(1, "hello", true)
-- 输出:
-- 调用了 table!参数为: 1 hello true
print(result) -- 输出: 返回值
第九题:介绍一下lua的table.move
答:将 source 中 start 到 end 的元素复制到 target_table 的 target_index 至 target_index + (end - start) 位置。
table.move(source, start, end, target_index [, target_table])
参数 target_table 可选,不选的话默认与 source 相同。若指定,元素将复制到此表。仅复制元素引用(如嵌套表,不深拷贝)。