主页
文章
分类
系列
标签
Lua
发布于: 2015-1-15   更新于: 2015-1-15   收录于: Language , Cheat sheet
文章字数: 4544   阅读时间: 10 分钟  

Lua 特性

  • 轻量级:源码2.5万行左右C代码, 方便嵌入进宿主语言(C/C++)
  • 可扩展:提供了易于使用的扩展接口和机制, 使用宿主语言提供的功能
  • 高效性:运行最快的脚本语言之一
  • 可移植:跨平台

Lua 入门

Lua 5.3参考手册
Lua 源码

Lua 语法

变量

作为动态类型语言,变量本身没有类型,赋值决定某一时刻变量的类型。
私有静态变量带local, 公有静态变量不带local。
数据类型:

  • nil 为空,无效值,在条件判断中表示false
  • boolean 包含两个值:false和true
  • number 表示双精度类型的实浮点数
  • string 字符串由一对双引号或单引号来表示
  • function 由 C 或 Lua 编写的函数
  • table Lua 中的表(table)其实是一个"关联数组"(associative arrays),数组的索引可以是数字、字符串或表类型
  • thread 协程
  • userdata 存储在变量中的C数据结构
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
print(type(signal))						--nil

signal = true				
print(type(signal))						--boolean

signal = 1454
print(type(signal))						--number

signal = "UnionTech"
print(type(signal))						--string			

signal = function() 
	print(type(signal))
end 	
print(type(signal))						--function

signal = {}								
print(type(signal))						--table

signal = coroutine.create(function()
	print(type(signal))
end)
print(type(signal))						--coroutine

流程控制

if…else

1
2
3
4
5
6
7
8
ty_signal = type(signal)
if ty_signal == "coroutine" then
    print("signal type is coroutine")
elseif ty_signal == "table" then
    print("signal type is table")
else
    print("signal type is other")
end

while

1
2
3
4
5
6
7
ut_companys = {"beijing company", "shanghai company", "nanjing company", "wuxi company", "guangzhou company", "yunfu company", "wuhan company", "chengdu company", "xian company"}
count = 0
while count <= #ut_companys 
do
    count = count + 1
    print("ut_companys[", count, "] is ", ut_companys[count])
end

for

1
2
3
for i=#ut_companys, 1, -2 do        --以2为步长反向遍历
    print("num: ", i, "company: ", ut_companys[i])
end

表作为 Lua 唯一自带的数据结构, 使用简单方便, 兼具数组和 Map 作为容器的功能,通过表可以很容易组成常见的数据结构, 如栈、队列、链表、集合,用 for 循环很容易迭代遍历表数据。

lua 的 table 充当了数组和映射表的双重功能,所以在实现时就考虑了这些,让 table 在做数组使用时尽量少效率惩罚。

lua 是这样做的。它把一个 table 分成数组段和 hash 段两个部分。数字 key 一般放在数组段中,没有初始化过的 key 值全部设置为 nil 。当数字 key 过于离散的时候,部分较大的数字 key 会被移到 hash 段中去。这个分割线是以数组段的利用率不低于 50% 为准。 0 和 负数做 key 时是肯定放在 hash 段中的。

string 和 number 都放在一起做 hash ,分别有各自的算法,但是 hash 的结果都在一个数值段中。hash 段采用闭散列方法,即,所有的值都存在于表中。如果 hash 发生碰撞,额外的数据记在空闲槽位里,而不额外分配空间存放。当整个个表放满后,hash 段会扩大,所有段内的数据将被重新 hash ,重新 hash 后,冲突将大大减少。

这种 table 的实现策略,首先保证的是查找效率。对于把 table 当数组使用时将和 C 数组一样高效。对于 hash 段的值,查找几乎就是计算 hash 值的过程(其中string 的 hash 值是事先计算好保存的),只有在碰撞的时候才会有少许的额外查找时间,而空间也不至于过于浪费。在 hash 表比较满时,插入较容易发生碰撞,这个时候,则需要在表中找到空的插槽。lua 在 table 的结构中记录了一个指针顺次从一头向另一头循序插入来解决空槽的检索。每个槽点在记录 next 指针保存被碰撞的 key 的关联性。

数组

下标从 1 开始

1
2
3
4
5
6
7
8
for i, c in ipairs(ut_companys) do
	print(string.format("1 UnionTech company: %d		%s", i, c))
end

table.sort(ut_companys)
for i=#ut_companys, 1, -1 do
	print(string.format("2 UnionTech company: %d		%s", i, ut_companys[i]))
end

hash map

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
ut_cptypes = {}

