lua 是一门用标准 C 语言编写的、轻量小巧的脚本语言,它常常被嵌入到各种 C 语言项目中做扩展语言。它与 C 的兼容性不必多说,本身也是用 C 写的,速度也是非常的快。像 Redis 、 Nginx 、 AwesomeWM 等都拿它做扩展语言,现在 Neovim 也加入了增强 lua 语言扩展,性能也是非常好。虽然我目前没有用 lua 语言扩展的打算,但也不打算未来不用。总之,先学起来。

Learn Lua in Y minutes

lua 快速学习, https://learnxinyminutes.com/docs/lua/

数据类型

主要分为:

  • nil
  • number
  • boolean
  • string
  • function
  • table

我们一个个看。

nil

local a -- output: nil

nil 代表空值,默认没有定义时就是 nil

local a = 1
a = nil

当变量被赋 nil ,变量其实也就被删除了,值在正确的时候会被释放。

number

local num = 42  -- All numbers are doubles.
-- Don't freak out, 64-bit doubles have 52 bits for
-- storing exact int values; machine precision is
-- not a problem for ints that need < 52 bits.

number 跟 JavaScript 一样 int 和 double 不分。

boolean

local a = true
local b = false

boolean 值主要就是两个: truefalse

string

s = 'walternate'  -- Immutable strings like Python.
t = "double-quotes are also fine"
u = [[ Double brackets
       start and end
       multi-line strings.]]
e = [=[string have a [[]].]=]

lua 有三种表示字符串的方法:单引号、双引号以及括号。前面两个没有什么好说的,考好主要是用以表示多行字符串,以及不转义的字符串表达,想要规避中括号则用 [=[]=] ,不想规避直接 [[]] 即可。

function

local function foo()
    print("in the function")
    --dosomething()
    local x = 10
    local y = 20
    return x + y
end

local a = foo    --把函数赋给变量

print(a())

lua 中视函数也是值,于是我们可以借助回调的手段实现闭包。

table

local corp = {
    web = "www.google.com",   --索引为字符串,key = "web",
                              --            value = "www.google.com"
    telephone = "12345678",   --索引为字符串
    staff = {"Jack", "Scott", "Gary"}, --索引为字符串,值也是一个表
    100876,              --相当于 [1] = 100876,此时索引为数字
                         --      key = 1, value = 100876
    100191,              --相当于 [2] = 100191,此时索引为数字
    [10] = 360,          --直接把数字索引给出
    ["city"] = "Beijing" --索引为字符串
}

lua 借助 table 类型以实现面向对象、字典、数组等特性。

值得一提的是, lua 作为数组是,下标是从 1 开始的。

局部变量和全局变量

通过上面的代码其实你应该已经注意到了 local 这个关键字, local 代表局部变量。我们不用 local 声明变量不存在语法上的问题,不过此时,我们的变量变成了全局变量。如果不是必要的话,这将会污染我们的全局变量命名空间。因此,还请谨慎把握。

控制流

  • if/else
  • while
  • repeat
  • for
  • break, return 和 goto

if/else

if num > 40 then
  print('over 40')
elseif s ~= 'walternate' then  -- ~= is not equals.
  -- Equality check is == like Python; ok for strs.
  io.write('not over 40\n')  -- Defaults to stdout.
else
  -- Variables are global by default.
  thisIsGlobal = 5  -- Camel case is common.

  -- How to make a variable local:
  local line = io.read()  -- Reads next stdin line.

  -- String concatenation uses the .. operator:
  print('Winter is coming, ' .. line)
end

我们可以注意到, lua 的风格是 pascal 式的。

与 C/C++ 不同的是, lua 的 elseif 是写在一起的,并不等同于 else 里嵌 if

while

while num < 50 do
  num = num + 1  -- No ++ or += type operators.
end

repeat

repeat
  print('the way of the future')
  num = num - 1
until num == 0

repeat 类似于 do-while ,但条件与 do-while 是相反的,这点需要注意。

for

