C#使用者的lua学习笔记——作用域和复杂数据类型
题外话:上一篇和这一篇隔的时间有点久了
中间沉迷老滚去了 感觉每年冬天我都会打一遍老滚
现在真是越来越觉得自己已经不会学习了 什么时候才能找回当初学习的感觉呢
参考文献
详解Lua作用域和闭包 – Ruyi Y的文章 – 知乎 https://zhuanlan.zhihu.com/p/452689653
作用域
Clike语言的作用域是非常严谨的 基本可以用{}来区分开来
但一个函数的声明可以完全可以后置 也就是说使用之前不需要实现
而lua就不太一样了 首先从变量来说 它的所有变量默认都是全局的
如果你希望这个变量在这个函数范围内局部 需要添加local关键字
a = 10 function func() --仍然是全局变量 b = 10 --局部变量 local c = 20 end func() --输出20 所以能拿到b的值 print(a + b) --c为nil 拿不到局部变量的值 print(c)
lua是静态的作用域 函数定义的时候作用域就已经确定下来了
所以即便有值试图去修改自己作用域内的变量 它也不会受到影响
val = 1 function foo() print(val) end function bar() local val = 2 foo() end --bar的执行试图声明一个新的局部变量val --但foo在声明时使用val就已经确定为全局的那个了 --所以最终输出的结果仍然为1 bar()
一个函数在使用之前 必须得先声明
这个上一篇文章也提到过了
闭包
C#中的闭包现象其实很常见 只要涉及到匿名函数就会有这种情况
之前的文章(可能被我删了)我举过一个简单的闭包例子
就是Unity当中的多个按钮批量执行近似事件的问题
//没开IDE盲打的 可能有错 //声明一个按钮组 List<Buttons> buttons = new(); //遍历按钮组 批量添加单击事件 for(int i = 0; i < buttons.Count; i++) { //在这里缓存了一遍当前的下表 int index = i; buttons[i].onClick.Addlistener(()=>{ //输出buttos.Count Debug.Log(i); //输出当前下标 Debug.Log(index); } });
假设我们给buttons添加了10个按钮的引用 当我们点击任意一个按钮的时候
每个按钮都会输出10和自己的下标 这是为什么呢?
原因在于这个匿名函数导致了“非法的”的生命周期延长的情况
当你点击这个按钮的时候 匿名函数所找到的变量i实际上已经循环走完了 是一个需要被销毁的局部变量
然而这个lambda人为的延长了它的生命周期 所以当匿名函数调用i的时候只会得到遍历完的结果 也就是10
在这里为了解决闭包 我们声明了一个临时变量index 注意 这个index声明是每一次循环都会执行的
也就是说我们实际上声明了10个index 分别存储了不同的值 所以打印出来的自然是符合我们预期的下标
在计算机科学中,闭包是在词法环境中绑定自由变量的头等函数
闭包并不是某种语言的独有 lua自然也有这种现象
在lua里 闭包最直接的体现就在函数嵌套里
--这里偷懒了 直接引用参考文献文章给出的范例了 a = 0 function fun1() local b = 0 inner = function () a = a + 1 b = b + 1 return b end return inner end print(b) --nil --将inner函数赋值给fun2,生成第一个闭包 fun2 = fun1() for i = 1, 3, 1 do print("fun2 : " .. fun2()) end --将inner函数赋值给fun3,生成第二个闭包 fun3 = fun1() for i = 1, 3, 1 do print("fun3 : "..fun3()) end --输出的结果为 fun2 : 1 fun2 : 2 fun2 : 3 fun3 : 1 fun3 : 2 fun3 : 3
按照常规思路 fun1赋值给fun2和fun3后 b的生命周期就到此为止了
后面每一次inner访问局部变量b 局部变量b的值都应该为0
所以应该输出1 1 1才对 然而事实是它输出了1 2 3
也就是说每次调用inner的时候b事实上并没有被销毁 它的生命周期被延长了
Table(表)
所有的复杂类型都是Table 都是一张表
任意类型都可以塞在一张表里
数组
a = {1,2,3,4,”123″,true,nil}
这样的表达在lua是完全成立的 自此我们可以延伸出很多我们熟悉的复杂类型
a = {1,2,3,4,"123",true,nil} --lua中 索引从1开始 --这个一定要记住了 print(a[1]) --数组的长度 print(#a) --#是通用的获取长度的关键字 --这里打印6 遇到nil就会停止遍历 --如果表中某一位变成nil 会影响#获取的长度 --数组的遍历 for i=1,#a do print(a[i]) end --二维数组 --实际上就是表里装了两个表 表是可以相互嵌套的 a = {{1,2,3},{4,5,6}} print(a[1][3]) --二维数组的遍历 for i=1,#a do b = a[i] for j=1,#b do print(b[j]) end end --自定义索引 aa = {[0] = 1, 2 ,3, [-1] = 4, 5} print(aa[0]) print(aa[-1]) aa = {[1] = 1, [2] = 2, [4] = 4, [5] = 5} print(#aa) --打印5 aa = {[1] = 1, [2] = 2, [5] = 4, [6] = 5} print(#aa) --打印2 aa = {[1] = 1, [2] = 2, [4] = 4, [6] = 6} print(#aa) --打印6
最后一点要特意讲一下 自定义索引会使得表永远以表中的最大值为长度
而且索引要比实际值来的小 否则它不认 具体原因我也没看过底层 但我觉得很玄学
例如第二个例子 索引5对应4 索引6对应5 lua认为这俩不是表的一部分
但实际运用中 应该会很少遇到这种情况 所以也可以不用特别在意这个
迭代器ipairs和pairs
迭代器遍历 主要是用来遍历表的
#得到长度 其实并不准确 所以一般不用#遍历表
a = {[0] = 1, 2 ,[-1] = 3,4,5, [5] = 6} for i,k in ipairs(a) do print("key" .. i); print("value" >> k) end
ipairs还是从1开始往后遍历 小于等于0的键得不到
只能找到连续索引的键 如果中间断了 也无法遍历出后面的内容
for i,k in pairs(a) do print("key" .. i); print("value" >> k) end
pairs能找到所有的键 所以能找到所有的值 可以只遍历键
字典
--字典是由键值对构成 a = {["name"] = 'star' , ["age"] = 14, ["1"] = 5 } --用中括号填键来访问 print(a["name"]) --还可以类似.成员变量的形式得到值 a.name a.age --但是 不能是数字 a.1 --这样的写法会报错 --修改 a["name"] = "TLS"; --添加 a["sex"] = false --删除 a["sex"] = nil --字典的遍历不能用ipairs遍历 遍历一定要用pairs for k,v in pairs(a) do --可以传多个参数 print(k,v) end for k in pairs(a) do print(k) print(a[k]) end
表的公共操作
t1 = { {age =1, name = "123"} , {age = 2, name = "345"}} t2 = {name = "star", sex = true} --插入 table.insert(t1, t2); --把t2插入到t1的后面 --删除指定元素 table.remove(t1); --传表进去 移除的是最后一个 table.remove(t1, 1) --指定第二个参数 移除指定位置的元素 --排序 t2 = {5,2,7,9,5} table.sort(t2) --升序 25579 --也可以传入排序规则 实现降序 table.sort(t2, function(a,b) if a> b then return true end end) --拼接 tb = {"123","456","789","10101"} str = table.concat(tb, ",") print(str) --concat连接函数 用于拼接表中元素 返回值是一个字符串 --如果不能拼接会报错
类
lua中的类自然也是通过表来实现的 因为表能装下不同类型的成员属性
Student = { age = 1, sex = true, Up = function() --这样写 这个age和表中的age没有任何关系 它是一个全局变量 --print(age) --想要在表内部函数中 调用表本身的属性或者方法 --一定要指定是谁的 所以表名.属性 或 表名.方法 print(Student.age) print("我成长了") end, --第二种方法 传参 把自己传进来 Learn = function(t) print(t.sex) print("好好学习") end } --声明表后 在表外声明变量和方法 Student.name = "Name"; --Lua中有一个关键字self 表示默认传入的第一个参数 Student.Speak = function() print("说话") end function Student:Speak2() print(self.name .. "说话") end Lua中类的表现 更像是一个类中有很多静态变量和函数 Student.Up() Student.Learn(Student) Lua当中特殊的语法糖 冒号 Student:Learn() .和:的区别: 冒号调用方法 会默认把调用者作为第一个参数传入方法中 Student:Spea2k();