ut_cptypes["adapter"] = {"beijing company", "wuhan company", "guangzhou company"}
ut_cptypes["developer"] = {"beijing company", "wuhan company", "nanjing company", "chengdu company", "xian company", "guangzhou company"}
ut_cptypes["general"] = {"beijing company"}

for ty, cps in pairs(ut_cptypes) do
	for i, cp in ipairs(cps) do
		print(string.format("3 UnionTech companys: type:%s  identifier:%s	company:%s", ty, i, cp))
	end
end

函数

在 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
ut_types = {"adapter", "developer", "general"}
function company_types(cp, cptypes)
	local adpt, dvlp, genl = nil, nil, nil
	for i, ty in ipairs(ut_types) do
		for _, _cp in ipairs(cptypes[ty]) do
			if _cp == cp then
				if i == 1 then
					adpt = true
				elseif i == 2 then
					dvlp = true
				elseif i == 3 then
					genl = true
				end
				break
			end
		end
	end
	return adpt, dvlp, genl
end

cp = "wuhan company"
types = {company_types(cp, ut_cptypes)}

for i, ty in ipairs(types) do
	if ty then
		print(string.format("%s  is %s", cp, ut_types[i]))
	end
end 

可变参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
function printf(str, ...)
	print(string.format(str, ...))
end

function add_companys(...)
	local newcps = {...}
	local num = #newcps
	for _, cp in ipairs(newcps) do
		table.insert(ut_companys, cp)
	end
	return ut_companys, num
end

_, _ = add_companys("changsha company", "zhengzhou company", "hefei company")
for i=1, #ut_companys do
	--print(string.format("4 UnionTech company: %d		%s", i, ut_companys[i]))
	printf("4 UnionTech company: %d		%s", i, ut_companys[i])
end

闭包

 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
function all_companys(cps)
	local companys, n = {}, 0
	for _, v in ipairs(cps) do
		table.insert(companys, v)
	end
	return function()
		n = n + 1
		if n > #companys then
			return ""
		else
			return companys[n]
		end
	end
end

get_company = all_companys(ut_companys)
while true
do
	cp = get_company()
	if cp == "" then
		break
	else	
		printf("get company: %s", cp)
	end
end

协程(coroutine)

Lua ,也叫 协同式多线程。与线程比较类似:拥有独立的堆栈,独立的局部变量,独立的指令指针,同时又与其它协同程序共享全局变量和其它大部分东西。

与多线程系统中的线程的区别在于, 协程仅在显式调用一个让出(yield)函数时才挂起当前的执行。协程可以视为程序的执行单位,和线程不同,线程是抢占式的(因为是在单线程内),多条线程是并行时运行的,而协程则不是,协程是协同式的,比如有三个协程按顺序先后创建 coA、coB、coC,那么在没有任意一条协程主动挂起(yield)的情况下,执行顺序则是 coA 执行完,在执行 coB,然后再执行 coC。也就是说,除非有协程主动要求挂起,否则必须等当前协程执行完,再去执行下面一个创建的协程。比如说,coA 执行完,接着就是执行 coB,此时 coB 挂起,那么直接执行 coC,coC 执行完以后,如果 coB 被唤醒了,则接着上次开始阻塞的部分继续执行余下的逻辑。

调用函数 coroutine.create 可创建一个协程。 其唯一的参数是该协程的主函数。 create 函数只负责新建一个协程并返回其句柄 (一个 thread 类型的对象); 而不会启动该协程。

调用 coroutine.resume 函数执行一个协程。 第一次调用 coroutine.resume 时,第一个参数应传入 coroutine.create 返回的线程对象,然后协程从其主函数的第一行开始执行。 传递给 coroutine.resume 的其他参数将作为协程主函数的参数传入。 协程启动之后,将一直运行到它终止或 让出。

协程的运行可能被两种方式终止: 正常途径是主函数返回 (显式返回或运行完最后一条指令); 非正常途径是发生了一个未被捕获的错误。 对于正常结束, coroutine.resume 将返回 true, 并接上协程主函数的返回值。 当错误发生时, coroutine.resume 将返回 false 与错误消息。

通过调用 coroutine.yield 使协程暂停执行,让出执行权。 协程让出时,对应的最近 coroutine.resume 函数会立刻返回,即使该让出操作发生在内嵌函数调用中 (即不在主函数,但在主函数直接或间接调用的函数内部)。 在协程让出的情况下, coroutine.resume 也会返回 true, 并加上传给 coroutine.yield 的参数。 当下次重启同一个协程时, 协程会接着从让出点继续执行。 此时,此前让出点处对 coroutine.yield 的调用 会返回,返回值为传给 coroutine.resume 的第一个参数之外的其他参数。

