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 添加两个字段:keyvalue

接着打印 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
  1. getmetatable(table) 拿到的是 table 的元表(之前设定为 {},所以此处能取到)。
  2. 定义了 __newindex 元方法,这个元方法会在 尝试给 table 添加不存在的键时被调用
  3. __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 的作用:

  1. 保持状态
    即使外部函数已经执行完毕,upvalue 仍然会被闭包持有,从而保留变量状态。
  2. 创建闭包(Closure)
    Lua 的闭包依赖 upvalue,闭包可以捕获其外部作用域的局部变量。
  3. 共享变量
    多个闭包引用同一个 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

  1. 闭包创建阶段
  • 当外层函数执行到内层函数定义的位置时,Lua 会分析内层函数依赖的外部变量(upvalue),并为这些变量生成一个静态的引用链(如 upvalue 链表)。
  • 这些引用会被固化在闭包的内存结构中,形成类似“快照”的效果。即使外层函数的执行已结束,闭包仍能通过该引用链安全地访问所需的外部变量。
  1. 闭包调用阶段
  • 每次调用闭包时,直接通过预先生成的引用链访问 upvalue,无需额外遍历或重建指针。
  • 这种设计保证了闭包的高效性,避免了因动态查找导致的性能损耗。
  1. 共享与更新特性
  • 若多个闭包共享同一 upvalue(例如嵌套函数共享父级变量),它们的引用会指向相同的内存地址,确保数据一致性。
  • 修改 upvalue 会影响所有关联它的闭包,因为操作的是同一底层对象。

此作者没有提供个人介绍。
最后更新于 2025-05-28