感受 lua

2021/09/20 lua 总结 共 5199 字,约 15 分钟

前言

这两天稍微看了看 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

lua 教程

Programming in lua (first edition)

lua 5.3 Reference Manual

lua 查找表元素过程(元表、__index方法是如何工作的)

文档信息

Search

    Table of Contents