与 coroutine.create 类似, coroutine.wrap 函数也会创建一个协程。 不同之处在于,它不返回协程本身,而是返回一个函数。 调用这个函数将启动该协程。 传递给该函数的任何参数均当作 coroutine.resume 的额外参数。 coroutine.wrap 返回 coroutine.resume 的所有返回值,除了第一个返回值(布尔型的错误码)。 和 coroutine.resume 不同, coroutine.wrap 不会捕获错误; 而是将任何错误都传播给调用者。

 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
-- 下面的代码展示了一个协程工作的范例:
function foo (a)
    print("foo", a)
    return coroutine.yield(2*a)
end
co = coroutine.create(function (a,b)
    print("co-body", a, b)
    local r = foo(a+1)
    print("co-body", r)
    local r, s = coroutine.yield(a+b, a-b)
    print("co-body", r, s)
    return b, "end"
end)

print("main", coroutine.resume(co, 1, 10))
print("main", coroutine.resume(co, "r"))
print("main", coroutine.resume(co, "x", "y"))
print("main", coroutine.resume(co, "x", "y"))

-- 当你运行它,将产生下列输出:
co-body 1       10
foo     2
main    true    4
co-body r
main    true    11      -9
co-body x       y
main    true    10      end
main    false   cannot resume dead coroutine
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
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将主协程数据传入次协程, yield将次协程中数据传回主协程

元表(Metatable)

__index

当你通过键来访问 table 的时候,如果这个键没有值,那么 Lua 就会寻找该 table 的 metatable(假定有 metatable)中的 __index 键。如果 __index 包含一个表格,Lua 会在表格中查找相应的键(元素是方法也是如此)。

1
2
3
4
5
6
> other = { foo = 3 }
> t = setmetatable({}, { __index = other })
> t.foo
3
> t.bar
nil

Lua 查找一个表元素时的规则,其实就是如下 3 个步骤:

  1. 在表中查找,如果找到,返回该元素,找不到则继续
  2. 判断该表是否有元表,如果没有元表,返回 nil,有元表则继续
  3. 判断元表有没有 __index 方法,如果 __index 方法为 nil,则返回 nil;如果 __index 方法是一个表,则重复 1、2、3;如果 __index 方法是一个函数,则返回该函数的返回值。

__newindex

__newindex 元方法用来对表更新,__index 则用来对表访问 。当你给表的一个缺少的索引赋值,解释器就会查找 __newindex 元方法,如果存在则调用这个函数而不进行赋值操作。

__call

__call 元方法在 Lua 调用一个值时调用。

本质上来说就是存放元方法的表结构, 通过元表实现对表中数据和行为的改变。
Lua 查找一个表元素时的规则,其实就是如下 3 个步骤:

  1. 在表中查找,如果找到,返回该元素,找不到则继续
  2. 判断该表是否有元表,如果没有元表,返回 nil,有元表则继续。
  3. 判断元表有没有 __index 方法
    • 如果 __index 方法为 nil,则返回 nil
    • 如果 __index 方法是一个表,则重复 1、2、3
    • 如果 __index 方法是一个函数,则返回该函数的返回值
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
father = {
	colourofskin = "yellow",
	weight = 70,
	work = "programming",
	otherwork = function() 
		print "do housework"
	end
}
father.__index = father

son = {
	weight = 50,
	like = "basketball"
}

setmetatable(son, father)
printf("weight:%d like:%s work:%s colourofskin:%s ", son.weight, son.like, son.work, son.colourofskin)
son.otherwork()

面向对象

因为 lua 本身不是面向对象的语言,在lua中,通过 table 和 function 来模拟一个对象,用 metatable 来模拟面向对象中的继承,但是在使用的时候需要考虑 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
--父类
rect = {
    area = 0,
    length = 0, 
    width = 0,
}

function rect:getArea()
    if self.area == 0 then
        self.area = self.length * self.width
    end
    
    return self.area
end

function rect:getLength()
    return self.length
end

function rect:new(leng, wid)
    self.length = leng
    self.width = wid
    return self    
end

--子类
cuboid = {
    volume = 0,
    height = 0,
}

function cuboid:getVolume()
    if self.volume == 0 then
        self.volume = self.height * self:getArea()
    end
    return self.volume
end

function cuboid:new(_rect, _height)
    setmetatable(self, _rect)
    _rect.__index = _rect
    self.height = _height
    return self
end

rect1 = rect:new(5, 10)
print("rect1 rectangle:", rect1:getArea())

cuboid1 = cuboid:new(rect1, 2)
print("cuboid1 volume: ", cuboid1:getVolume())
print("cuboid1 rectangle: ", cuboid1:getArea())             --子类调用父类方法getArea
print("cuboid1 length function: ", cuboid1:getLength())     --子类调用父类方法getLength
print("cuboid1 length variable: ", cuboid1.length)          --子类使用父类变量length