karlSum = 0
for i = 1, 100 do  -- The range includes both ends.
  karlSum = karlSum + i
end

-- Use "100, 1, -1" as the range to count down:
fredSum = 0
for j = 100, 1, -1 do
  fredSum = fredSum + j
end

这里的 1, 100 表示数字 1 到 100 ,且步长为 1 。 100, 1, -1 表示数字 100 到 1 ,且步长为 -1 。这里对一个数字范围进行了迭代。

-- 打印数组a的所有值
local a = {"a", "b", "c", "d"}
for i, v in ipairs(a) do
  print("index:", i, " value:", v)
end
-- 打印table t中所有的key
for k in pairs(t) do
    print(k)
end

这里则对 table 进行了迭代。

break, return 和 goto

break

-- 计算最小的x,使从1到x的所有数相加和大于100
sum = 0
i = 1
while true do
    sum = sum + i
    if sum > 100 then
        break
    end
    i = i + 1
end
print("The result is " .. i)  -->output:The result is 14

return

local function add(x, y)
    return x + y
    --print("add: I will return the result " .. (x + y))
    --因为前面有个return,若不注释该语句,则会报错
end

local function is_positive(x)
    if x > 0 then
        return x .. " is positive"
    else
        return x .. " is non-positive"
    end

    --由于return只出现在前面显式的语句块,所以此语句不注释也不会报错
    --,但是不会被执行,此处不会产生输出
    print("function end!")
end

local sum = add(10, 20)
print("The sum is " .. sum)  -->output:The sum is 30
local answer = is_positive(-10)
print(answer)                -->output:-10 is non-positive

lua 还可以用 do-end 括起来。

local function foo()
    print("before")
    do return end
    print("after")  -- 这一行语句永远不会执行到
end

goto

在 lua 中我们使用 goto 来实现 continue

