0.创建表与创建元表
-- 创建表
local myTable = {} -- 创建一个空表
myTable.name = "Lua" -- 给表添加字段
myTable[1] = 100 -- 使用数组索引添加元素
-- 创建元表1
local t = {} -- 普通表
local mt = {} -- 元表
setmetatable(t, mt) -- 将元表 mt 赋给 t
-- 创建元表2
local table = setmetatable({}, {})
元表的 key/value 可以随便写,和表的key、value没有直接联系。只有特定的元表key(如 __index
)会被 Lua 用于控制行为。
1.ipairs和pairs的区别
local tbl3 = {2, s1 = "s1", s2 = "s2", s3 = "s3" }
print("=======ipairs的执行结果=======")
for i, v in ipairs(tbl3) do
print(i, '=", v)
end
print("=======pairs的执行结果=======")
for i, v in pairs(tbl3) do
print(i, '=", v)
end
结论:如果是正常的数组,ipair和pairs的结果没有区别
遍历table或array时,如果key是非数字(如,自定义的key可能为string),使用pairs迭代遍历,使用ipairs的话遍历结果不全、被截断。
2.函数冒号和点的区别
冒号定义方法,默认会接受self参数;而点号定义的时候,默认不会接受self参数。
class是一个表,test是表中的一个函数:
function class:test()
--这里会接受self参数,比如
print(self.a, self.b)
--在这里self就是class对象本身,因此不会出错
end
如果这里换成class.test(),就不能print(self.a, self.b),应该手动传入我们要调用的东西:
local class = {}
class.a = 1
class.b = 2
function class.test(tbl)
print(tbl.a, tbl.b)
end
-- 调用示例
class.test(class) -- 输出:1 2
3.可变参数(...)
-- 定义一个函数 average,接受可变参数(...)
function average(...)
local result = 0 -- 用于累加所有参数的总和
-- 将所有可变参数打包成一个表 args,便于遍历
local args = {...}
-- 遍历 args 表中的每个值
for i, v in ipairs(args) do
result = result + v -- 将每个数加到 result 中
end
-- 输出总共传入了多少个参数
print("总共传入: " .. #args .. "个数")
-- 返回平均值:总和除以数量
return result / #args
end
Lua函数可以接受可变数目的参数,和C++语言类似:在函数参数列表中使用三点(...)表示函数有可变的参数。
4.考题:在元表中禁止创建新字段如何写?
应用场景:
local table = setmetatable({}, {})
table.key = "iam key"
table.value = 123
print(table.key)
使用 setmetatable({}, {})
创建了一个空表 table
,并赋予它一个空的元表(此时没有任何元方法)。
然后向 table
添加两个字段:key
和 value
。
接着打印 table.key
,输出应为 "iam key"
。
答案:考察Lua 中的元表(metatable)机制,尤其是元方法 __newindex
的使用方式,以及其对 table 赋值行为的影响。
local mt = getmetatable(table) -- 获取table的元表
function mt:__newindex(key, value) -- 定义__newindex元方法
mt[key] = nil
print("cannot create new property:" .. key .. ",value=" .. value)
end
getmetatable(table)
拿到的是table
的元表(之前设定为{}
,所以此处能取到)。- 定义了
__newindex
元方法,这个元方法会在 尝试给 table 添加不存在的键时被调用。 - 在
__newindex
中:- 没有实际赋值(用
mt[key] = nil
只是在元表上做事,其实没意义,可以理解为防止污染)。 - 输出一个错误提示信息。
- 没有实际赋值(用
关于元方法
元方法是写在元表中的特定键名的函数,Lua 会在特定行为时自动调用。
元方法 | 触发时机 |
---|---|
__index | 访问不存在的键 |
__newindex | 赋值给不存在的键 |
__add | 加法运算 + |
__tostring | 使用 tostring() 时 |
__call | 表当函数调用时 |
5. Lua中的深拷贝与浅拷贝
1.拷贝对象是string、number、bool基本类型:深拷贝
拷⻉的过程就是复制黏贴!修改新拷⻉出来的对象,不 会影响原先对象的值,两者互不⼲涉。
2.拷贝对象是table表:浅拷贝
拷⻉出来的对象和原先对象时同⼀个对象,占⽤同⼀个对象,只是⼀个⼈的两个名字,类似C#引⽤地址,指向同⼀个堆⾥的数据~,两者任意改变都会影响对方。
如何对LUA的表实现深拷贝:
-- 实现一个深拷贝函数,可以复制任意嵌套的表结构,并正确处理循环引用
function deepCopy(object)
-- 创建一个查找表,用于记录已经拷贝过的表,以处理循环引用
local lookup_table = {}
-- 定义一个局部的递归拷贝函数
local function _copy(object)
-- 如果不是表(如 number、string、boolean、nil),直接返回原值
if type(object) ~= "table" then
return object
-- 如果这个表之前已经拷贝过,直接返回旧拷贝,避免死循环
elseif lookup_table[object] then
return lookup_table[object]
end
-- 创建一个新的空表,用来存放拷贝结果
local new_table = {}
-- 将原始表和新表的映射关系记录下来(防止递归循环)
lookup_table[object] = new_table
-- 遍历原始表中的所有键值对,递归复制
for key, value in pairs(object) do
-- 拷贝键和值,支持键也为 table 的情况
new_table[_copy(key)] = _copy(value)
end
-- 将原表的元表也拷贝过去(如果有的话)
return setmetatable(new_table, getmetatable(object))
end
-- 返回最终的拷贝结果
return _copy(object)
end
6. Lua中实现面向对象的功能
-- 声明一个 Lua 类系统的函数
-- 参数:
-- className:类名字符串,仅用于标识用途
-- super:父类(可选),用于实现继承
local function class(className, super)
-- 创建一个类表,记录类名和父类
local clazz = {
__cname = className, -- 类名(用于调试或标识)
super = super -- 父类引用(可为 nil)
}
-- 如果有父类,则设置 clazz 的元表,使其支持继承
if super then
setmetatable(clazz, {
__index = super -- 当访问 clazz 中不存在的字段时,转而从 super 查找
})
end
-- 定义类的构造方法,用于创建类实例
clazz.new = function(...)
-- 创建一个实例表
local instance = {}
-- 设置元表,让 instance 可以访问 clazz 的方法和字段
setmetatable(instance, {
__index = clazz -- 实现对象对类方法的访问
})
-- 如果类中定义了构造函数 ctor,则调用它
if clazz.ctor then
clazz.ctor(instance, ...) -- 以 instance 作为 self 传入
end
return instance -- 返回构建好的实例
end
return clazz -- 返回定义好的类
end
功能项 | 实现方式 |
---|---|
类的定义 | local clazz = {} |
类的继承 | 通过设置元表:setmetatable(clazz, { __index = super }) |
实例化对象 | clazz.new(...) 创建对象并绑定元表 |
构造函数支持 | 调用 clazz.ctor(instance, ...) |
对象访问方法 | setmetatable(instance, { __index = clazz }) |
使用案例:
-- 定义一个基类
local Animal = class("Animal")
function Animal:ctor(name)
self.name = name
end
function Animal:speak()
print(self.name .. " makes a sound.")
end
-- 定义一个子类
local Dog = class("Dog", Animal)
function Dog:ctor(name)
self.super.ctor(self, name) -- 调用父类构造
end
function Dog:speak()
print(self.name .. " barks.")
end
-- 使用
local d = Dog.new("Buddy")
d:speak() -- 输出: Buddy barks.
7. Upvalue
在 Lua 中,upvalue(上值) 是指在一个嵌套函数(闭包)中引用其外部函数的局部变量。upvalue 的作用:
- 保持状态
即使外部函数已经执行完毕,upvalue 仍然会被闭包持有,从而保留变量状态。 - 创建闭包(Closure)
Lua 的闭包依赖 upvalue,闭包可以捕获其外部作用域的局部变量。 - 共享变量
多个闭包引用同一个 upvalue 时,它们共享这个变量的值。
代码示例:下面的代码返回了两个匿名函数:这两个匿名函数都是闭包,引用并修改count。
因为这两个函数都是在同一个作用域中创建的,它们 共享同一个 count
upvalue。
function createCounters()
local count = 0 -- 局部变量 count 是 upvalue
return function()
count = count + 1
return count
end,
function()
count = count - 1
return count
end
end
local incCounter, decCounter = createCounters()
print(incCounter()) -- 第一次加 1,count 从 0 → 1,输出 1
print(incCounter()) -- 再加 1,count 从 1 → 2,输出 2
print(decCounter()) -- 减 1,count 从 2 → 1,输出 1
print(decCounter()) -- 再减 1,count 从 1 → 0,输出 0
createCounters是多返回值函数,这里分别使用incCounter和decCounter来接收第一个、第二个返回值。
仅使用incCounter,表示你只接收了 createCounters
返回的第一个函数,也就是递增的那个。
upvalue的存储细节
在lua中,会生成一个全局栈,所有的upvalue都会指向该栈中的值,若对应的参数离开的作用域,栈中的值也会被释放,upvalue的指针会指向自己,等待被gc
- 闭包创建阶段:
- 当外层函数执行到内层函数定义的位置时,Lua 会分析内层函数依赖的外部变量(upvalue),并为这些变量生成一个静态的引用链(如 upvalue 链表)。
- 这些引用会被固化在闭包的内存结构中,形成类似“快照”的效果。即使外层函数的执行已结束,闭包仍能通过该引用链安全地访问所需的外部变量。
- 闭包调用阶段:
- 每次调用闭包时,直接通过预先生成的引用链访问 upvalue,无需额外遍历或重建指针。
- 这种设计保证了闭包的高效性,避免了因动态查找导致的性能损耗。
- 共享与更新特性:
- 若多个闭包共享同一 upvalue(例如嵌套函数共享父级变量),它们的引用会指向相同的内存地址,确保数据一致性。
- 修改 upvalue 会影响所有关联它的闭包,因为操作的是同一底层对象。