前言
Lua是目前最流行的轻量级脚本语言,在很多嵌入式设备上已经广泛应用。不仅如此,某些应用程序、网页脚本、游戏开发、数据库等等都使用Lua来对功能进行扩展,比如Redis就能用Lua脚本灵活操作。记得以前博哥也提到过Lua脚本,说明Lua真的值得一学。接下来主要讲述Lua的特别之处,更加详细的语法细节已经记录在wiki上,只需用到的时候查找文档即可。
一、Lua背景介绍
Lua于1993年开发,名字的原意是“Moon”,是一个名词而不是缩写,所以作者建议写为“Lua”,不要写成“LUA”。Lua由标准C语言编写,最大的特点是轻量,在64位Linux下,Lua的解释器247K(最新的版本)、Lua库421K,所有的脚本引擎中Lua是最快的。Lua和Python很容易被拿来比较,但Lua最核心的使用方式是和其他语言配合,而不是像Python自己能完成所有的事情。Lua是开源的,使用MIT协议,你可以在官网下载Lua源码,目前最新的Lua版本是2018年6月26日更新的Lua 5.3.5。此外,Lua支持面向过程编程和函数式编程,通过闭包和table数据结构竟然可以支持面向对象编程的一些机制。而且Lua有自动内存回收机制,十分方便。
二、环境搭建
Debian 8 下安装Lua
wget http://www.lua.org/ftp/lua-5.3.5.tar.gz
tar -zxvf lua-5.3.5.tar.gz
cd lua-5.3.5
make linux test
make install
可能会遇到:lua.c:82:31: fatal error: readline/readline.h: No such file or directory,这是因为缺少libreadline-dev依赖包,解决:
apt-get install libreadline-dev
再安装就好了。
which lua
/usr/local/bin/lua
lua -v
Lua 5.3.5 Copyright (C) 1994-2018 Lua.org, PUC-Rio
Windows Lua IDE 环境搭建
(1)安装本地Lua
和Java、Python类似,下Bin包、加环境变量,lua的环境变量名字是LUA_HOME。
Lua各个版本的Bin包 sf下载 | Lua 5.3.4 for win64百度云下载(密码2p0l)
下载完成后解压到喜欢的位置,复制lua53.exe为lua.exe,添加用户或者系统的环境变量:
测试一下:
(2)配置IDE环境
使用国人开发的开源插件EmmyLua for IntelliJ IDEA(Apache 2.0协议),把IDEA作为Lua的IDE。EmmyLua支持IDEA 2017.1、IDEA2017.2/IDEA2018.1、IDEA2018.2,我使用的IDEA版本是2018.1,所以下载IntelliJ-EmmyLua-1.2.6-IDEA172.zip| 百度云下载(密码64gl),然后文件→设置→插件→Install plugin from disk,选择刚刚下载的ZIP包(不要解压),选择重启IDEA:
lua环境就搭好了,新建项目里就有Lua项目了:
然后来一波helloworld(如果提示lua.exe找不到的,查看环境变量是否配对+手动重启IDEA):
EmmyLua是支持断点调试、自动补全的。
三、Lua主要特性
1、基本信息
大小写敏感、动态类型语言(值有类型,变量无类型)
2、基本语法
(1)格式:字母下划线开头,后跟字母数字下划线。
(2)空:nil
注意,判断空是要加引号作为字符串的:
if test == "nil" or test == 'nil'
(3)逻辑:and、or、not
(4)布尔:true、false
注意,0、""、"0"都是真,只有false、nil是假。
if 0 then
print("0为真")
end
if "" then
print("\"\"为真")
end
if "0" then
print("\"0\"为真")
end
if nil then
print("nil为真")
end
--[[
0为真
""为真
"0"为真
--]]
(5)注释
普通注释:
-- 我是注释
段落注释:
--[[
<html>
<div> </div>
<html>
--]]
(6)数字
数字的字符串看作数字,并且数字只有number(浮点数):
print("1"+"2") -- =3.0
print("1" + "test") -- error
(7)运算符
多了乘幂:^,例如print(2^3)
不等于:~=
求长度:#,例如:
a = "aaa"
print(#a) --3
(8)变量
全局变量:默认全局变量,无论在哪里声明
局部变量:local xxxxx
(9)赋值
支持连着赋值,右值少了则补nil,右值多了则忽略:
a = 1
b = 2
a,b,c = b,a
print(a,b,c) -- 2 1 nil
(10)条件判断
if true then
xxxxx
else if false then
xxxxx
else
xxxx
end
(11)for循环
for i=1,10,1 do -- 从1到10步进1
xxxx
end
for k,v in pairs(table) do -- 遍历table的key、value
xxxx
end
for k,v in ipairs(table) do -- 从下标1开始遍历table的key、value,忽略字符串的key
xxxx
end
(12)其他循环
while...do end
repeat... until
(13)数组
数组的下标是从1开始的!
一维数组:
array = {"Lua", "Tutorial"}
print(array[1]) -- Lua
高维数组:
-- 初始化数组
array = {}
for i=1,3 do
array[i] = {}
for j=1,3 do
array[i][j] = i*j
end
end
-- 访问数组
for i=1,3 do
for j=1,3 do
print(array[i][j])
end
end
(14)文件IO
file = io.open("test.lua", "r")-- 以只读方式打开文件
print(file:read())-- 输出文件第一行
file:close() -- 关闭打开的文件
3、超强数据结构——table
(1)table类似java中的Map,key-value形式。创建方式为:
mytable = {}
mytable[1] = "hello"
mytable[2] = "world"
-- 或者
mytable = {"hello","world"}
-- 或者
mytable = {key = "value"}
(2)table的下标是从1开始的。
(3)table支持同时使用整数和字符串作为key,支持同时使用多种值类型
例如:
mytable={"hello","world"}
mytable["test"] = "bewindoweb"
mytable["test2"] = 1
print(#mytable)
在print上打断点就能看到:
但是注意#只能识别正常的从数字1开始的数目,所以长度为2:
(4)table有两种访问方式
print(mytable[1])
print(mytable.test)
(5)table销毁
mytable=nil
(6)其他
table支持insert、remove、sort、concat、maxn(求table中的最大值,Lua5.2后移除)
4、Lua函数
(1)可以返回多个值:
function myfunc ()
a = 100
b = 200
return a,b
end
(2)可以有可变参:
function average(...)
local arg={...} --> arg 为一个表,局部变量
end
(3)可以被赋值、并作为参数传递
function myprint(msg)
print("hello "..msg)
end
function mymain(anyfunc,msg)
anyfunc(msg)
end
myfunc = myprint
mymain(myfunc,"world")
5、Lua字符串
(1)三种创建方式
str = "hello"
str = 'hello'
str = [[hello]]
(2)字符串替换
string.gsub(mainString,findString,replaceString,num)
str = "hello bwb lua bwb bwb"
print(
string.gsub(str,"bwb","world",2)
)
-- hello world lua world bwb 2
(3)字符串查找
string.find (str, substr, [init, [end]])
str = "hello bwb lua bwb bwb"
print(
string.find(str,"bwb",1)
)
-- 7 9
(4)缩水版正则匹配
由于Lua模式匹配只用了500行代码,当然不可能实现POSIX的4000行代码的标准正则,所以只能匹配一些常见的东西(已经足够使用了)。简单列举一些有特点的(详细的见wiki):
占位符 | 含义 | 占位符 | 含义 |
---|---|---|---|
%a | 所有字母 | %l | 所有小写字母 |
%p | 所有标点符号 | %u | 所有大写字母 |
%w | 所有字母和数字 | %z | 所有0值字符 |
%S:所有的大写占位符都是其补集,比如%S
代表非空白字符
。
%n:这里的 n 可以从 1 到 9; 这个条目匹配一个等于 n 号捕获物(后面有描述)的子串。
%bxy:这里的 x 和 y 是两个明确的字符; 这个条目匹配以 x 开始 y 结束, 且其中 x 和 y 保持 平衡 的字符串。 意思是,如果从左到右读这个字符串,对每次读到一个 x 就 +1 ,读到一个 y 就 -1, 最终结束处的那个 y 是第一个记数到 0 的 y。 举个例子,条目 %b() 可以匹配到括号平衡的表达式。
举个例子:
str = "hello bwb lua bwb bwb \0"
s1,s2,s3,s4 = string.match(str,"(%bbb).*(%1)%s+(%a+)%s+(%z+)$") --
print(s1,s2,s3,s4)
6、模块和包
(1)Lua可以定义模块(由table实现):
-- 文件名为 module.lua
module = {} -- 定义一个名为 module 的模块
module.constant = "这是一个常量" -- 定义一个常量
function module.func1() -- 定义一个函数
io.write("这是一个公有函数!\n")
end
local function func2()
print("这是一个私有函数!")
end
function module.func3()
func2()
end
return module
(2)Lua从环境变量LUA_PATH
中加载模块
require("模块名")
require "模块名"
(3)Lua可以加载C的库
local path = "/usr/local/lua/lib/libluasocket.so"
-- 或者 path = "C:\\windows\\luasocket.dll",这是 Window 平台下
local f = assert(loadlib(path, "luaopen_socket"))
f() -- 真正打开库
7、Lua难点之一——元表
元表(Metatable)是table的一个东西,初始化:
mytable = {} -- 普通表
mymetatable = {} -- 元表
setmetatable(mytable,mymetatable) -- 把 mymetatable 设为 mytable 的元表
或者:
mytable = setmetatable({},{})
元表的意义在于,定义了table的操作对应的一组函数,执行自己想要的逻辑,其实有点像运算符重载。举个例子,当table产生赋值操作时,发现table没有这个key,那么有两种做法,一种是直接报错,一种是新插入这个键值对。那么就可以在table的元表的__newindex元方法里面写下这些逻辑。
例如用table实现一个集合Set,并带有交集和并集的方法:
Set = {}
Set.mt = {} --将所有集合共享一个metatable
function Set.new (t) --新建一个表
local set = {}
setmetatable(set,Set.mt)
for _, l in ipairs(t) do set[l] = true end
return set
end
function Set.union(a,b) --并集
local res = Set.new({})--注意这里是大括号
for i in pairs(a) do res[i] = true end
for i in pairs(b) do res[i] = true end
return res
end
function Set.intersection(a,b) --交集
local res = Set.new({}) --注意这里是大括号
for i in pairs(a) do
res[i] = b[i]
end
return res
end
function Set.tostring(set) --打印函数输出结果的调用函数
local s = "{"
local sep = ""
for i in pairs(set) do
s = s..sep..i
sep = ","
end
return s.."}"
end
function Set.print(set) --打印函数输出结果
print(Set.tostring(set))
end
Set.mt.__add = Set.union
s1 = Set.new{1,2}
s2 = Set.new{3,4}
print(getmetatable(s1))
print(getmetatable(s2))
s3 = s1 + s2
Set.print(s3)
Set.mt.__mul = Set.intersection --使用相乘运算符来定义集合的交集操作
Set.print((s1 + s2)*s1)
8、Lua难点之二——协同程序
(1)什么是协同(coroutine)?
Lua 协同程序(coroutine)与线程比较类似:拥有独立的堆栈,独立的局部变量,独立的指令指针,同时又与其它协同程序共享全局变量和其它大部分东西。 协同是非常强大的功能,但是用起来也很复杂。
(2)线程和协同程序区别
线程与协同程序的主要区别在于,一个具有多个线程的程序可以同时运行几个线程,而协同程序却需要彼此协作的运行。 在任一指定时刻只有一个协同程序在运行,并且这个正在运行的协同程序只有在明确的被要求挂起的时候才会被挂起。 协同程序有点类似同步的多线程,在等待同一个线程锁的几个线程有点类似协同。
Lua的协程在底层实现就是一个线程。
(3)协程执行时间
当使用resume触发事件的时候,协程create的函数就被执行了;
当遇到yield的时候就代表挂起当前协程,等候再次resume触发事件。
(4)一个具体的例子来看yield和resume
function foo (a)
print("foo 函数输出", a)
return coroutine.yield(2 * a) -- 返回 2*a 的值
end
co = coroutine.create(function (a , b)
print("第一次协同程序执行输出", a, b) -- co-body 1 10
local r = foo(a + 1)
print("第二次协同程序执行输出", r)
local r, s = coroutine.yield(a + b, a - b) -- a,b的值为第一次调用协同程序时传入
print("第三次协同程序执行输出", r, s)
return b, "结束协同程序" -- b的值为第二次调用协同程序时传入
end)
print("main", coroutine.resume(co, 1, 10)) -- true, 4
print("main", coroutine.resume(co, "r")) -- true 11 -9
print("main", coroutine.resume(co, "x", "y")) -- true 10 end
print("main", coroutine.resume(co, "x", "y")) -- cannot resume dead coroutine
第一次resume,create的function开始执行,接受到a=1,b=10,打印1,10;
进入foo函数,a' = a+1 = 2,打印2,2*a = 4,yield返回4,所以coroutine.resume打印出来是4;
然后主程序第二次resume,协程接受到参数"r",这时候coroutine.yield的值为"r",从foo函数的return开始执行,即 return "r";
协程继续执行,打印"r",用yield返回a+b=11,a-b=-9;coroutine.resume打印11,-9……
直到最后,协程退出,主程序还想去resume协程,已经没有协程在yield了,因此报错。
整个过程就是主程序和协程在相互传递参数,在某些地方这种传递是很有用的。
9、Lua难点之三——面向对象与元表
(1)类的设计(用table+function实现),很容易理解:
-- Meta class
Shape = {area = 0}
-- 基础类方法 new
function Shape:new (o,side)
o = o or {}
setmetatable(o, self)
self.__index = self
side = side or 0
self.area = side*side;
return o
end
-- 基础类方法 printArea
function Shape:printArea ()
print("面积为 ",self.area)
end
-- 创建对象
myshape = Shape:new(nil,10)
myshape:printArea()
(2)继承和多态
看似是继承和多态,其实就是一个table在模拟而已:
Rectangle = Shape:new()
-- 派生类方法 new
function Rectangle:new (o,length,breadth)
o = o or Shape:new(o)
setmetatable(o, self)
self.__index = self
self.area = length * breadth
return o
end
-- 派生类方法 printArea
function Rectangle:printArea ()
print("矩形面积为 ",self.area)
end
-- 创建对象
myrectangle = Rectangle:new(nil,10,20)
myrectangle:printArea()
10、Lua内存回收机制
Lua构建了增量标记-扫描收集器
,使用垃圾收集器间歇率
和垃圾收集器步进倍率
来控制扫描频率,两个数字都是百分比(100代表100%)。
(1)间歇率表明开启新循环前需要等待多久:值越大,回收越慢
值小于100,则下次循环前不会有任何等待;
值等于200,则表明收集器需要等总内存使用量达到之前的2倍才开始新循环
(2)步进倍率表明收集器的运作速度相对于内存分配速度的倍率:值越大,回收越快
值小于100,则内存分配速度永远比回收要快,这是禁止的;
默认值等于200,表明收集器以内存分配速度的2倍速工作;
值非常大,则相当于暂停了应用程序的运行,进行一次完整的垃圾回收了。
总结
作为脚本语言,Lua并不复杂,强大的table数据结构让Lua能够实现很多功能。Lua还有一些错误处理、调试、数据库等API,平时应该不会用到,用到的时候再去查找相关资料即可。
一天能学到的只是皮毛,Talk is cheap,期待有机会实践。