Menu

  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay
  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay

Lua学习笔记

13
May
2016

Lua学习笔记

By Alex
/ in Lua
0 Comments
安装

参考如下命令,编译并安装Lua解释器:

Shell
1
2
3
4
5
6
curl -R -O http://www.lua.org/ftp/lua-5.3.4.tar.gz
tar zxf lua-5.3.4.tar.gz
cd lua-5.3.4
sudo apt-get install libreadline-dev
make linux
make install INSTALL_TOP=/home/alex/Lua/sdk/5.3.4
语言基础
变量

要声明一个变量,使用如下语法: varname = value 

任何变量,除非显式添加local限定符,否则均为全局变量,不管它在何处声明:

Lua
1
2
globalVar = true
local localVar = nil
局部变量

Lua支持块级作用域,局部变量的作用域在所在块内部。

全局变量

一般的,应当严格避免使用自己定义的全局变量。

Lua在一个名为 _G的表格中管理所有全局变量:

Lua
1
2
_G.globalVar
_G["globalVar"]
虚变量

类似于Go语言的空白标识符。如果多返回值中有你不需要的,可以将其赋值给 _

操作符

需要注意Lua不支持++、--、+=之类的操作符。

Lua中的逻辑或与非操作符为 or、 and、 not,不等于操作符为 ~=

Lua中有一个特殊操作符 #,它可以获取对象的长度,实际上是调用元方法__len()

类型
数字

Lua中的数字均为双精度浮点型,你不需要担心运算精度或者速度问题。示例:

Lua
1
2
3
4
5
-- 语句尾部的分号是可选的
num = 4096
num = 6.12
num = 6.02E23
num = 0xff
字符串

可以使用单引号、双引号包围,支持多行字符串,支持C风格的转义字符。示例: 

Lua
1
2
3
4
str = "Hello World"
str = 'Hello\n World'
str = [[Hello
World]]
空值

访问一个未定义变量时,其值为空,使用关键字 nil表示。 

布尔

有true和false两个值。类型转换时,仅nil转换为false,其余的例如数字0、空串都转换为true。

表格

Lua不区分数组和映射,以整数作为键的表格,可以作为数组来使用:

Lua
1
2
3
4
5
6
7
-- 如果不显示指定键,则自动从1开始为每个元素赋予键
users = { "Alex", "Meng", "Cai", "Dang" }
-- 数组的第一个元素的索引为1,而不是0
print(users[1])  -- Alex
-- 可以使用复数索引
users[-1] = 'Nobody'
print(users[-1]) -- Nobody

以字符串作为键的表格,则相当于其它编程语言中的映射或字典:

Lua
1
2
3
4
5
6
7
8
9
10
11
12
user = {
    -- 注意语法,使用=号指定键值对
    name = "Alex Wong",
    age = 32,
    gender = 'M',
    -- 也可以使用方括号语法指定键值对
    ['nickname'] = 'Alex'
}
-- 方括号语法、点号导航都支持
user.name = "汪震"
user.name = nil
user.nickname = '震'

要获取表格的元素个数,可以使用 #操作符:

Lua
1
2
3
4
5
users = { [1] = 'Alex', [2] = 'Meng', [3] = 'Cai', [4] = 'Dang' }
-- #users 可以获取表格users的长度
for i = 1, #users do
    print(users[i])
end

要遍历表格的键值对,可以:

Lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for idx, user in pairs( users ) do
    print( idx, user )
end
 
 
array = {
    ['Z'] = 'z',
    [0] = 'a',
    [1] = 'b',
    [10] = 'b'
};
-- pairs打印所有键值对
for i, v in pairs(array) do print(i .. '=' .. v) end
-- ipairs只能打印从索引从1开始的,而且不能有中断
for i, v in ipairs(array) do print(i .. '=' .. v) end -- 只打印1=b

注意:不要在表格中使用 nil 值,如果一个元素要删除,直接 remove,不要用 nil 去代替:

Lua
1
2
3
users = { 1, 2, 3 }
table.remove(users, 1)
print(table.concat(users, ' ')) -- 2 3
注释
Lua
1
2
3
4
5
-- 单行注释
 
--[[
多行注释
--]]
控制结构
循环
Lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
n, i, factorial = 10, 1, 1
 
-- while循环
while i <= n do
    factorial = factorial * i
    i = i + 1
end
print(factorial) -- 3628800
 
-- for循环
for i = 1, n do
    factorial = factorial / i
end
print(factorial) -- 1.0
 
-- until循环
i = 1
repeat
    factorial = factorial * i
    i = i + 1
until i > n 
分支
Lua
1
2
3
4
5
6
7
8
if len > 100 then
    print("L")
elseif len <= 100 and len > 50 then
    print("M")
elseif len ~= 0 then
    print("S")
else
end
函数

Lua中函数必须在先定义,然后再使用。也就是说,在脚本中函数定义出现在前面。

Lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
function getUserName(id)
    return "Alex"
end
 
-- 支持返回多个值
function getUser(id)
    return id, "Alex", 32
end
 
