1.1ipairs和pairs区别pairs遍历表中所有的Key可以返回nil比较常用。解释遍历表中的所有键值对包括数组部分和哈希部分。它会遍历所有不为 nil 的元素但不保证遍历顺序具体顺序取决于底层实现。ipairs:不能返回nil只能返回数字0遇到nil则退出循环。解释只遍历表的数组部分即键为从 1 开始的连续整数遇到第一个 nil 就停止。它不会遍历哈希部分即非整数键或非连续整数键。举例子local table {} --创建空表 table[1] 1 table[2] 2 table[3] 3 table[5] 5 table[yingstar] yingstar print(ipairs输出) for i,v in ipairs(table) do print(i,,v) end print(pairs输出) for i,v in pairs(table) do print(i,,v) end print(table的长度为:,#table)--长度输出为3实际长度为5 --[[ 补充: 第一:Lua 在计算 #table 时会从索引 1 开始向后遍历 直到遇到第一个 nil 为止然后返回前一个索引。 第二使用,连接时会默认带空格 所以输出的是 1 1 使用..连接时 输出11 --]]输出结果ipairs输出1 12 23 3pairs输出1 12 23 35 5yingstar yingstartable的长度为:31.2 如何求table长度已知使用#还是table.getn都会在索引终端的地方停止计数无法获得正确table长度所以只能自己写一个方法function table_getlen(t) local len0 for k,v in pairs(t) do lenlen1 end return len; end2.函数冒号与点的区别定义方面冒号定义默认接收self参数点号定义默认不接受self参数。调用方面冒号调用方法会默认将对象本身(self)传递给方法而点号调用则不会。表(table)方法中:1.冒号定义:默认接收self参数function person:sayName() --默认传入self参数 print(self.name) --所以直接可以使用self对象本身 end2.点号定义:默认不接受self参数function person.sayName() --点号定义默认不接受self参数 prinf(self.name) --所以这么写会报错 end3.调用方法区别(不常用但是作为扩展知识)3.1冒号定义但是点号调用function person:sayName() print(self.name) end 点号调用:person.sayName()这时候在sayName方法中使用self就会报错 解决方法: person.sayName(person)3.2点号定义但是冒号调用function person.sayName() print(self.name) end 冒号调用:class:test()这时会报错虽然冒号会传self进去 但是点号不接受一样报错 想要调用就只能显式接受参数 重新定义方法: fuction person.sayName(person) print(person.name) end4.日常调用冒号local person { name 小明, age 18 } function person:sayHello() print(你好我是 .. self.name) end person:sayHello() -- 输出你好我是小明3.可变参数和多返回值可变参数--取平均数的函数 function average(...) result 0 local args{...} for i,v in ipairs(args) do result result v end print(一共传入:.. #args ..个数) return result/#args print(平均值为.. average(2,4,6,8,10)) --[[ 输出 一共传入5个数 平均值为6 --]]可变参数也可以和固定参数一起用function select (n,...) local args {...} return args[n] end print(select(1,1,2,3,4,5)) -- 1 print(select(2,1,2,3,4,5)) -- 2 print(select(6,1,2,3,4,5)) -- nil多返回值function duofanhuizhi() local a 1 local b 2 local c 999 return a,b,c end print(duofanhuizhi()) --输出1 2 9994.如何对table新建值时提示错误如果不想修改表里的值避免出现错误可以让table新建值时提示错误知识点:__newindexlocal table setmetatable({},{}) table.key iam key table.value 123 print(table.key) local mt getmetatable(table) function mt:__newindex(key,value) table[key] nil print(不允许新建值..key) end5.lua实现深拷贝5.1先分清楚什么是深拷贝/浅拷贝/赋值- 赋值给你一把钥匙你们共用同一个箱子 同一个小箱子- 浅拷贝给你一个新箱子但小箱子还是原来那个- 深拷贝给你一个新箱子里面的小箱子也重新做一个5.2实际例子function deepCopy(object) -- lookup_table 用于建立【原表】到【新表】的映射。 -- 它的作用有两个 -- 1. 防止循环引用导致无限递归遇到已经拷贝过的表直接返回新表。 -- 2. 保持共享引用如果多个地方引用同一个子表拷贝后它们仍然指向同一个新子表。 local lookup_table {} -- 内部递归函数真正执行拷贝操作 local function _copy(object) -- 情况1如果 object 不是表例如数字、字符串、布尔值、函数、nil -- 直接返回它本身。因为这些类型在 Lua 中是“值类型”赋值就是独立复制。 if type(object) ~ table then return object end -- 情况2如果 object 是一个表并且已经在 lookup_table 中有记录 -- 说明之前已经拷贝过这个表直接返回之前创建的新表lookup_table[object]。 -- 这一步是解决循环引用和共享引用的关键。 if lookup_table[object] then return lookup_table[object] end -- 情况3object 是一个表且是第一次遇到。 -- 创建一个新表作为原表的副本。 local new_table {} -- 在 lookup_table 中建立映射原表 object 对应新表 new_table。 -- 必须在递归复制键值对之前记录因为递归过程中可能再次遇到 object循环引用。 lookup_table[object] new_table -- 遍历原表的所有键值对使用 pairs 包括所有非数字索引和数字索引 for key, value in pairs(object) do -- 注意键和值都可能是表所以都需要递归调用 _copy 进行深拷贝。 -- 例如key 本身是一个表或者 value 是一个表。 -- 递归调用 _copy(key) 和 _copy(value) 会 -- - 如果 key/value 是基本类型直接返回。 -- - 如果是表则进入 _copy 函数同样会经历上述三种情况。 -- 然后将拷贝后的键和值赋给新表。 new_table[_copy(key)] _copy(value) end -- 最后将原表的元表也设置给新表保持元表行为一致。 -- getmetatable(object) 获取原表的元表setmetatable 将元表赋给新表。 return setmetatable(new_table, getmetatable(object)) end -- 启动递归拷贝从最外层的 object 开始 return _copy(object) end6.通过Lua来实现class面向对象local function class(className,father) --构建类 local clazz {__cname className, fatherfather} if father then --设置类的元表此类中没有的可以查找父类是否含有 setmetatable(clazz,{__index father}) end --new方法创建对象 function clazz.new(...) -- 构造一个对象 local instance{} --设置对象元表为当前类对象就可以调用当前类生命的方法 setmetatable(instance,{__index clazz}) if clazz.ctor then clazz.ctor(instance,...) end return instance end return clazz end测试代码local printf function(str, ...) return print(string.format(str, ...)) end -- 声明 classA并定义其构造函数 local ClassA class(ClassA) ClassA.static Static A function ClassA:ctor(a, b) self.a a or 0 self.b b or 0 end function ClassA:print() printf(%s, a %s, b %d, static %s, self.__cname, self.a, self.b, self.static) end function ClassA:getSum() return self.a self.b end -- 声明 classB并且继承 ClassA local ClassB class(ClassB, ClassA) function ClassB:ctor(...) self.father.ctor(self, ...) end -- 重写print function ClassB:print() print(Class B 重写父类 print) end -- 声明 classC并且继承 ClassA local ClassC class(ClassC, ClassA) ClassC.static Static C local obja1 ClassA.new(10, 20) local obja2 ClassA.new(100, 200) local objb1 ClassB.new(1, 2) local objc ClassC.new(3, 4) obja1:print() obja2:print() objb1:print() objc:print() printf(3 4 %s, objc:getSum()) 输出结果 ClassA,a 10,b 20,static Static A ClassA,a 100, b 200,static Static A ClassB 重写父类 print ClassC,a 3, b 4, static Static C 3 4 77.什么是Upvalue什么是闭包可以理解成“匿名函数”“内部函数”1. 全局栈与 upvalue 的初始状态openLua 有一个全局栈实际上是一个主栈用于存放所有函数调用的局部变量、参数和临时值。每个函数调用时它的局部变量就分配在这个栈的某个位置上。当一个内部函数闭包引用了外部函数的局部变量时Lua 会为该变量创建一个 upvalue 对象。这个对象最初处于 open 状态它包含一个指针直接指向栈上那个变量的位置。这样闭包就可以通过这个指针实时访问变量的当前值。2. 变量离开作用域upvalue 关闭closed当外部函数执行结束它的栈帧【栈帧是程序在运行期间每当调用一个函数时在栈Stack上分配的一块内存区域用于存储该函数执行所需的所有信息。函数的参数调用者传入的实参。局部变量函数内部定义的变量。返回地址函数执行完毕后应该返回到调用者代码的哪一条指令继续执行。保存的寄存器为了恢复调用者现场而暂存的CPU寄存器值。临时值表达式计算过程中产生的中间结果。】将被销毁栈上的局部变量也会随之失效栈空间被回收。但如果某个局部变量还被闭包引用即存在 open 状态的 upvalue直接让指针指向已失效的栈地址会导致错误。因此Lua 会执行 关闭 upvalue 的操作将栈上变量的值 复制 到 upvalue 对象内部的一个独立存储空间中。然后 upvalue 的指针从指向栈改为指向自己的内部存储。此时 upvalue 变为 closed 状态。这样即使外部函数已经返回闭包仍然可以通过 upvalue 访问到变量的副本并且后续对变量的修改会保留在这个内部存储中。3. 闭包运行时如何找到 upvalue每个闭包内部维护一个 upvalue 链表【upvalue linked list】按顺序记录了它所需的所有 upvalue 对象。当闭包被执行时它会遍历这个链表通过每个 upvalue 的指针可能指向栈也可能指向内部存储来获取或修改对应的变量值。这种设计使得闭包可以高效地访问外部变量同时动态适应变量从栈上迁移到堆上的变化。4. 垃圾回收与 upvalue当一个 upvalue 不再被任何闭包引用时它就会成为垃圾等待 Lua 的垃圾回收器回收。注意upvalue 本身也是 Lua 对象遵循相同的 GC 规则。myInner nil -- 解除引用 collectgarbage() -- 可选强制进行一次完整垃圾回收5.例子function A()--定义一个外部函数A local x 100 --x是一个局部变量 --定义一个在A里面的内部函数Inner --内部函数可以访问外部函数的局部变量x --原因是通过作用域和闭包 function Inner() print(x) x x 1 end return Inner end local myInner A() --调用A函数来返回Inner函数让myInner作为Inner函数使用闭包 myInner() --100 myInner() --101 myInner nil -- 解除引用GC回收 collectgarbage() -- 可选强制进行一次完整垃圾回收解析定义一个叫A的外部函数具有一个名为x的局部变量在A中定义内部函数Inner,打印并修改x的值。这里的x就是一个upvalue当A被调用他返回Inner函数。即使A的执行已经结束。Inner仍可以访问x以为x是Inner的一个upvalue每次调用myInner时他都会使用和修改upvalue x的值所以连续调用myInner时输出的值在递增6.共享如果多个闭包引用了同一个外部局部变量那他们就共享同一个upvalue。function createCounters() local count 0 return fuction() count count 1 return count end, function() count count - 1 return count end end local inc,dec createCounters() print(inc()) --输出:1 print(inc()) --输出:2 print(dec()) --输出:1 print(dec()) --输出:08.Lua中的闭包闭包函数引用环境子函数可以使用父函数中的局部变量这种行为是闭包Lua支持嵌套函数【一个函数写在另一个函数里】function A() local x 100 function Inner() print(x) x x 1 end return Inner end local myInner A() myInner() myInner()Inner函数可以读取函数A里面的局部变量x并修改他的值即使这个变量不在Inner里面定义这个特性叫做词法作用域闭包的基础就是词法作用域。闭包简单理解其实是一个函数不过它访问了另外一个函数词法作用域中的变量按照闭包的定义来看Lua的所有函数实际上都是闭包即使没有写嵌套也是闭包。这是因为Lua编译器会把Lua脚本外面再包装一层主函数。例子local foo,bar local function fn() foo 1 bar 2 end --编译以后 function main(...) local foo,bar local function fn() foo 1 bar 2 end end --函数fn使用了主函数的两个局部变量因此也是闭包总结闭包的主要作用有两个一简洁不需要在不使用时显式生成对象【能不用table来存储信息 而是直接用外部局部变量】也不需要函数名【闭包常用匿名函数所以不需要函数名】二捕获外部变量形成不同的调用环境。闭包原理概述闭包函数编译时会生成原型prototype包含参数、调试信息、虚拟机指令等一系列该闭包的原信息其中在递归编辑内层函数时会为内层函数生成指令同时为该层函数需要的所有upvalue创建表以便之后调用时进行upvalue值搜索。在lua中会生成一个全局栈所有的upvalue都会指向该栈中的值若对应的参数离开的作用域栈中的值也会被释放upvalue的指针会指向自己等待被gc。闭包运行时会通过创建指向upvalue的指针并循环upvalue linked list找到所需要的外部变量进行运行。9.Lua中的GC机制【内存管理机制】基于垃圾回收【garbage collection】原理采用标记-清除mark and sweep算法对象分配Lua有自己的内存分配器基于固定大小的内存块【blocks】这些内存块叫做内存块列表【blocks list】。需要分配一个对象的时候会从blocks list中选择一个合适的内存块来存储该对象。垃圾回收器周期性运行。分为两个阶段标记阶段和清除阶段标记阶段垃圾回收期从根集【root set】开始遍历所有可达的对象并将他们标记为活动对象。根集包括全局变量当前执行函数中的局部变量C函数中的一些特殊数据结构。清除阶段标记阶段完成后垃圾回收器扫描所有对象清除没有被标记的对象。分代回收Lua的垃圾回收器采用分代回收每一代有不同的优先级第一代优先级最高。新分配的对象被放置在第一代中老的对象会逐渐晋升更高的代。分代回收会提高回收效率。因为新分配的对象一般有较短的生命周期手动内存管理collectgarbage函数可以手动触发垃圾回收通过调整该函数的参数可以控制垃圾回收的行为比如设置不同阈值关闭垃圾回收。lua还提供weak table帮助管理对象之间引用关系来避免循环引用导致内存泄漏。总结Lua内存管理机制通过垃圾回收器和分代回收方式自动管理内存分配释放。不需要手动释放内存而是依靠垃圾回收器来标记和清除不再被引用的对象。10.C#与Lua的交互细节1.C#和Lua的相互调用C#调用Lua:C# - Bridge - dll - Lua生成Bridge文件然后Bridge文件调用dll然后dll执行Lua代码Lua调用C#生成Wrap文件Wrap文件把字段方法注册到lua虚拟机中Lua通过Wrap调用C#为什么调用会慢因为Lua调用C#会在ObjectTranslator中取对象这步有消耗。取出来后把C#对象包装成Lua能识别的userdata并给它关联一个元表这样才能具体的在lua层出来东西比如.localPosition这一步也有很大消耗。C#对象要传递给Lua使用时Lua会为它创建一个特殊的数据类型叫 userdata你可以把它理解为一个“空盒子”这个盒子里只放了一个指向真实C#对象的“钥匙”指针。为了让Lua能操作这个C#对象比如读取它的.localPosition属性必须给这个盒子贴上一张说明书——也就是元表metatable。这张说明书里写着“如果有人想从盒子里拿东西访问属性或方法就按这个步骤去C#里找真正的方法来执行。”补充ObjectTranslator是 C# 与 Lua 交互框架如 ulua、tolua、xlua中的一个核心组件它的职责是管理 C# 对象与 Lua 中对应的 userdata 之间的映射关系。你可以把它理解为一个“翻译官”。UserdataLua 代码中拿到的 go 实际上就是一个 userdata在 Lua 中userdata 是一种内置类型专门用来存储来自 C/C或其他宿主语言的任意数据。当运行go.transform.localPosition 时Lua 会发现 go 是 userdata于是去查找它的元表触发元表中的 __index 元方法从而进入 C# 世界执行真正的属性获取。2.Lua中糟糕的写法第一点在lua中引用c#的object代价昂贵gameobj.transform.position pos每用一次点.就要做一次昂贵的跨语言“翻译”打个比方gameobj 就像一个贴着“C# 物品”标签的盒子里面的东西你看不见必须通过翻译官元表才能拿到。第一次 .transform你得先打电话给翻译官让他帮你从盒子里取出 transform 部件这本身就是一个新盒子也得贴标签。第二次 .position拿到 transform 盒子后你又得打电话给翻译官让他帮你把里面的 position 改成新值。两次电话就是两结论在 Lua 中操作 C# 对象时尽量减少点.的次数能缓存的属性尽量先缓存成局部变量。第二点在lua和c#之间传递unity独有的值类型(Vector3/Quanternion)也很昂贵以Vector3为例子lua里面没有Vector3.所有Vector3传入Lua要先把Vector3拆成(x,y,z),然后再建一个表把(x,y,z)赋值给表{xx,yy,zz}结论别传 Vector3直接传三个独立的 float 参数x, y, z3.Lua优化的注意点[重点]lua和C#之间传参和返回时传递类型的注意点不要传递:Vector3/Quaternion等unity值类型数组最好不要传递:bool/string/各种object建议传递:int/float/doublelua调用C#时频繁调用的函数的参数数量要控制频繁调用的函数不要超过4个参数lua调用c#的性能与参数类型和参数数量都有关。优先使用static函数导出减少使用成员方法导出例子在Lua中调用obj.transform.position pos改成先在C# 中定义一个静态方法专门用来设置位置public static class LuaUtil { public static void SetPos(GameObject obj, float x, float y, float z) { obj.transform.position new Vector3(x, y, z); } }然后将这个方法导出给 Lua例如通过生成 Wrap 或手动注册。在 Lua 中调用LuaUtil.SetPos(obj, pos.x, pos.y, pos.z)lua有引用c#对象的时候会造成c#对象无法释放这是内存泄露常见起因c# object返回给lua是通过dictionary将lua的userdata和c# object关联起来最常见的就是gameobject和component如果lua里头引用了他们即使你进行了Destroy也会发现他们还残留在mono堆里。不过要发现这个问题也并不难遍历一下这个dictionary就很容易发现。ulua下这个dictionary在ObjectTranslator类、slua则在ObjectCache类考虑在lua中只使用自己管理的id而不直接引用c#的object想避免lua引用c# object带来的各种性能问题的其中一个方法就是不在 Lua 中直接持有 C# 对象的引用而是使用一个由 C# 分配的、唯一且稳定的 ID 来代替自己分配id去索引object同时相关c#导出函数不再传递object做参数而是传递int。这带来几个好处1.函数调用的性能更好2.明确地管理这些object的生命周期避免让ulua自动管理这些对象的引用如果在lua中错误地引用了这些对象会导致对象无法释放从而内存泄露3.c#object返回到lua中如果lua没有引用又会很容易马上gc并且删除ObjectTranslator对object的引用。自行管理这个引用关系就不会频繁发生这样的gc行为和分配行为。例如上面的LuaUtil.SetPos(GameObject obj, float x, float y, float z)可以进一步优化为LuaUtil.SetPos(int objID, float x, float y, float z)然后我们在自己的代码里头记录objID跟GameObject的对应关系如果可以用数组来记录而不是dictionary则会有更快的查找效率。如此下来可以进一步省掉lua调用c#的时间并且对象的管理也会更高效。合理利用out关键字返回复杂的返回值在c#向lua返回各种类型的东西跟传参类似也是有各种消耗的。比如Vector3 GetPos(GameObject obj)可以写成void GetPos(GameObject obj, out float x, out float y, out float z)表面上参数个数增多了但是根据生成出来的导出代码我们以ulua为准会从LuaDLL.tolua_getfloat3内含get_field tonumber 3次变成isnumber tonumber 3次get_field本质上是表查找肯定比isnumber访问栈更慢因此这样做会有更好的性能。11.多人开发如何避免全局变量泛滥【G表】不加local的话就很容易避免全局变量泛滥_G : 指向全局table设置_G的元表和元方法通过重写__newindex和__index元方法的方式来做到禁止新建全局变量和禁止访问不存在的全局变量并提示错误setmetatable(_G, { __newindex function(_,key) print(不允许新建全局变量..key) end __index function(_,key) print(不允许访问全局变量..key) end } ) --这样就会禁止新建和访问全局变量但是为什么这样不会顺便静止局部变量的新建和访问呢 --因为全局变量是存在_G表里的而局部变量是通过local存在当前帧栈/闭包中和G表无关12.热更新原理-- 定义热更新函数参数为模块名 function reloadUp(module_name) --从全局环境_G表中获取旧的模块表 local old_module _G[module_name] --将package.loaded中对应模块标识为未加载 package.loaded[module_name] nil --重新加载该模块 requre(module_name) --从全局环境_G中获取刚才加载的新版本模块表 local new_module _G[module_name] --遍历新模块表并赋值给旧模块表 for k,v in pairs(new_module) do old_module[k] v end --将更新后的旧模块重新放回package.loaded,表示已经加载 package.loaded[module_name] old_module end13.值类型传递为什么会有GC,Xlua中是怎么解决这个问题为什么会产生GC通常来说只要堆分配了内存也就是实例化引用对象在对象使用完的时候就会被GC。值类型是从栈上分配的正常来说不产生GC但是在lua和C#交互过程中会因为装箱拆箱产生GCXlua中的解决方法打上[GCOptimize]标签[GCOptimize] [LuaCallCSharp]--标记这个结构体可以被 Lua 脚本直接调用。 public struct Temp { public Temp(int p1,int p2) { a p1; b p2; } public int a; public int b; } --[GCOptimize] 和 [LuaCallCSharp] 是自定义特性通常用于 Unity 的 xLua热更新框架。14.什么是弱引用使用__mode元方法实现弱引用t {} -- 给t设置一个元表添加__mode元方法并赋值 -- 有三种形式 1key值弱引用 setmetatable(t,{__mode k}) 2value值弱引用 setmetatable(t,{__mode v}) 3key和value都弱引用 setmetatable(t,{__mode kv})那么什么是弱引用呢,为什么要使用它遇到的问题例子t {} key {name key} t[key] 1 key nil for k, v in pairs(t) do print(k.name .. : .. v) end 输出key:1 collectgarbage() --手动垃圾回收 for k, v in pairs(t) do print(k.name .. : .. v) end 输出key:1问题1虽然key已经被置nil但是为什么第一次依然可以输出问题2解决了问题1但是key被置nil调用collectgarbage()按常理应该被回收第二次为什么还能输出问题1解释key {name key}-- 本质上是创建表{name “key”},然后key是指向这个表的引用t[key] 1 --是通过key这个引用把这个表本身作为键存入t中这样t内部就又有一个对{name key}的引用且这个引用与key不同。所以key nil 只是把变量key的引用断开了t表内部的引用没有受到影响固第一次可以输出问题2解释即使调用 collectgarbage() 强制垃圾回收由于 t 仍然引用着这个 table 对象所以它不会被回收第二次还是能输出那么问题来了如果我想让这个{name key}的键在除了作为 t 的键之外没有其他引用的情况下可以回收他并自动从 t 中移除对应的条目该怎么做答案使用弱引用1t {} setmetatable(t,{__mode k}) key {name key} t[key] 1 key nil for k, v in pairs(t) do print(k.name .. : .. v) end 输出key:1 collectgarbage() --手动垃圾回收 for k, v in pairs(t) do print(k.name .. : .. v) end 无法输出15.table表的内部数据结构和ReHash可以深入讲数据分布table表的存储分为两部分数组部分哈希表部分数组部分从1开始作整数数字索引。可以紧凑且高效的随机访问哈希表部分唯一不能做哈希键值的是nil这个限制可以帮助发现运行错误数据结构typedef union TKey { struct { TValuefields; // 宏展开后包含值和类型字段 struct Node *next; // 用于解决哈希冲突的链表指针 } nk; TValue tvk; // 当作普通 TValue 使用 } TKey; typedef struct Node { TValue i_val; // 节点存储的值 TKey i_key; // 节点存储的键 } Node; typedef struct Table { CommonHeader; // 垃圾回收相关如 next、tt 等 lu_byte flags; // 用于缓存元方法是否存在避免重复查找 lu_byte lsizenode; // 哈希部分大小的对数2^lsizenode 即为 node 数组大小 struct Table *metatable; // 指向元表的指针 TValue *array; // 数组部分的起始指针 Node *node; // 哈希部分的起始指针 Node *lastfree; // 指向哈希部分中下一个空闲位置的指针 GCObject *gclist; // 用于垃圾回收的链表 int sizearray; // 数组部分的大小 } Table;ReHash过程数组部分和哈希表部分的内存大小是动态变化的如果空间不够就分配更多空间空间利用率太低就缩减内存。这个行为叫ReHash。rehash内部主要是做了一下几件事计算array part的key的数量统计当前 table 中所有整数键并确定哪些整数键适合放在数组部分计算hash part的key的数量统计所有不适合放在数组部分的键包括非整数键如字符串、table、布尔值等虽然为整数但不符合数组部分存储条件的键例如键 1000 而前面大量空洞导致放入数组会浪费过多空间计算新设的key之后array part部分的数量rehash 通常是因为要插入一个新键而触发的也可能因删除导致空间利用率过低。因此必须把即将加入的新键也纳入统计。计算一个新的array part部分需要分配的内存大小resize总结尽量提前分配好大小。错误方法 local tb {} tb[1] 1 tb[2] 2 tb[3] 3 改为 正确方法 local tb {1,2,3} 因为错误方法会多次触发rehash在使用长度操作符#对数组取数组长度时数组不应该包括nil值容易出错table中想删除一个元素等于把对应key赋值为nil然后等待垃圾回收。但是删除table一个元素的时候不会触发表重构行为即不会触发rehash操作16.rawset与rawget(绕过元方法)通过之前的学习我们知道__newindex和__index这两个元方法是重载lua的默认操作__newindex在用于处理赋值不存在字段时的行为__index是处理访问表中不存在的字段时的行为那么我要是想绕过这两个元方法直接进行赋值和访问呢就使用rawset来赋值rawget来访问即可rawset(table,key,value)value rawget(table,key)17.lua调试原理1.如何让程序暂停运行lua虚拟机提供一个接口用户可以在应用程序中设置一个钩子函数虚拟机在执行指令码的时候会检查用户是否设置钩子函数。如果设置了就调用钩子函数本质上就是设置一个回调函数。debug.sethook([thread,]hook,mask [,count])2.lua调试库