for i = 1, 3 do
    if i <= 2 then
        print(i, "yes continue")
        goto continue
    end

    print(i, " no continue")

    ::continue::
    print([[i'm end]])
end

在其他语言里,一般我们不建议使用 goto ,主要是因为 goto 过分灵活会破坏程序结构。实质上,只要你能把握好代码结构, goto 可以更好的简化代码。

and, or , not

lua 采用了关键字进行布尔运算,类似于 python 。

函数

function fib(n)
  if n < 2 then return 1 end
  return fib(n - 2) + fib(n - 1)
end

-- Closures and anonymous functions are ok:
function adder(x)
  -- The returned function is created when adder is
  -- called, and remembers the value of x:
  return function (y) return x + y end
end
a1 = adder(9)
a2 = adder(36)
print(a1(16))  --> 25
print(a2(64))  --> 100

-- Returns, func calls, and assignments all work
-- with lists that may be mismatched in length.
-- Unmatched receivers are nil;
-- unmatched senders are discarded.

x, y, z = 1, 2, 3, 4
-- Now x = 1, y = 2, z = 3, and 4 is thrown away.

function bar(a, b, c)
  print(a, b, c)
  return 4, 8, 15, 16, 23, 42
end

x, y = bar('zaphod')  --> prints "zaphod  nil nil"
-- Now x = 4, y = 8, values 15...42 are discarded.

-- Functions are first-class, may be local/global.
-- These are the same:
function f(x) return x * x end
f = function (x) return x * x end

-- And so are these:
local function g(x) return math.sin(x) end
local g; g  = function (x) return math.sin(x) end
-- the 'local g' decl makes g-self-references ok.

-- Trig funcs work in radians, by the way.

-- Calls with one string param don't need parens:
print 'hello'  -- Works fine.

lua 的函数定义其实一看就懂,这里比较特色的是, lua 函数传参允许传更多的参数,虽然多出来的会被抛弃;可以不用圆括号;多返回值。

当我们不为函数设定名字是,该函数就是匿名函数。函数也分全局函数和局部函数,前面讲过,不赘述。

函数回调

unpack = table.unpack or unpack

local function run(x, y)
    print("run", x, y)
end

local function attack(targetId)
    print("targetId", targetId)
end

local function callback(method, ...)
    local args = {...} or {}
    method(unpack(args, 1, #args))
end

callback(run, 1, 2)
callback(attack, 1111)

简单的面向对象

Dog = {}                                   -- 1.

function Dog:new()                         -- 2.
  newObj = {sound = 'woof'}                -- 3.
  self.__index = self                      -- 4.
  return setmetatable(newObj, self)        -- 5.
end

function Dog:makeSound()                   -- 6.
  print('I say ' .. self.sound)
end

mrDog = Dog:new()                          -- 7.
mrDog:makeSound()  -- 'I say woof'         -- 8.
-- 1. Dog acts like a class; it's really a table.
-- 2. function tablename:fn(...) is the same as
--    function tablename.fn(self, ...)
--    The : just adds a first arg called self.
--    Read 7 & 8 below for how self gets its value.
-- 3. newObj will be an instance of class Dog.
-- 4. self = the class being instantiated. Often
--    self = Dog, but inheritance can change it.
--    newObj gets self's functions when we set both
--    newObj's metatable and self's __index to self.
-- 5. Reminder: setmetatable returns its first arg.
-- 6. The : works as in 2, but this time we expect
--    self to be an instance instead of a class.
-- 7. Same as Dog.new(Dog), so self = Dog in new().
-- 8. Same as mrDog.makeSound(mrDog); self = mrDog.

下面是继承:

LoudDog = Dog:new()                           -- 1.

function LoudDog:makeSound()
  s = self.sound .. ' '                       -- 2.
  print(s .. s .. s)
end

seymour = LoudDog:new()                       -- 3.
seymour:makeSound()  -- 'woof woof woof'      -- 4.

-- 1. LoudDog gets Dog's methods and variables.
-- 2. self has a 'sound' key from new(), see 3.
-- 3. Same as LoudDog.new(LoudDog), and converted to
--    Dog.new(LoudDog) as LoudDog has no 'new' key,
--    but does have __index = Dog on its metatable.
--    Result: seymour's metatable is LoudDog, and
--    LoudDog.__index = LoudDog. So seymour.key will
--    = seymour.key, LoudDog.key, Dog.key, whichever
--    table is the first with the given key.
-- 4. The 'makeSound' key is found in LoudDog; this
--    is the same as LoudDog.makeSound(seymour).

-- If needed, a subclass's new() is like the base's:
function LoudDog:new()
  newObj = {}
  -- set up newObj
  self.__index = self
  return setmetatable(newObj, self)
end

模块

local _M = {}

local function get_name()
    return "Lucy"
end

function _M.greeting()
    print("hello " .. get_name())
end

return _M

在这里我们定义了一个 table ,并在最后将它 return 出去, _M 就是我们这个模块所要导出的数据。注意,这里全局变量其实是会有影响的,不定义为 local 才能暴露出去。

local my_module = require("my")
my_module.greeting()     -->output: hello Lucy

require 加载模块,返回值就是之前的 _M

其他一些要注意的

数组大小的获取

local a = {1, 2, 3, 4}
print #a

# 操作符可以获取数组的长度。

虚变量

local t = {1, 3, 5}

print("all  data:")
for i,v in ipairs(t) do
    print(i,v)
end

print("")
print("part data:")
for _,v in ipairs(t) do
    print(v)
end

虚变量指的是 _ ,一般我们不会去读它的值。虚变量可以被多次使用。

function foo()
    return 1, 2, 3, 4
end

local _, _, bar = foo();    -- 我们只需要第三个
print(bar)

一般就是用来占位的。

比较运算符

lua 的不等于为 ~= ,其他的与 C 基本一致。

调用代码前先定义函数

lua 必须先定义,后调用,这与 JavaScript 还有 Python 不一样。

点号与冒号操作符的区别

obj = { x = 20 }

function obj:fun1()
    print(self.x)
end

等价于

obj = { x = 20 }

function obj.fun1(self)
    print(self.x)
end