C#使用者的lua学习笔记——作用域和复杂数据类型

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();

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注