[toc]
缘起
由于工作中用到了 OpenResty(如果你不知道它,没关系,我不怪你。),所以就顺带学习了一下 Lua。看了 Lua 的官方文档,觉得这们语言还是非常容易上手的。所以这里就根据官方文档进行了稍微的翻译,并整理成此文,希望对大家有所帮助。
多学点东西,一则可以打发时间,二则可以提高身段。
注释
- 单行注释:
--
- 多行注释:
--[[ --]]
数据类型
变量
Lua的数字只有double型,64bits,你不必担心Lua处理浮点数会慢(除非大于100,000,000,000,000),或是会有精度问题。
你可以以如下的方式表示数字,0x开头的16进制和C是很像的。
num = 1024
num = 3.0
num = 3.1416
num = 314.16e-2
num = 0.31416E1
num = 0xff
num = 0x56
字符串你可以用单引号,也可以用双引号,还支持C类型的转义,比如: ‘\a’ (响铃), ‘\b’ (退格), ‘\f’ (表单), ‘\n’ (换行), ‘\r’ (回车), ‘\t’ (横向制表), ‘\v’ (纵向制表), ‘\’ (反斜杠), ‘\”‘ (双引号), 以及 ‘\” (单引号)
下面的四种方式定义了完全相同的字符串(其中的两个中括号可以用于定义有换行的字符串)
a = 'alo\n123"'
a = "alo\n123\""
a = '\97lo\10\04923"'
a = [[alo
123"]]
C语言中的NULL在Lua中是nil,比如你访问一个没有声明过的变量,就是nil,比如下面的v的值就是nil
v = UndefinedVariable
布尔类型只有nil和false是 false,数字0啊,‘’空字符串(’\0’)都是true!
另外,需要注意的是:lua中的变量如果没有特殊说明,全是全局变量,那怕是语句块或是函数里。变量前加local关键字的是局部变量。
theGlobalVar = 50
local theLocalVar = "local variable"
全局变量
全局变量不需要声明,给一个变量赋值后即创建了这个全局变量,访问一个没有初始化的全局变量也不会出错,只不过得到的结果是nil
。
> print(b)
nil
> b = 10
> print(b)
10
如果我们想删除一个全局变量,只需要将变量赋值为nil
即可:
> b = 10
> print(b)
10
> b = nil
> print(b)
nil
这样变量b就好像从没被使用过一样。换句话说, 当且仅当一个变量不等于nil时,这个变量存在。
局部变量与代码块
使用local
创建一个局部变量,与全局变量不同,局部变量只在被声明的那个代码块内有效。代码块:指一个控制结构内,一个函数体,或者一个chunk
(变量被声明的那个文件或者文本串)。
x = 10
local i = 1 -- local to the chunk
while i<=x do
local x = i*2 -- local to the while body
print(x) --> 2, 4, 6, 8, ...
i = i + 1
end
if i > 20 then
local x -- local to the "then" body
x = 20
print(x + 2)
else
print(x) --> 10 (the global one)
end
print(x) --> 10 (the global one)
注意,如果在交互模式下上面的例子可能不能输出期望的结果,因为第二句local i=1
是一个完整的chunk,在交互模式下执行完这一句后,Lua将开始一个新的chunk,这样第二句的i已经超出了他的有效范围。可以将这段代码放在do..end
(相当于c/c++的{})块中。
应该尽可能的使用局部变量,有两个好处:
- 避免命名冲突
- 访问局部变量的速度比全局变量更快
我们给block划定一个明确的界限:do..end
内的部分。当你想更好的控制局部变量的作用范围的时候这是很有用的。
do
local a2 = 2*a
local d = sqrt(b^2 - 4*a*c)
x1 = (-b + d)/a2
x2 = (-b - d)/a2
end -- scope of 'a2' and 'd' ends here
print(x1, x2)
表达式
三元运算符(这里有点Python的味道):
max = (x > y) and x or y
语句
这里主要介绍一下Lua中的控制语句。
while
直接看代码:
sum = 0
num = 1
while num <= 100 do
sum = sum + num
num = num + 1
end
print("sum =", sum)
for
直接看代码:
sum = 0
for i = 1, 100 do
sum = sum + i
end
print("1到100的和: ", sum)
sum = 0
for i = 1, 100, 2 do
sum = sum + i
end
print("1到100的奇数的和: ", sum)
sum = 0
for i = 100, 1, -2 do
sum = sum + i
end
print("100到1的偶数的和: ", sum)
until
直接看代码:
sum = 2
repeat
sum = sum ^ 2 --幂操作
print(sum)
until sum >1000
函数
一般情况下,函数调用时,不要忽略其后面的一对小括号。如果该函数只有一个参数,而且这个参数是一个字面量的字符串或是一个表的话,那么函数后面的一对小括号可以省略。
Lua函数实参和形参的匹配与赋值语句类似,多余部分被忽略,缺少部分用nil补足。
function f(a, b) return a or b end
CALL PARAMETERS
f(3) a=3, b=nil
f(3, 4) a=3, b=4
f(3, 4, 5) a=3, b=4 (5 is discarded)
多返回值
Lua的函数可以返回多个返回值。可以返回多返回值的语言不是很多。目前据我所知可以返回多个返回值的语言有:
- Python
- Swift
- Golang
- Lua
- Julia
目前我只知道上述语言的函数是可以返会多值的,如果还有其它编程语言可以返回多值,请大家告诉我。
废话不多说了,直接看个例子:
shell$ lua
> function test ()
>> name = "lavenliu"
>> age = 28
>> sex = boy
>> return name, age, sex
>> end
> = test()
lavenliu 28 nil
> name, age, sex = test()
> = name
lavenliu
> = age
28
> = sex
nil
上述我是在交互式环境中直接输入的代码,我把它抽出来,写成一个文件。如下:
-- filename: func01.lua
function test ()
name = "lavenliu"
age = 28
sex = "boy"
return name, age, sex
end
print(test()) -- 直接调用该函数
name, age, sex = test() -- 调用并赋值给name, age, sex三个变量
print("name is: " .. name)
print("age is: " .. age)
print("sex is: " .. sex)
执行结果为:
LavenLius-MacPro:lua liuchuan$ lua func01.lua
lavenliu 28 boy
name is: lavenliu
age is: 28
sex is: boy
如果你有其他语言的编程基础,上述代码对你完全不是问题。
可变参数
一个函数也可以接收不定数量的参数的,就像print
函数一样,我们可以给这样的函数传递多个参数。今天的绝大部分语言都是支持的,当然今天介绍的 Lua 语言也是没有问题的。
我们直接介绍其规则吧,在函数的参数列表中,如果出现...
,则表示当前函数是可变参数的函数。举个例子看看就明白了,这里很像 Golang 哦。
function add (...)
local s = 0
for i, v in ipairs{...} do
s = s + v
end
return s
end
print(add(3, 4, 10, 25, 12))
执行一下,看看效果:
LavenLius-MacPro:lua liuchuan$ lua func02.lua
54
再看一个进阶的例子:
function hello (message, ...) -- message 被称为固定参数(或者位置参数),...被称为额外参数
print(message, ...)
end
-- 用不同的参数多调用几次看看有什么输出
hello()
hello("lavenliu")
hello("lavenliu", "You are so handsome!")
hello("lavenliu", "You are so handsome!", "I know")
hello("lavenliu", "You are so handsome!", "I know", "You can say that again")
执行结果为:
LavenLius-MacPro:lua liuchuan$ lua func03.lua
nil
lavenliu
lavenliu You are so handsome!
lavenliu You are so handsome! I know
lavenliu You are so handsome! I know You can say that again
我们在函数中要迭代额外参数该怎么办呢?我们可以使用 {...}
表达式,就像上面的那个 add
函数一样使用。
使用 {...}
我们无法知道该序列中的尾部是否含有 nil
。Lua 提供了 table.pack()
函数,该函数同样返回一个 table,并且还有一个额外的参数 n
,表示这个表中的参数的个数。接下来我们就看个例子,这个例子演示了如何判断额外的参数中是否含有 nil
这样的值。代码如下:
function nonils (...)
local arg = table.pack(...)
for i = 1, arg.n do
if arg[i] == nil then return false end
end
return true
end
print(nonils(2, 3, nil)) --> false
print(nonils(2, 3)) --> true
print(nonils()) --> true
print(nonils(nil)) --> false
print(nonils(nil, 2, 3)) --> false
执行结果如下:
LavenLius-MacPro:lua liuchuan$ lua func04.lua
false
true
true
false
false
命名参数
Lua 中的函数传参形式是按位置进行传递的。第一个函数参数变量对应第一个参数,以此类推。但有时通过给参数指定一个名字会来得更加方便些。绝大部分的语言都支持命名参数,我们大 Lua 也不能不支持不是?使用命名参数的好处是,看到这样的函数签名后,我们就知道需要传递具体什么样的参数了。
看个例子吧:
function Window (options)
-- check mandatory options
if type(options.title) ~= "string" then
error("no title")
elseif type(options.width) ~= "number" then
error("no width")
elseif type(options.height) ~= "number" then
error("no height")
end
return options
end
w = Window{x=0, y=0, width=300, height=200,
title="Lua", border=true}
for k, v in pairs(w) do
print(k, v)
end
执行结果为:
LavenLius-MacPro:lua liuchuan$ lua func05.lua
width 300
x 0
border true
y 0
title Lua
height 200
这里给大家一个稍微复杂的例子,八皇后的问题。代码如下:
-- filename: eigth_queen.lua
local N = 8 -- board size
-- check wheather position (n,c) is free from attacks
local function isplaceok (a, n, c)
for i = 1, n-1 do -- for each queen already placed
if (a[i] == c) or -- same column?
(a[i] - 1 == c -n) or -- same diagonal?
(a[i] + i == c + n) then -- same diagonal?
return false -- place can be attacked
end
end
return true -- no attacks; place is OK
end
-- print a board
local function printsolution (a)
for i = 1, N do
for j = 1, N do
io.write(a[i] == j and "X" or "-", " ")
end
io.write("\n")
end
io.write("\n")
end
-- add to board 'a' all queen from 'n' to 'N'
local function addqueen (a, n)
if n > N then
printsolution(a)
else
for c = 1, N do
if isplaceok(a, n, c) then
a[n] = c
addqueen(a, n+1)
end
end
end
end
-- run the program
addqueen({}, 1)
大家可以动手执行一下,看看结果如何?我这里贴一下部分执行结果:
LavenLius-MacPro:lua liuchuan$ lua eight_queen.lua
X - - - - - - -
- - X - - - - -
- - - X - - - -
- - - - X - - -
- - - - - X - -
- - - - - - X -
- - - - - - - X
- X - - - - - -
X - - - - - - -
- - X - - - - -
- - - - - X - -
- X - - - - - -
- - - - - - - X
- - - X - - - -
- - - - X - - -
- - - - - - X -
...还有很多输出,省略之
表
表在Lua中是唯一的一种数据结构。用它可以表示其他语言中的数组、记录(或字典)、列表、队列及集合等数据结构。因为 Lua 只有一种数据结构,那么 Lua 实现上述数据结构时效率是非常高的。
在 Lua 中,可以使用表来模拟各种数据结构。直接贴上代码看看吧,大家记得动手试试哦。
a = {} -- new array
for i = 1, 1000 do
a[i] = 0
end
print(#a)
-- create an array with indices from -5 to 5
a = {}
for i = -5, 5 do
a[i] = 0
end
-- matrices and multi-dimentional arrays
mt = {} -- create the matrix
for i = 1, N do
mt[i] = {} -- create a new row
for j = 1, M do
mt[i][j] = 0
end
end
-- another way to create matrix
mt = {}
for i = 1, N do
for j = 1, M do
mt[(i-1)*M + j] = 0
end
end
function mult (a, rowindex, k)
local row = a[rowindex]
for i, v in pairs(row) do
row[i] = v * k
end
end
-- linked lists
list = nil
list = {next = list, value = v}
-- traverse the list
local l = list
while l do
-- -- 这里是伪代码,记得跳过哦
-- l = l.next
end
-- Queues and double queues
function ListNew ()
return {first = 0, last = -1}
end
-- to avoid polluting the global space,
-- we will define all list operations inside a table,
List = {}
function List.new ()
return {first = 0, last = -1}
end
-- now, we can insert or remove an element at both ends
function List.pushfirst (list, value)
local first = list.first - 1
list.first = first
list[first] = value
end
function List.pushlast (list, value)
local last = list.last + 1
list.last = last
list[last] = value
end
function List.popfirst (list)
local first = list.first
if first > list.last then
error("list is empyt")
end
local value = list[first]
list[first] = nil -- to allow garbage collection
list.first = first + 1
return value
end
function List.poplast (list)
local last = list.last
if list.first > last then error("list is empty") end
local value = list[last]
list[last] = nil -- to allow garbage collection
list.last = last - 1
return value
end
-- sets and bags
reserved = {
["while"] = true,
["end"] = true,
["function"] = true,
["local"] = true,
}
for w in all_words() do
if not reserved[w] then
---- -- 'w' is not reserved word
end
end
function Set (list)
local set = {}
for _, l in ipairs(list) do set[l] = true end
return set
end
reserved = Set{"while", "end", "function", "local",}
function insert (bag, element)
bag[element] = (bag[element] or 0) + 1
end
function remove (bag, element)
local count = bag[element]
bag[element] = (count and count > 1) and count - 1 or nil
end
-- string buffers
locat t = {}
for line in io.lines() do
t[#t+1] = line .. "\n"
end
local s = table.concat(t)
数组
a = {} -- new array
for i = 1, 1000 do
a[i] = 0
end
-- the length operator('#') uses this fact to find the size of an array
print(#a) -- 1000
We can start an array at index 0, 1, or any other value:
-- creates an array with indices from -5 to 5
a = {}
for i = -5, 5 do
a[i] = 0
end
矩阵与多维数组
mt = {} -- create the matrix
for i = 1, N do
mt[i] = {} -- create a new row
for j = 1, M do
mt[i][j] = 0
end
end
元表与元方法
MetaTable与MetaMethod是Lua中最重要的语法,MetaTable主要是一些语言中(C++
或Python
)的操作符重载功能.
还有一点儿Python中__getattribute__
的味道。
比如,我们有两个分数:
fraction_a = {numberator=2, denominator=3}
fraction_b = {numberator=4, denominator=7}
我们想实现分数间的相加:2/3 + 4/7
,我们如果要执行fraction_a+fraction_b
会报错的。
所以,我们可以用MetaTable来实现:
fraction_op={}
function fraction_op.__add(f1, f2)
ret = {}
ret.numerator = f1.numerator * f2.denominator + f2.numerator * f1.denominator
ret.denominator = f1.denominator * f2.denominator
return ret
end
为之前定义的两个table设置MetaTable:
setmetatable(fraction_a, fraction_op)
setmetatable(fraction_b, fraction_op)
于是,我们就可以如下这样做了(调用的是fraction_op.__add()
函数):
fraction_s = fraction_a + fraction_b
至于__add
,在 Lua 中称为MetaMethod,这是Lua内建约定的,其它的还有如下的MetaMethod:
__add(a, b) 对应表达式 a + b
__sub(a, b) 对应表达式 a - b
__mul(a, b) 对应表达式 a * b
__div(a, b) 对应表达式 a / b
__mod(a, b) 对应表达式 a % b
__pow(a, b) 对应表达式 a ^ b
__unm(a) 对应表达式 -a
__concat(a, b) 对应表达式 a .. b
__len(a) 对应表达式 #a
__eq(a, b) 对应表达式 a == b
__lt(a, b) 对应表达式 a < b
__le(a, b) 对应表达式 a <= b
__index(a, b) 对应表达式 a.b
__newindex(a, b, c) 对应表达式 a.b = c
__call(a, ...) 对应表达式 a(...)
标准库
面向对象编程
我们看到了__index
这个重载,这个内建方法重载了find key的操作。这个操作可以让Lua变得有点面向对象的感觉,还让其有点像JavaScript的prototype。
一个简单的例子:
-- create class
Account = {balance = 0}
-- constructor
function Account:new (o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end
-- method `deposit'
function Account:deposit (v)
self.balance = self.balance + v
end
-- method `withdraw'
function Account:withdraw (v)
if v > self.balance then error"insuficient funds" end
self.balance = self.balance - v
end
-- use example
a = Account:new()
print(a.balance) --> 0
a:deposit(1000.00)
a:withdraw(100.00)
print(a.balance) --> 900
第二个例子:
Person = {}
function Person:new (p)
local obj = p
if (obj == nil) then
obj = {name="LavenLiu", age=28, handsome=true}
end
self.__index = self
return setmetatable(obj, self)
end
function Person:to_string ()
return self.name .." : ".. self.age .." : ".. (self.handsome and "handsome" or "ugly")
end
-- example
me = Person:new()
print(me:to_string())
kf = Person:new{name="King's fucking", age=70, handsome=false}
print(kf:to_string())
模块
我们可以直接使用require(“model_name”)来载入别的lua文件,文件的后缀是.lua。载入的时候就直接执行那个文件了。比如:
我们有一个hello.lua的文件:
print("Hello, World!")
如果我们:require(“hello”),那么就直接输出Hello, World!了。
注意:
1)require函数,载入同样的lua文件时,只有第一次的时候会去执行,后面的相同的都不执行了。
2)如果你要让每一次文件都会执行的话,你可以使用dofile(“hello”)函数
3)如果你要玩载入后不执行,等你需要的时候执行时,你可以使用 loadfile()函数,如下所示:
local hello = loadfile("hello")
... ...
... ...
hello()
loadfile(“hello”)后,文件并不执行,我们把文件赋给一个变量hello,当hello()时,才真的执行。
当然,更为标准的玩法如下所示。假设我们有一个文件叫mymod.lua,内容如下:
-- filename: mymod.lua
local MyModel = {}
local function getname()
return "Laven Liu."
end
function MyModel.Greeting()
print("Hello, My name is "..getname())
end
return MyModel
于是我们可以这样使用:
-- filename: use_mymod.lua
local my_model = require("mymod")
my_model.Greeting()
执行一下看看:
LavenLius-MacPro:lua liuchuan$ lua use_mymod.lua
Hello, My name is Laven Liu.
其实,require干的事就如下:(所以你知道为什么我们的模块文件要写成那样了)
local my_model = (function ()
--mymod.lua文件的内容--
end)()
全文完。