id, name, age = getUser(1)
-- 如果左边变量不足,多余的返回值自动丢弃
-- 如果左边变量过多,后面的全部置为nil
 
 
-- 函数调用出现在列表表达式中时,除非函数调用在列表尾部,否则仅仅返回其第一个值
local function twonums() return 1, 2 end
-- 注意下面两个列表表达式的区别
local x, y, z = twonums(), 3;
print(x, y, z) -- 1    3    nil
local x, y, z = 3, twonums();
print(x, y, z) -- 3    1    2
-- print的实参也是列表表达式
print(twonums(), 3)    -- 1       3
print(3, twonums())    -- 3    1    2
-- 要强制仅仅返回第一个值,可以用()包围函数调用
print(3, (twonums()))  -- 3    1
 
 
-- 支持闭包
function counter()
    local count = 0
    return function()
        count = count + 1
        return count
    end
end
 
c = counter()
print(c(), c()) -- 1  2
 
-- 函数可以递归调用自身
function fib(n)
    if n < 2 then return 1 end
    return fib(n - 2) + fib(n - 1)
end
 
-- 具有局部作用域的函数
local function f()
end
 
-- 函数可以赋值给变量
local f = function() return 0 end
f()
 
 
 
-- 变长参数
local function join(...)
    local args = { ... } -- 把变长参数填充到表格
    print(table.concat(args, "."))
end
 
-- LuaJIT 2尚不能JIT编译这种变长参数的用法,只能解释执行。所以对性能敏感的代码,应当避免使用此种形式
join('cc', 'gmem', 'study') -- cc.gmem.study
 
-- 当表格作为参数时,传递的是引用
local function printUser(user)
    print(user.name, user.age)
end
printUser({ name = 'Alex', age = 32 })
 
 
-- 调用回调
local function call(func, ...)
    local args = { ... } or {}
    -- unpack,解包表格
    func(...)
end
local function add(x, y, z) print(x + y + z) end
call(add, 1, 2, 3) -- 6
方法

作为表格成员的函数,即为方法:

Lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Person = {
    name = "Unknown"
}
-- 方法可以用TableName.MethodName的形式定义,此时第一个形参必须为self
-- 代表当前对象
function Person.getName(self)
    return self.name
end
 
-- 方法也可以用TableName:MethodName的形式定义,此时隐式的自动定义self局部变量
function Person:getName()
    return self.name
end
 
print(Person:getName()) -- Unknown
MetaTable

MetaTable可以用来实现操作符重载:

Lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- 元表格
user_ops = {}
-- __add是一种元方法(MetaMethod)
function user_ops.__add(u1, u2)
    return 'child of ' .. u1.name .. ' and ' .. u2.name
end
 
alex = { name = 'Alex' }
meng = { name = 'Meng' }
-- 可以为任何表格设置元表格
setmetatable(alex, user_ops)
setmetatable(meng, user_ops)
 
print(alex + meng) -- child of Alex and Meng

所有可用的元方法如下表:

元方法 说明
__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,可以实现基于原型的继承机制,类似于JavaScript:

Lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
Flower = {}
print(Flower) -- 0x1cc1400
-- 定义构造器
function Flower:new(f)
    -- self指向调用者本身 0x1cc1400 也就是Flower这个表格
    -- 下面这个表达式将Flower变为元表格
    self.__index = self
    print(self)
    -- 调用setmetatable(a, {__index = b}),即让b作为a的原型
    -- 在这里,就是让self成为f的原型
    return setmetatable(f, self)
end
 
-- 定义普通方法
function Flower:getColor()
    return self.color
end
 
-- 也可以使用点形式定义,但是self需要作为第一个形参
function Flower.getColor(self)
    return self.color
end
 
-- 实例化新对象
jasmine = Flower:new({ color = 'White' })
print(jasmine.color) -- White
 
-- 调用方法,注意使用冒号而非点号
print(jasmine:getColor()) -- White
-- 如果用点号调用,则必须把self作为第一个参数传入
print(jasmine.getColor(jasmine)) -- White
 
-- 继承
Jasmine = Flower:new({ color = 'White' })
function Jasmine:new(j)
    self.__index = self
    return setmetatable(j, self)
end
 
function Jasmine:getSmell()
    return self.smell
end
 
function Jasmine:toString()
    -- 可以在方法中定义方法
    return self:getColor() .. ' Jasmine Smells ' .. self.smell
end
 
jasmine = Jasmine:new({ smell = 'Nice' })
print(jasmine:toString()) -- White Jasmine Smells Nice
模块

你可以使用 require('XX')语句来引入其它模块,也就是XX.lua文件。require时目标.lua文件会自动被执行。注意:

  1. 如果多次require同一个文件,则仅仅第一次会执行目标文件
  2. 如果想多次执行某个文件,调用 dofile('xx')
  3. 如果仅仅希望载入模块,但是不执行其中的代码,可以调用:
    Lua
    1
    2
    3
    4
    local xxModule = loadfile("XX")
    -- ...
    -- 下面的调用导致目标模块被执行
    xxModule()
定义模块

一个长方形模块的示例:

rectangle.lua
Lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
-- 此局部变量,一个表格,作为模块成员的容器
local _M = {}
-- 此属性,通常作为模块版本
_M._VERSION = '1.0'
 