--重写子类接口getArea, lua中没有重载
function cuboid:getArea()                                   
    return 2 * (self.height * self.length + self.height * self.width + self.length * self.width)
end

cuboid2 = cuboid:new(rect1, 2)
print("cuboid2 function: getArea: ", cuboid2:getArea())                     --调用子类重写的方法getArea
print("cuboid2 base function: getArea: ", getmetatable(cuboid2):getArea())  --显示调用父类方法getArea

模块与 C 包

模块类似封装库,有利于代码复用,降低耦合,提供被调用的 API
文件名为 module.lua, 定义一个名为 module 的模块

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
module = {}
module.constant = "这是一个常量"
function module.func1()
	io.write("这是一个公有函数!\n")
end

local function func2()
	print("这是一个私有函数!")
end

function module.func3()
	func2()
end

return module

在其他模块中调用 module 模块

1
2
	local m = require("module")
	print(m.constant)

与 Lua 中写包不同,C 包在使用以前必须首先加载并连接,在大多数系统中最容易的实现方式是通过动态连接库机制
Lua 在一个叫 loadlib 的函数内提供了所有的动态连接的功能

Lua 标准库

标准库中接口可直接使用不需要 require
常用标准库:

  • math 数学计算
  • table 表结构数据处理
  • string 字符串处理
  • os 系统库函数
  • io 文件读写
  • coroutine 协程库
  • debug 调式器

Lua 虚拟机

脚本语言没有像编译型语言那样直接编译为机器能识别的机器代码,这意味着解释性脚本语言与编译型语言的区别
由于每个脚本语言都有自己的一套字节码,与具体的硬件平台无关,所以无需修改脚本代码,就能运行在各个平台上
硬件、软件平台的差异都由语言自身的虚拟机解决。由于脚本语言的字节码需要由虚拟机执行,而不像机器代码那样能够直接执行,所以运行速度比编译型语言差不少
有了虚拟机这个中间层,同样的代码可以不经修改就运行在不同的操作系统、硬件平台上。Java、Python 都是基于虚拟机的编程语言,Lua 同样也是这样

Andy
Welcome to andy blog
目录
相关文章
Go
安装 前往 官网 下载 go1.19.4.linux-amd64.tar.gz 1 2 3 tar -C /usr/local/ -xzf go1.19.4.linux-amd64.tar.gz export PATH=$PATH:/usr/local/go/bin go version 看到版本号代表 go 安装成功 编译器命令 1 2 3 4 5 6 7 8 9 10 11 12
2019-12-30
C#
数据类型 类型 大小 举例 String 2 bytes/char s = “reference” bool 1 byte b = true char 2 bytes ch = ‘a’ byte 1 byte b = 0x78 short 2 bytes val = 70 int 4 bytes val = 700 long 8 bytes val
2019-7-13
Python
常规 Python 对大小写敏感 Python 的索引从 0 开始 Python 使用空白符(制表符或空格)来缩进代码,而不是使用花括号 帮助 获取主
2018-10-6
Batch
什么是批处理 批处理(Batch),也称为批处理脚本,批处理就是对某对象进行批量的处理 批处理文件的扩展
2017-11-5
Bash
常用快捷键 默认使用 Emacs 键位 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 CTRL+A # 移动到行
2017-4-21
JavaScript
基础知识 类型 基本类型 最新的 ECMAScript 标准定义了 8 种数据类型,分别是 string number bigint boolean null undefined symbol (ECMAScript 2016新增) 所有基本类型
2016-2-26
MySQL
MySql 安装 Mac Linux 基本操作 导入导出 1 2 mysqldump -h hostname -u username -p database_name -P port > file.sql # Export database mysql -u username -p database_name < file.sql # Import database 连接 1 2 3 show status where `variable_name` = 'Threads_connected';
2015-8-3
Redis
启动 Redis 1 2 3 4 redis-server /path/redis.conf # 指定配置文件启动 redis redis-cli # 开启 redis 客户端 systemctl restart redis.service # 重启 redis systemctl status redis # 检查 redis 运行状态 字符串 1 2
2019-12-24
Nginx
Nginx 常用命令 官方文档 1 2 3 4 5 sudo nginx -t # 检测配置文件是否有错误 sudo systemctl status nginx # nginx 当前的运行状态 sudo systemctl reload nginx # 重新加
2018-2-12
Linux
bash 目录操作 文件操作 进程管理 管道符 竖线 | ,在 linux 中是作为管道符的,将 | 前面命令的输出作为 | 后面的输入 1 grep
2018-1-12