前言
这两天稍微看了看 lua ,小巧奇妙的语言,他有很多有意思的特性,在这总结一下。
lua 特性
- 轻量级: 它用标准C语言编写并以源代码形式开放,编译后仅仅一百余K,可以很方便的嵌入别的程序里。
- 可扩展: lua 提供了非常易于使用的扩展接口和机制:由宿主语言(通常是C或C++)提供这些功能, lua 可以使用它们,就像是本来就内置的功能一样。
- 其它特性
- 支持面向过程(procedure-oriented)编程和函数式编程(functional programming);
- 自动内存管理;只提供了一种通用类型的表(table),用它可以实现数组,哈希表,集合,对象;
- 语言内置模式匹配;闭包(closure);函数也可以看做一个值;提供多线程(协同进程,并非操作系统所支持的线程)支持;
- 通过闭包和table可以很方便地支持面向对象编程所需要的一些关键机制,比如数据抽象,虚函数,继承和重载等。
数据结构
基本类型
string 和 number
所有数都是双精度类型的实浮点数。甚至连int都没有,也没有整除。
function
函数是被看作是”第一类值(First-Class Value)”,函数可以存在变量里,因此, lua 可以函数式编程。
nyaaar@nyaaar-ubu:~/myPrograms/ lua $ lua
lua 5.3.3 Copyright (C) 1994-2016 lua .org, PUC-Rio
> function factorial1(n)
>> if n == 0 then
>> return 1
>> else
>> return n * factorial1(n - 1)
>> end
>> end
> print(factorial1(5))
120
> factorial2 = factorial1
> print(factorial2(6))
720
函数可以有多个返回值。用 , 隔开就可以。
table
更像是一个 map,无论是索引还是内容都是没有限制的。无论是 number,string,function 还是 table 都能存。
> table={}
> type(table)
table
> table[1]="qwe"
> table[1]
qwe
> table[2]=123
> table[2]
123
> type(table[1])
string
> type(table[2])
number
>
键可以多种多样
> table[1.1]
nil
> table[1.1]="awsdf"
> table[1.1]
awsdf
> table["hello"]="hi"
> table["hello"]
hi
>
function也可以
> table[1]=factorial1
> table[1](5)
120
>
甚至可以引用自己
> table[3]
nil
> table[3]=table
> type(table[3])
table
> table[3]
table: 0x560fe2007f60
> table[3][1]
fail
> table[1]="qwe"
> table[3][1]
qwe
> table[3][3]
table: 0x560fe2007f60
>
真是非常自由啊。
nil
nil,如果调用未赋值的变量,就会返回 nil。同时,nil 在条件判断的时候,是 false,而0(number)是 true。 lua ,真的很神秘。
nil 也可以算作析构,给一个变量赋 nil,就会被 collectgarbage, lua 的垃圾回收机制回收。
元表(Metatable)
lua 有趣的一个地方来了,元表。
lua 的 table 由于使用实在是太过自由,因此 lua 提供了元表(Metatable),允许我们改变 table 的行为,每个行为关联了对应的元方法。
> table={}
> metatable={}
> setmetatable(table,metatable)
table: 0x560a1c091da0
> getmetatable(table)
table: 0x560a1c094400
>
比如__index
> fallback[1]="fail"
> fallback[1]
fail
> table=setmetatable({},{__index=fallback})
> table[1]
fail
>
table 在通过键取值失败时,就会去找 table 的 metatable 的__index,这个__index 可以是另一个 table,也可能是函数。
lua 查找一个表元素时的规则,其实就是如下 3 个步骤:
- 1.在表中查找,如果找到,返回该元素,找不到则继续
- 2.判断该表是否有元表,如果没有元表,返回 nil,有元表则继续。
- 3.判断元表有没有 __index 方法,如果 __index 方法为 nil,则返回 nil;如果 __index 方法是一个表,则重复 1、2、3;如果 __index 方法是一个函数,则返回该函数的返回值。
还有__newindex 元方法,为表添加操作符,__call 元方法,__tostring 元方法,不一一说明了,我的理解就是 metatable 对于table 进行了一定程度的修饰,包装,让 table 的使用更加轻松了,有意思的是 metatable 也是一个 table,这个特性甚至没有引入新的东西,真不错。
垃圾回收(collectgarbage)
weaktable
lua 中垃圾回收是自动的,这里还有一个有意思的弱表的机制,用于回收存在表里的数据。
metatable 也能赋“v”,此时会自动回收表中的 value
> table={}
> metatable={}
> setmetatable(table,metatable)
table: 0x560fe1fd7830
> metatable.__mode="k" -- 设置metatable的__mode为“k”,此时表为弱表,即weaktable
> key={}
> table[key]=1
> key={}
> table[key]=2
> for k,v in pairs(table) do print(k); print(v); print("----") end
table: 0x560fe200ff50
1
----
table: 0x560fe2005f90
2
----
> collectgarbage() -- forces a garbage collection cycle
0
> for k,v in pairs(table) do print(k); print(v); print("----") end
table: 0x560fe2005f90
2
----
>
如果不赋值,就不会回收
> table={}
> key={}
> table[key]=1
> key={}
> table[key]=2
> for k,v in pairs(table) do print(k); print(v); print("----") end
table: 0x560fe1ffcf10
1
----
table: 0x560fe20070d0
2
----
> collectgarbage()
0
> for k,v in pairs(table) do print(k); print(v); print("----") end
table: 0x560fe1ffcf10
1
----
table: 0x560fe20070d0
2
----
>
协同程序(coroutine)
lua 协同程序(coroutine)与线程比较类似:拥有独立的堆栈,独立的局部变量,独立的指令指针,同时又与其它协同程序共享全局变量和其它大部分东西。
> co = coroutine.create(
>> function(i)
>> print(i);
>> end
>> )
> print(coroutine.status(co))
suspended
> coroutine.resume(co, 1)
1
true
> print(coroutine.status(co))
dead
>
yield
> co2 = coroutine.create(
>> function()
>> for i=1,10 do
>> print(i)
>> if i == 3 then
>> print(coroutine.status(co2)) --running
>> print(coroutine.running()) --thread:XXXXXX
>> end
>> coroutine.yield()
>> end
>> end
>> )
> coroutine.resume(co2)
1 -- function()中的 print
true -- coroutine.resume()返回是否执行成功
> coroutine.resume(co2)
2
true
> coroutine.resume(co2)
3
running
thread: 0x560fe1fd7188 false
true
> coroutine.resume(co2)
4
true
> print(coroutine.status(co2))
suspended
> print(coroutine.running())
thread: 0x560fe1fd32a8 true
>
yield 让协程能暂时挂起
python 中的 yield 是不是就是来自这里的?
> function foo (a)
>> print("foo", a)
>> return coroutine.yield(2 * a)
>> end
> co = coroutine.create(function (a , b)
>> print("co-body", a, b)
>> local r = foo(a + 1)
>>
>> print("co-body", r)
>> local r, s = coroutine.yield(a + b, a - b)
>>
>> print("co-body", r, s)
>> return b, "end"
>> end)
> print("main", coroutine.resume(co, 1, 10))
co-body 1 10
foo 2
main true 4
> print("main", coroutine.resume(co, "rrrr"))
co-body rrrr
main true 11 -9
> print("main", coroutine.resume(co, "qwer","asdf"))
co-body qwer asdf
main true 10 end
> print("main", coroutine.resume(co, "qwer","asdf"))
main false cannot resume dead coroutine
>
协程还能再次接受参数,很强大,看着就能简化很多复杂的问题。
总结
一看到 lua 我就觉得像 python,同是作脚本用的语言,和c联系紧密,找了个好爹,然而经过一番学习思考,发现无论是功能,还是用途上,差别都很多。
总结一下,就是 lua 仍然是c,或者说c的一部分,而 python 毫无疑问是另一种语言了。python 的 c 内嵌是方便地使用 c,python 本身和 c 距离已经太远了,有自己的哲学,python 是主体。而 lua 就是完全无法摆脱 c 了,更像是 c 的延伸,很多处理的方式,还是 c。
lua 和 python 的比较,其实就是 c 和 python的 比较了,至此,已经没啥比的必要了。这里还有一个问题可以反映这个事实, lua 里面甚至没有多线程的说法,只有 coroutine,那么谁来管这个事情呢?我猜是 c,c 里面可以在不同的线程创建 lua 的虚拟机。
lua 是一种小巧有意思的语言,但该有的东西都有了,垃圾回收也有。coroutine 这种优秀的设计,yield 这种优秀的特性,都被后来者吸收了。值得从这些后来者溯源学习一下。
reference
Programming in lua (first edition)
lua 查找表元素过程(元表、__index方法是如何工作的)
文档信息
- 本文作者:nyaaar
- 本文链接:https://nyaaarlathotep.github.io/2021/09/20/%E6%84%9F%E5%8F%97lua/
- 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)