local mt = { __index = _M }
 
function _M.new(self, width, height)
    return setmetatable({ width=width, height=height }, mt)
end
 
function _M.get_rectangle(self)
    return self.width * self.height
end
 
function _M.get_circumference(self)
    return (self.width + self.height) * 2
end
 
-- 返回模块容器
return _M

使用此模块:

Lua
1
2
3
4
5
local rectangle = require "rectangle"
 
local s = rectangle:new(1, 2)
print(s:get_rectangle())          -- 2
print(s:get_circumference())   -- 6 
错误处理

要在 Lua 中处理错误,必须使用函数 pcall(protected call)来包装需要执行的代码。 pcall接受以下参数:

  1. 被调用的函数
  2. 转发给被调用函数的参数

pcall的返回值有两个:是否调用成功,被调用函数的返回值。

示例:

Lua
1
2
3
4
5
6
7
8
function json_decode( str )
    -- 安全的解码JSON
    local ok, t = pcall(_json_decode, str)
    if not ok then
      return nil
    end
    return t
end
常用模块
string
Lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
-- string.byte(s [, i [, j ]])
-- 获取字符串s从i到j这些字符的ASCII码,返回列表
a, b, c = string.byte("abc", 1, 3)
print(a, b, c) -- 97    98    99
-- string.char (...)
-- 从列表ASCII码构建字符串
print(string.char(97, 98, 99)) -- abc
 
-- 大小写转换
print(string.upper("Hello Lua")) -- HELLO LUA
print(string.lower("Hello Lua")) -- hello lua
 
-- 重复字符串
print(string.rep("abc", 3))      -- abcabcabc
 
-- 返回子串 string.sub(s, i [, j])
print(string.sub(123456,3,5))    -- 345
 
-- 替换子串 string.gsub(s, p, r [, n])
-- 替换s中的p为r,n表示替换的最大次数
-- 返回两个值:替换后的字符串,替换发生的次数
print(string.gsub("Lua Lua Lua", "Lua", "hello")) -- hello hello hello    3
 
