[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++的{})块中。

应该尽可能的使用局部变量,有两个好处:

  1. 避免命名冲突
  2. 访问局部变量的速度比全局变量更快

我们给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)()

全文完。