-- 获得字符串长度
str = "hello lua"
print(string.len(str))
print(#str) -- 推荐使用这种方式获得长度,获取字节数
 
-- string.find(s, p [, init [, plain]])
-- 子串查找,从s中勋撰p,可以指定搜索起始位置init。返回子串的索引
sidx, eidx = string.find('123456789', '45')
print(sidx, eidx) -- 4    5
 
-- C风格的字符串格式化  string.format(formatstring, ...)
print(string.format("%.4f", 3.1415926)) -- 3.1416
print(string.format("%d %x %o", 31, 31, 31)) -- 31 1f 37
print(string.format("%02d/%02d/%d", 1, 1, 2018)) -- 01/01/2018
 
-- 模式匹配,不支持JIT,考虑使用ngx.re.match代替
-- string.match(s, p [, init]) 从s中搜索模式p,返回匹配的子串
print(string.match("Linux Zircon 3.13.0-83-generic", "%d.%d+.%d%-%d+")) -- 3.13.0-83
 
-- 全局模式匹配,返回一个迭代器,此迭代器依次返回每个匹配的子串
for word in string.gmatch("quick fox jumps over the lazy dog", "%w+") do
    print(word)
end
table

此库将表格作为数组来操作。

Lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
-- table.getn(t) 获取表格长度
-- 注意:获取到的是最后一个数字下标
print(table.getn({ 1, 2, 3 }))                -- 3
print(table.getn({ 1, two = 2, 3 }))          -- 2 最后一个索引从1开始顺延
print(table.getn({ 1, 2, nil, 4, 5, 6 }))     -- 6 如果最后是非nil,其索引被使用
print(table.getn({ 1, 2, nil, 4, 5, nil }))   -- 2 如果最后是nil,则第一个nil之前的索引被使用
 
-- table.maxn (table) 返回最大的索引值
print(table.maxn({ [1000] = 0 })) -- 1000
 
-- table.concat (table [, sep [, i [, j ] ] ]) 连接数组元素为字符串
-- sep没认为空串,i为起始元素,j为结束元素
print(table.concat({'Alex','Meng'}))          -- AlexMeng
 
 
-- table.insert (table, [pos ,] value) 插入元素到数组,pos默认为表长度+1,即默认为附加元素
array = { 1, 2, 3 }
table.insert(array, 4)
print(array[4])  -- 4
 
 
-- table.remove (table [, pos]) 删除指定位置上的元素,默认从尾部删除
table.remove(array)
print(#array) -- 3
 
 
-- table.sort (table [, comp]) 根据传入的比较器来排序
local function compare(x, y)
    --从大到小排序
    return x > y
end
 
table.sort(array) -- 默认从小到大
table.sort(array,compare) -- 从大到小

LuaJIT 2.1 新增加 table.new 和 table.clear。前者用来预分配 Lua table 空间,后者用来高效的释放 table 空间,都可以被 JIT 编译。 

时间

os模块的一些函数,提供了日期、时间相关的功能,但是比较重量级,不支持被LuaJIT编译。使用OpenResty时推荐使用ngx_lua模块提供的接口。

Lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
-- os.time ([table]) 返回当前时间,或者table所指定的时间(从UNIX纪元开始流逝的秒数
print(os.time()) -- 当前时间
a = { year = 1970, month = 1, day = 1, hour = 8, min = 0, sec = 10 }
print(os.time(a)) -- 10,因为当前是东八区
 
-- os.difftime (t2, t1) 返回两个时间的差值(t2-t1) 单位秒
a = { year = 1970, month = 1, day = 1, hour = 8, min = 0, sec = 10 }
b = { year = 1970, month = 1, day = 1, hour = 8, min = 0, sec = 15 }
print(os.difftime(os.time(a), os.time(b))) -- -5
 
 
-- os.date ([format [, time]]),返回一个描述时间的表格
-- format 格式说明,time时间点(秒)
local tab1 = os.date("*t")
local now = ""
for k, v in pairs(tab1) do --把tab1转换成一个字符串
    now = string.format("%s %s = %s ", now, k, tostring(v))
end
print(now) -- hour = 18  min = 3  wday = 2  day = 26  month = 2  year = 2018  sec = 52  yday = 57  isdst = false
 
-- 下面的占位符用于格式化日期
-- %a    一星期中天数的简写(例如:Wed)
-- %A    一星期中天数的全称(例如:Wednesday)
-- %b    月份的简写(例如:Sep)
-- %B    月份的全称(例如:September)
-- %c    日期和时间(例如:07/30/15 16:57:24)
-- %d    一个月中的第几天[01 ~ 31]
-- %H    24小时制中的小时数[00 ~ 23]
-- %I    12小时制中的小时数[01 ~ 12]
-- %j    一年中的第几天[001 ~ 366]
-- %M    分钟数[00 ~ 59]
-- %m    月份数[01 ~ 12]
-- %p    “上午(am)”或“下午(pm)”
-- %S    秒数[00 ~ 59]
-- %w    一星期中的第几天[1 ~ 7 = 星期天 ~ 星期六]
-- %x    日期(例如:07/30/15)
-- %X    时间(例如:16:57:24)
-- %y    两位数的年份[00 ~ 99]
-- %Y    完整的年份(例如:2015)
-- %%    字符'%'
print(os.date('%A'))        -- Monday
print(os.date('%Y-%m-%d'))  -- 2018-02-26
数学
Lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
math.max(x, ...)               -- 返回参数中值最大的那个数
math.min(x, ...)               -- 返回参数中值最小的那个数
 
math.abs(x)                    -- 返回x的绝对值
math.fmod(x, y)                   -- 返回 x对y取余数
math.pow(x, y)                   -- 返回x的y次方
math.sqrt(x)                   -- 返回x的算术平方根
math.exp(x)                       -- 返回自然数e的x次方
math.log(x)                       -- 返回x的自然对数
math.log10(x)                   -- 返回以10为底,x的对数
math.floor(x)                   -- 返回最大且不大于x的整数
math.ceil(x)                   -- 返回最小且不小于x的整数
 
 
math.rad(x)                       -- 角度x转换成弧度
math.deg(x)                       -- 弧度x转换成角度
math.sin(x)                       -- 求弧度x的正弦值
math.cos(x)                       -- 求弧度x的余弦值
math.tan(x)                       -- 求弧度x的正切值
math.asin(x)                   -- 求x的反正弦值
math.acos(x)                   -- 求x的反余弦值
math.atan(x)                   -- 求x的反正切值
 
-- 不传入参数时,返回 一个在区间[0,1)内均匀分布的伪随机实数
-- 只使用一个整数参数m时,返回一个在区间[1, m]内均匀分布的伪随机整数
-- 使用两个整数参数时,返回一个在区间[m, n]内均匀分布的伪随机整数
math.random (m,n)
-- 为伪随机数生成器设置一个种子,设置后math.random产生的随机序列改变
math.randomseed (x)
 
pi=math.pi                        -- 圆周率
文件

Lua I/O提供的文件操作都是阻塞性的,不应该在OpenResty中和非阻塞的网络IO混合在一起。

Lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
path = [[/home/alex/Lua/projects/luna/logs/error.log]]
-- 设置输入输出默认针对的文件
file = io.input(path)
-- 逐行读取默认文件
repeat
    line = io.read() -- 读取一行
    if (line) then print(line) end
until (nil == line)
io.close(file) -- 关闭文件描述符
 
-- 以追加模式打开文件
file = io.open(path, "a+")
-- 所有可用的文件打开模式
-- 默认     说明                                            文件不存在时的行为
-- "r"        读模式 (默认)                                    返回nil加错误信息
-- "w"        写模式                                            创建文件
-- "a"        添加模式                                        创建文件
-- "r+"        更新模式,保存之前的数据                        返回nil加错误信息
-- "w+"        更新模式,清除之前的数据                        创建文件
-- "a+"        添加更新模式,保存之前的数据,在文件尾进行添加    创建文件
 
-- 设置默认输出文件
io.output(file)
io.write("\nHello Lua")
 
 
 
-- 调用file:***方法,可以针对特定文件(而不是默认文件)进行读写
file = io.open(path, "r")
-- file:lines()方法返回所有行的迭代器
for line in file:lines() do
    print(line)
end
 
-- 其它方法:
-- file:read (...)  读取数据
-- file:write (...) 写入数据:
--     *n 读取一个数字
--     *a 从当前位置读取整个文件。若当前位置为文件尾,则返回空字符串
--     *l 读取下一行的内容。若为文件尾,则返回nil
--    100 读取100字节
print(file:read(10))
 
-- file:seek ([whence] [, offset]) 设置和获取当前文件位置
-- offset 向前搜索的偏移量
-- whence 偏移量的相对位置:set相对文件开始;cur相对当前位置(默认);end相对文件结束
file:seek('cur', 100)
-- file:setvbuf (mode [, size])  设置输出文件的缓冲模式
-- mode 模式:no无缓冲;full全缓冲(缓冲区满了后才输出);line最多缓冲一行
file:setvbuf('full', 2048)
 
-- file:flush ()  刷出文件缓冲区
file:flush()
-- file:close ()  关闭文件
file:close() 
LuaJIT
简介

LuaJIT是Lua的一个即时(Just-In-Time)编译器,是最快的动态语言实现之一。通过编译,Lua的执行效率大大提高。LuaJIT被大量的用在游戏、网络等编程领域。LuaJIT仅仅是一个独立的可执行程序,其命令行选项和标准的Lua没有太大区别。

LuaJIT 的运行时环境包括一个用手写汇编实现的 Lua 解释器和一个可以直接生成机器代码的 JIT 编译器。

安装
Shell
1
2
3
4
wget http://luajit.org/download/LuaJIT-2.0.5.tar.gz
tar xzf LuaJIT-2.0.5.tar.gz && rm LuaJIT-2.0.5.tar.gz
cd LuaJIT-2.0.5/
make install PREFIX=/home/alex/Lua/LuaJIT/2.0.5
FFI

C函数要能够作为Lua扩展库,其接口必须遵循 typedef int (*lua_CFunction)(lua_State* L)的形式。此接口的入参是lua_State类型的指针,从Lua传递来的实参,必须通过该指针间接的读取。返回值int表示返回值的数量,返回值本身无法直接传递回去,必须使用虚拟栈来传递。开发Lua扩展库非常麻烦。

FFI是LuaJIT中最重要的一个扩展库。支持从纯Lua代码调用外部C函数,使用C数据结构。有了FFI,你就不再需要编写Lua扩展库,另外FFI的性能比扩展库更高。

术语
名词 说明
cdecl 一个抽象的C类型的定义,表现为Lua字符串
ctype C类型对象,属于一种特殊的cdata,由 ffi.typeof()获得,你可以调用它,获得cdata对象
cdata C数据对象,和ctype类型匹配的值
ct C类型格式,可以指cdecl, cdata, ctype
cb 回调对象,持有一个特殊的函数指针,从C代码调用此函数指针,会执行对应的Lua函数
VLA 变长数组,例如 int[?]。数组元素的个数,必须在创建VLA时给出
VLS 变长结构,即最后一个成员是VLA的C结构体
ffi模块
ffi.cdef

用于声明(而不是定义)C函数或者C数据结构。所有使用到的函数都需要声明。

函数可以是 C 标准函数,第三方库函数,或自定义的函数。

据结构可以是结构体、枚举或者是联合体。

示例:

Lua
1
2
3
4
ffi.cdef [[
    typedef struct foo { int a, b; } foo_t;
    int printf(const char *fmt, ...);
]]
ffi.load

通过给定的名称来加载动态库,调用格式: ffi.load(name [,global])。

动态库可以指定全限定路径,如果仅仅指定basename则必须位于LD_LIBRARY_PATH,动态库扩展名可以不加:

Lua
1
ffi.load("z")   -- 寻找libz.so,或者z.dll

如果global设置为true,则在POSIX系统中目标动态库中的符号被加载到命名空间 ffi.C。例如:

Lua
1
2
3
4
5
6
7
ffi.load('z',true)
 
ffi.cdef[[
    int z(int x, int y);
]]
 
local res = ffi.C.z(1, 2)
ffi.typeof 

创建一个ctype对象。调用格式: ctype = ffi.typeof(ct)。示例:

Lua
1
2
3
4
5
6
7
8
9
local uintptr_t = ffi.typeof("uintptr_t")
local c_str_t = ffi.typeof("const char*")
local int_t = ffi.typeof("int")
local int_array_t = ffi.typeof("int[?]")
 
-- 示例:
local int_t = ffi.typeof("int")
local i = int_t(10)
print(i)  -- cdata<int>: 0x401258e8
ffi.new 

在堆内存中开辟空间,创建指定类型的ct。调用格式:

Lua
1
2
3
4
-- 从指定的ct创建cdata,对于VLA/VLS类型,需要指定元素个数(nelem)
cdata = ffi.new(ct [,nelem] [,init...])
-- 等价方式:
cdata = ctype([nelem,] [init...])

使用ffi.new分配的 cdata 对象指向的内存块是由垃圾回收器 LuaJIT GC 自动管理的,不需要用户去释放内存。

使用 ffi.C.malloc()分配的空间不使用 LuaJIT 自己的分配器,需要开发人员自己负责释放。但是ffi.C.malloc返回的cdata对象仍然由LuaJIT GC管理。

你可以通过 ffi.gc()为ffi.C.malloc返回的cdata对象注册析构函数,并在此函数中调用 ffi.C.free()。这样的话,当cdata对象被GC后,C内存也可以被释放。

ADM64上LuaJIT能够管理的内存量在2G这个级别,如果要分配非常大的内存,请使用ffi.C.malloc()

示例代码:

Lua
1
2
local int_array_t = ffi.typeof("int[?]")
local array = ffi.new(int_array_t, 100)
ffi.fill

填充内存。调用格式: ffi.fill(dst, len [,c])。 类似于C函数 memset(dst, c, len)

ffi.copy

拷贝内存。调用格式:

Lua
1
2
3
4
-- src作为const void *看待
-- dst作为void *看待
ffi.copy(dst, src, len)
ffi.copy(dst, str) 
ffi.cast

将指定的值强制转换为cdata。调用格式: cdata = ffi.cast(ct, init)

cdata的初始值从init得到,使用C类型转换约定。示例:

Lua
1
2
local c_str_t = ffi.typeof("const char*")
local c_str = ffi.cast(c_str_t, "Hello")
ffi.gc

注册一个析构函数(finalizer)。调用格式: cdata = ffi.gc(cdata, finalizer)

示例:

Lua
1
2
3
4
5
-- 返回注册了析构函数之后的cdata
local p = ffi.gc(ffi.C.malloc(n), ffi.C.free)
 
p = nil -- 消除最后一个引用
-- 下一个GC周期中,会自动调用ffi.C.free(p)
ffi.errno

获取上一次C函数调用的error。调用格式: err = ffi.errno([newerr])。如果指定newerr,则返回旧error,并将error设置为新值。

ffi.string

从指针创建Lua字符串。调用格式: str = ffi.string(ptr [,len])

其中ptr为C指针。如果:

  1. 省略len,则ptr自动作为char*看待,也就是\0结束的字符串
  2. 否则,ptr被作为void*看待
回调

每当Lua函数被转换为C函数指针时,LuaJIT FFI 都会自动创建特殊的callback。这种情况可以:

  1. 隐式发生,例如在把Lua函数传递给C函数的函数指针参数时
  2. 显式发生,你可以调用 ffi.cast()来强制转换Lua函数为C函数指针

目前,仅仅特定的C函数类型可以作为用作回调。

FFI为回调提供了两个特殊方法。

cb:free()

释放和回调相关的资源,关联的Lua函数将可以被GC。对应的函数指针不再有效,不能再被调用。

cb:set(func)

将回调关联到一个新的Lua函数,此回调的C类型、回调的函数指针保持不变。

垃圾回收

不管隐式创建的cdata,还是ffi.new(), ffi.cast()显式创建的cdata,都可以被垃圾回收。使用cdata时,你必须确保在Lua栈、upvalue或Lua table上保留对cdata的有效引用,一旦最后一个引用失效,则在下一个GC周期中cdata会被自动释放。

如果你要分配一个数组类型的cdata给一个指针的话,必须保证cdata有额外的引用:

Lua
1
2
3
4
5
6
7
8
9
ffi.cdef[[
typedef struct { int *a; } foo_t; // 包含指针成员的结构
]]
 
local s = ffi.new("foo_t", ffi.new("int[10]")) -- 错误
 
local a = ffi.new("int[10]")   -- 正确,自己持有cdata
local s = ffi.new("foo_t", a)  -- 然后传递给C指针
-- 直到不需要了,你才能销毁引用a
调用C函数

在LuaJIT中调用C函数非常的方便,例如:

Lua
1
2
3
4
5
6
local ffi = require("ffi")
ffi.cdef [[
    int printf(const char *fmt, ...);
]]
-- 标准C库不需要ffi.load,可以直接使用
ffi.C.printf("Hello %s!", "world")

更加复杂的代码示例:

Lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
-- 调用zlib库的例子
local ffi = require("ffi")
ffi.cdef [[
    unsigned long compressBound(unsigned long sourceLen);
    int compress2(uint8_t *dest, unsigned long *destLen, const uint8_t *source, unsigned long sourceLen, int level);
    int uncompress(uint8_t *dest, unsigned long *destLen, const uint8_t *source, unsigned long sourceLen);
]]
-- 不同操作系统中库的名称不一样
-- 将C共享库绑定到名字空间zlib
local zlib = ffi.load(ffi.os == "Windows" and "zlib1" or "z")
 
-- 压缩缓冲区txt
local function compress(txt)
    -- 将缓冲区长度传递给zlib
    local n = zlib.compressBound(#txt)
    -- 创建缓冲区cdata。uint8_t[?]为变长数组,其实际长度为n
    local buf = ffi.new("uint8_t[?]", n)
    -- C语言中可以传递变量地址(指针),但是Lua中没有对应的东西
    -- 因此只能使用长度为1的数组代替之
    local buflen = ffi.new("unsigned long[1]", n)
    -- 执行压缩
    -- buflen在C函数中是一个指针,实际使用buf的长度,需要通过buflen传递回来
    local res = zlib.compress2(buf, buflen, txt, #txt, 9)
    assert(res == 0)
    -- 从指定的C缓冲区创建Lua字符串,长度为buflen[0]
    return ffi.string(buf, buflen[0])
end 
使用C数据结构

cdata用于保存任何C类型到Lua变量中。这种变量实际上指向一块原生的内存区域。除了赋值和相同性判断,LuaJIT没有为cdata预定义任何的操作。

你可以通过元表为cdata自定义一组操作,并调用这些操作:

Lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
local ffi = require("ffi")
ffi.cdef [[
    // 定义一个C结构
    typedef struct { double x, y; } point_t;
]]
 
-- 一个元表
local mt = {
    __add = function(a, b) return point(a.x + b.x, a.y + b.y) end,
    -- # 操作符支持
    __len = function(a) return math.sqrt(a.x * a.x + a.y * a.y) end,
    __index = {
        -- :area方法
        area = function(a) return a.x * a.x + a.y * a.y end,
    },
}
-- 为ct设置元表,返回设置后的ctype
-- ctype = ffi.metatype(ct, metatable)
local point = ffi.metatype("point_t", mt)
 
local a = point(3, 4)
print(a.x, a.y)                 -- 3    4
print(#a)                       -- 5
print(a:area())                 -- 25
local b = a + point(0.5, 8)  
print(#b)                       -- 12.5

注意:元表与 C 类型的关联是永久的,而且不允许被修改,__index 元方法也是 。

语法对应关系
C Lua
x = *p x = p[0]
*p = y p[0] = y
x = p[i] x = p[i]
p[i+1] = y p[i+1] = y
x = a[i] x = a[i]
a[i+1] = y a[i+1] = y
x = s.field x = s.field
s.field = y s.field = y
x = sp->field x = sp.field
sp->field = y s.field = y
y = p - i y = p - i
x = p1 - p2 x = p1 - p2
x = &a[i] x = a + i
包管理器

LuaRocks是一个包管理器,它可以创建、安装Lua模块,将其打包为自包含的包,这种包称为rocks。

安装
Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
wget https://luarocks.org/releases/luarocks-2.4.3.tar.gz
tar zxpf luarocks-2.4.3.tar.gz
cd luarocks-2.4.3
./configure --prefix=/home/alex/Lua/sdk/5.3.4
# 将LuaRock作为一个Rock安装
make bootstrap
 
# 查看环境变量,将其拷贝到~/.profile中
luarocks path
 
 
 
# 配合LuaJIT安装
export PATH=/home/alex/Lua/LuaJIT/2.0.5/bin:$PATH
./configure --prefix=/home/alex/Lua/LuaJIT/2.0.5 --lua-suffix=jit \
            --with-lua-include=/home/alex/Lua/LuaJIT/2.0.5/include/luajit-2.0
安装Rocks

示例:

Lua
1
luarocks install luasocket

 Rock会被安装到:/home/alex/Lua/sdk/5.3.4下。

← PhpStorm知识集锦
Express学习笔记 →

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">

Related Posts

  • OpenResty学习笔记

Recent Posts

  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
  • A Comprehensive Study of Kotlin for Java Developers
  • 背诵营笔记
  • 利用LangChain和语言模型交互
  • 享学营笔记
ABOUT ME

汪震 | Alex Wong

江苏淮安人,现居北京。目前供职于腾讯云,专注容器方向。

GitHub:gmemcc

Git:git.gmem.cc

Email:gmemjunk@gmem.cc@me.com

ABOUT GMEM

绿色记忆是我的个人网站,域名gmem.cc中G是Green的简写,MEM是Memory的简写,CC则是我的小天使彩彩名字的简写。

我在这里记录自己的工作与生活,同时和大家分享一些编程方面的知识。

GMEM HISTORY
v2.00:微风
v1.03:单车旅行
v1.02:夏日版
v1.01:未完成
v0.10:彩虹天堂
v0.01:阳光海岸
MIRROR INFO
Meta
  • Log in
  • Entries RSS
  • Comments RSS
  • WordPress.org
Recent Posts
  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
    In this blog post, I will walk ...
  • A Comprehensive Study of Kotlin for Java Developers
    Introduction Purpose of the Study Understanding the Mo ...
  • 背诵营笔记
    Day 1 Find Your Greatness 原文 Greatness. It’s just ...
  • 利用LangChain和语言模型交互
    LangChain是什么 从名字上可以看出来,LangChain可以用来构建自然语言处理能力的链条。它是一个库 ...
  • 享学营笔记
    Unit 1 At home Lesson 1 In the ...
  • K8S集群跨云迁移
    要将K8S集群从一个云服务商迁移到另外一个,需要解决以下问题: 各种K8S资源的迁移 工作负载所挂载的数 ...
  • Terraform快速参考
    简介 Terraform用于实现基础设施即代码(infrastructure as code)—— 通过代码( ...
  • 草缸2021
    经过四个多月的努力,我的小小荷兰景到达极致了状态。

  • 编写Kubernetes风格的APIServer
    背景 前段时间接到一个需求做一个工具,工具将在K8S中运行。需求很适合用控制器模式实现,很自然的就基于kube ...
  • 记录一次KeyDB缓慢的定位过程
    环境说明 运行环境 这个问题出现在一套搭建在虚拟机上的Kubernetes 1.18集群上。集群有三个节点: ...
  • eBPF学习笔记
    简介 BPF,即Berkeley Packet Filter,是一个古老的网络封包过滤机制。它允许从用户空间注 ...
  • IPVS模式下ClusterIP泄露宿主机端口的问题
    问题 在一个启用了IPVS模式kube-proxy的K8S集群中,运行着一个Docker Registry服务 ...
  • 念爷爷
      今天是爷爷的头七,十二月七日、阴历十月廿三中午,老人家与世长辞。   九月初,回家看望刚动完手术的爸爸,发

  • 6 杨梅坑

  • liuhuashan
    深圳人才公园的网红景点 —— 流花山

  • 1 2020年10月拈花湾

  • 内核缺陷触发的NodePort服务63秒延迟问题
    现象 我们有一个新创建的TKE 1.3.0集群,使用基于Galaxy + Flannel(VXLAN模式)的容 ...
  • Galaxy学习笔记
    简介 Galaxy是TKEStack的一个网络组件,支持为TKE集群提供Overlay/Underlay容器网 ...
TOPLINKS
  • Zitahli's blue 91 people like this
  • 梦中的婚礼 64 people like this
  • 汪静好 61 people like this
  • 那年我一岁 36 people like this
  • 为了爱 28 people like this
  • 小绿彩 26 people like this
  • 彩虹姐姐的笑脸 24 people like this
  • 杨梅坑 6 people like this
  • 亚龙湾之旅 1 people like this
  • 汪昌博 people like this
  • 2013年11月香山 10 people like this
  • 2013年7月秦皇岛 6 people like this
  • 2013年6月蓟县盘山 5 people like this
  • 2013年2月梅花山 2 people like this
  • 2013年淮阴自贡迎春灯会 3 people like this
  • 2012年镇江金山游 1 people like this
  • 2012年徽杭古道 9 people like this
  • 2011年清明节后扬州行 1 people like this
  • 2008年十一云龙公园 5 people like this
  • 2008年之秋忆 7 people like this
  • 老照片 13 people like this
  • 火一样的六月 16 people like this
  • 发黄的相片 3 people like this
  • Cesium学习笔记 90 people like this
  • IntelliJ IDEA知识集锦 59 people like this
  • Bazel学习笔记 38 people like this
  • 基于Kurento搭建WebRTC服务器 38 people like this
  • PhoneGap学习笔记 32 people like this
  • NaCl学习笔记 32 people like this
  • 使用Oracle Java Mission Control监控JVM运行状态 29 people like this
  • Ceph学习笔记 27 people like this
  • 基于Calico的CNI 27 people like this
Tag Cloud
ActiveMQ AspectJ CDT Ceph Chrome CNI Command Cordova Coroutine CXF Cygwin DNS Docker eBPF Eclipse ExtJS F7 FAQ Groovy Hibernate HTTP IntelliJ IO编程 IPVS JacksonJSON JMS JSON JVM K8S kernel LB libvirt Linux知识 Linux编程 LOG Maven MinGW Mock Monitoring Multimedia MVC MySQL netfs Netty Nginx NIO Node.js NoSQL Oracle PDT PHP Redis RPC Scheduler ServiceMesh SNMP Spring SSL svn Tomcat TSDB Ubuntu WebGL WebRTC WebService WebSocket wxWidgets XDebug XML XPath XRM ZooKeeper 亚龙湾 单元测试 学习笔记 实时处理 并发编程 彩姐 性能剖析 性能调优 文本处理 新特性 架构模式 系统编程 网络编程 视频监控 设计模式 远程调试 配置文件 齐塔莉
Recent Comments
  • qg on Istio中的透明代理问题
  • heao on 基于本地gRPC的Go插件系统
  • 黄豆豆 on Ginkgo学习笔记
  • cloud on OpenStack学习笔记
  • 5dragoncon on Cilium学习笔记
  • Archeb on 重温iptables
  • C/C++编程:WebSocketpp(Linux + Clion + boostAsio) – 源码巴士 on 基于C/C++的WebSocket库
  • jerbin on eBPF学习笔记
  • point on Istio中的透明代理问题
  • G on Istio中的透明代理问题
  • 绿色记忆:Go语言单元测试和仿冒 on Ginkgo学习笔记
  • point on Istio中的透明代理问题
  • 【Maven】maven插件开发实战 – IT汇 on Maven插件开发
  • chenlx on eBPF学习笔记
  • Alex on eBPF学习笔记
  • CFC4N on eBPF学习笔记
  • 李运田 on 念爷爷
  • yongman on 记录一次KeyDB缓慢的定位过程
  • Alex on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • haolipeng on 基于本地gRPC的Go插件系统
  • 吴杰 on 基于C/C++的WebSocket库
©2005-2025 Gmem.cc | Powered by WordPress | 京ICP备18007345号-2