本文最后更新于 43 天前,其中的信息可能已经有所发展或是发生改变。
参考文档
- 《Lua程序设计(第二版)》 –电子工业出版社 [巴西]Roberto Ierusalimschy 著 周惟迪 译
- 菜鸟教程(RUNOOB.COM)
1. 类型与值
Lua是一种动态类型语言,在语言中没有类型定义的语法,任何变量都可以包含任何类型的值;Lua中有8中基础类型,可以使用函数type返回一个值的类型名称
nil空
print(type(nil)) --->nil
-->如果一个变量没有初始化,它的类型为`nil`
local a
print(type(a)) --->nil
boolean布尔
print(type(true)) --->boolean
number数字
print(type(10)) --->number
string字符串
print(type("Hello world")) --->string
userdata自定义类型function函数
print(type(print)) --->function
thread线程table表
local a ={}
print(type(a)) --->table
1.1 nil(空)
nil类型是一种特殊的类型,仅有一个值nil;其主要功能就是用于区分其他任何值,一个全局变量在第一次赋值前的默认值就是nil,将nil赋予一个全局变量等同于删除它,Lua将nil用于表述一种无效值的情况
1.2 Boolean(布尔)
boolean类型有两个值可以选择false和true;Lua将false和nil视为“假”,其余之外的其他值视为“真”
1.3 number(数字)
Lua中对于整数和浮点数不进行区分,全部使用number类型表示
1.4 string(字符串)
Lua中的字符串需要以一对匹配的单引号或者双引号来界定- 可以在字符串中使用类似于
C语言中的转义字符 - 使用字母进行转义,如
\n 换行、\r 回车 - 使用
ASCII码来指定字符串中的字符,如\97 a、\10 \n - 如果不想使用转义序列,可以使用一对匹配的双方括号界定一个字符串
Lua不会解释其中的转义字符,这种形式的字符串可以延伸多行
a = "\97\97"
b = [[\97\97]]
print(a) --->aa
print(b) --->\97\97
- 字符串连接操作符
..,当直接在一个数字后面输入它时必须要用一个空格来分隔它们,不然Lua会将第一个点理解为小数点,或者直接报错
a = "abc"
b = 123
rint(a..123) --->abc123
print(123 ..456) --->123456
- 在字符串前放置操作符
#可以获得该字符串的长度
a = "abc"
print(#a) --->3
1.5 table(表)
Lua中的表table其实是一个”关联数组”associative arrays,数组的索引可以是数字、字符串或表类型。在Lua里,table的创建是通过”构造表达式”来完成,最简单构造表达式是{},用来创建一个空表。Lua table是不固定大小的,可以根据自己的需要进行扩充Lua table使用关联型数组,你可以用任意类型的值来作数组的索引,但这个值不能是nilLua也是通过table来解决模块module、包package和对象Object的管理
1.5.1 table的特性
- 一个
table中包含许多成员,每个条目包含一个索引和一个元素,索引和元素为一组键值对 table的索引初始值默认为1,且索引不可以使用负数table的长度可以动态增长- 不同的变量可以持有相同的
table,一个持有table的变量与table自身之间没有固定的关联性 - 当没有任何对
table的引用时,Lua的垃圾回收器会删除该table并复用它的内存
1.5.2 table初始化与赋值
在定义时初始化
- 最简单的构造式就是一个空构造式即
{},用于创建一个空table
table_tese = {}
- 也可以使用构造式在初始化
table时进行赋值
local local_A = "local_A"
local num_3 = 3
table_test1 = {
--风格1.初始化时使用“=”赋值--
style_a_1 = "a",
style_a_2 = 1,
style_a_3 = "2",
style_a_4 = local_A,
--风格2.使用将索引值防在方括号中--
[1] = 1,
[2] = "a",
[num_3] = local_A,
--风格3.Lua提供了一种更加通用的风格,这种格式允许使用负数或运算符作为索引--
["-1"] = "2",
["+"] = "3"
}
--风格4.仅填充元素值,索引值由1开始自动依次递增--
table_test2 = { "a", "b", "c", "d" }
--风格5.仅填充元素值,并指定初始索引值,索引值由初始值开始自动依次递增--
table_test3 = { [0] = "a", "b", "c", "d" }
- 在创建后修改
table中的某些字段
--风格1. 使用方括号--
table_test["a"] = 3 --->将索引a对应的值更改为3
table_test[1] = "c" --->将索引1对应的值更改为c
table_test[2] = "d" --->新增索引2并将元素的值赋值为c
--风格2.使用在table变量后使用"."连接索引值,但索引值不可以为数组--
table_test.a = 3
--删除元素,将索引对应的值置为nil--
table_test.a = nil
table_test[1] = nil
1.5.3 table遍历
pairs可以遍历所有成员Luajit中使用for key, value in pairs(table) do这样的遍历顺序并非是代码中table中的排列顺序,而是根据table中key的hash值的排列顺序来遍历,key的hash是以时间戳相关的随机值作为种子生成的,导致最终打印的值的顺序是随机值ipairs只遍历1,2,数值型下标,下一个3不连续就中断了- 由于
#tbtest获取的值为4,故下标3的值为nil
table_1 = {
a = "a",
[1] = "1",
b = "b",
c = "c",
[2] = "2",
[4] = "4"
}
for key, value in pairs(table_1) do
print(tostring(key)..":"..tostring(value))
end
结果:
1:1
2:2
4:4
a:a
b:b
c:c
for key, value in ipairs(table_1) do
print(tostring(key)..":"..tostring(value))
end
结果:
1:1
2:2
for i=1, #table_1 do
print(tostring(i)..":"..tostring(table_1[i]))
end
结果:
1:1
2:2
3:nil
4:4
local table_1 ={
{name = "a", value = "1"},
{name = "c", value = "3"},
{name = "d", value = "4"},
{name = "b", value = "2"},
}
i = 0
for i, k in pairs(table_1) do
for key, value in pairs(k) do
print(tostring(key)..":"..tostring(value))
end
print("\n")
end
结果:
name:a
value:1
name:c
value:3
name:d
value:4
name:b
value:2
2. 语句
Lua支持的常规语句基本上和C语言中的差不多,这些语句包括赋值、控制结构、过程调用等。另外Lua还支持一些不太常见的语句,比如多重赋值和局部变量声明
2.1 多重赋值语句
Lua允许多重赋值,也就是一次将多个值赋给多个变量,每个值和变量之间用逗号分隔- 如果等号两侧的值数量不同的话,多余的变量会被赋值为
nil,如果值的个数更多的话,多余的值会被丢弃
x, y = 10, 10*10
print(x.. ":" ..y) --->10:100
2.2 局部变量和块
- 相对于全局变量,
Lua还提供了局部变量,通过local语句来创建局部变量 - 局部变量的作用域仅限于声明它们的那个块
- 一个块是一个控制结构的执行体、或者是一个函数的执行体或是一个程序块
- 局部变量通常会随着其作用域的结束而消失,相对应的内存资源由垃圾回收机制进行回收
3.控制结构
- 控制结构中的条件表达式可以是任何值,
Lua将所有不是false和nil的值视为真
3.1 if then else
- 判断条件放置在
if和then之间,函数体在then之后,整个判断语句程序块以end结束 - 多层嵌套结构使用
else if语句
if a > b then
print("a > b")
elseif a == b then
print("a = b")
else
print("a < b")
end
3.2 while循环
- 先判断
while的条件是否为真,如果条件为假那么结束循环,不然执行循环体并重复这一过程
local i = 1
while a[i] do
print(a[i])
i = i + 1
end
3.3 repeat循环
- 可视为
C语言中的do while语句 - 一条
repeat-until语句重复执行其循环体直到条件为真时结束。检测条件是在循环体之后做的,循环体会至少执行一次
--打印输入的第一行不为空的内容--
repeat
line = io.read()
until line ~= ""
print(line)
3.4 for循环
for语句有两种形式:数字型for和泛型for
3.4.1 数字型for循环
var从exp1变化到exp2,每次变化都以exp3作为步长递增var,并执行一次循环体。第三个表达式exp3是可选参数,如果不指定的话,Lua会将步长默认为1
for var = exp1, exp2, exp3 do
<执行体>
end
--例如,依次打印从100到1--
for i = 100, 1, -1 do
print(i)
end
- 如果不想给循环设置上限的话,可以使用常量
math.huge
--无限循环打印--
for i = 1, math.huge do
print(i)
end
- 在
for循环中可以使用break语句退出循环 for循环的三个表达式是在循环开始前一次性求值的- 控制变量会被自动的声明为
for循环语句的局部变量,并且仅在循环体内可见,因此控制变量在循环结束后就不存在了
3.4.2 泛型for循环
- 泛型
for循环通过一个迭代器函数来遍历所有值
for key, value in pairs(table_1) do
print(tostring(key)..":"..tostring(value))
end
3.4.3 迭代器
- 迭代器是一种可以遍历集合中所有元素的机制,
lua中迭代器使用函数表示,每调用函数一次,返回集合中的下一个元素 - 迭代器的使用方法是定义一个工厂,然后利用这个工厂生产闭包,然后调用这个闭包来获取集合中的元素。这种方式的缺点就是每次都得创建一个新的闭包,有一定的开销
- 使用迭代器的方式也很简单,直接调用迭代器就相当于调用闭包函数,迭代器会返回元素集合的下一个元素,当返回
nil时就代表元素遍历完了
function value(t)
local i = 0
return function()
i = i + 1
return t[i]
end
end
t = {1, 2, 3, 4, 5}
-- 创建一个迭代器
iter = value(t)
-- 遍历迭代器
while true do
local ele = iter()
if ele == nil then
break
end
print(ele)
end
- 无状态迭代器
- 无状态迭代器指的是在循环过程中,不保留任何状态的迭代器,这样我们就可以避免创建闭包所花费的代价。典型的无状态迭代器就是:
ipairs - 泛型
for循环的设计本身就包含迭代器,而且是一种无状态的迭代器,即不需要创建闭包 - 泛型
for循环内部保存了迭代器状态,包括迭代器函数、控制变量和恒定状态- 迭代器函数:迭代器工厂产生的匿名函数,不是闭包控制变量:
for循环使用这个变量控制当前遍历的位置及何时结束遍历恒定状态:迭代器遍历的目标,一般是指
- 迭代器函数:迭代器工厂产生的匿名函数,不是闭包控制变量:
iter = function(t, i)
i = i + 1
local v = t[i]
if v then
return i, v
end
end
ipairs = function(t)
return iter, t, 0
end
for i, v in ipairs(t) do
print(i, v)
end
- 多状态迭代器
- 泛型
for循环可以实现只有一个恒定状态的迭代器,如果有多个状态需要保存就不行了。如果要保存多个状态,可以有下面两种方式- 使用闭包把多个状态封装在一个
table里面,然后使用泛型for循环
- 使用闭包把多个状态封装在一个
local array = {"Google", "Runoob"}
function elementIterator (collection)
local index = 0
local count = #collection
-- 闭包函数
return function ()
index = index + 1
if index <= count
then
-- 返回迭代器的当前元素
return collection[index]
end
end
end
for element in elementIterator(array)
do
print(element)
end
4. 函数
- 参数调用,需要将所有参数放到一对圆括号中,即使调用函数时没有参数也必须写出一对圆括号
- 若一个函数若只有一个参数,并且此参数是一个字面字符串或
table构造式,那么圆括号便是可有可无的
print "Hello Morld" --->Hello Morld
print("Hello World") --->Hello Morld
print(type{}) --->table
print(type({})) --->table
Lua为面向对象式的调用也提供了一种特殊的语法——冒号操作符。表达式o.foo(o, x)的另一种写法是o:foo(x),冒号操作符使调用o.foo时将o隐含地作为函数的第一个参数。- 关于实参和形参,
Lua会自动调整实参的数量,以匹配参数表的要求。这项调整与多重赋值很相似,即“若实参多余形参,则舍弃多余的实参;若实参不足,则多余的形参初始化为nil
4.1 多重返回值
Lua具有一项非常与众不同的特征,允许函数返回多个结果,只需在return关键字后列出所有的返回值即可- 如果一个函数没有返回值或者没有返回足够多的返回值,那么
Lua会用nil来补充缺失的值 table构造式可以完整地接收一个函数调用的所有结果,即不会有任何数量方面的调整:
4.2 变长参数
Lua参数传递时可以将所有参数全部放在一个table中,然后将这个table作为唯一参数传递;在函数内部,对必要参数进行检查,这样也可以实现变长参数的效果Lua中的函数可以接受不同数量的实参- 参数表中的
3个点...表示该函数可接受不同数量的实参,一个函数要访问它的变长参数时,仍需用到3个点...但不同的是,此时这3个点是作为一个表达式来使用的
function foo(...)
local a,b,c = ...
end
function foo(...)
return ...
end
- 具有变长参数的函数同样也可以拥有任意数量的固定参数,但固定参数必须放在变长参数之前。
Lua会将前面的实参赋予固定参数,而将余下的实参(如果有的话)视为变长参数
function foo(fmt, ...)
return string.format(fmt, ...)
end
local a, b, c = foo("a") --->a,nil,nil
local a, b, c = foo("%d, %d", 4, 5) --->4,5,nil,nil
- 通常一个函数在遍历其变长参数时只需使用表达式
...,这就像访问一个table一样,访问所有的变长参数
--select函数用于访问可变数量的参数列表中的参数
for i = 1, select('#', ...) do --遍历所有变长参数
local arg = select(i, ...) --得到第i个参数
print(arg)
end
5. 深入函数
- 在
Lua中,函数是一种“第一类值”,它们具有特定的词法域,第一类值。这表示在Lua中函数与其他传统类型的值(例如数字和字符串)具有相同的权利。函数可以存储到变量中(无论全局变量还是局部变量)或table中,可以作为实参传递给其他函数,还可以作为其他函数的返回值。
local network ={
{name = "a", IP = "210.26.30.34"},
{name = "c", IP = "210.26.30.23"},
{name = "d", IP = "210.26.23.12"},
{name = "b", IP = "210.26.23.20"},
}
--如果想以name字段、按反向的字符顺序来对这个table排序的话,只需这么写:
table.sort(network, function (a, b) return(a.name > b.name) end)
Lua中函数可以作为一个变量被其他变量持有
5.1 闭合函数(闭包)
- 若将一个函数写在另一个函数内,那么这个位于内部的函数便可以访问外部函数中的局部变量,这项特征称之为词法域
Lua中一个函数可以嵌套在另一个函数中,内部函数可以访问外部函数中的变量- 函数作为表达式时不能命名
function foo_1(x)
x = x + 1
return function ()
print(x)
return x
end
end
local foo_3 = foo_1(10)
foo_3() --->11
- 外部函数中的变量生命周期与普通函数相同,在外部函数调用结束后变量释放
- 内部函数中的变量生命周期与外部接收闭包的变量相同
- 每次形成闭包后内部函数中的变量会单独申请一块内存
5.2 尾调用消除
- 当一个函数调用是另一个函数的最后一个动作时,该调用算是一条尾调用
function f (x) return g(x) end
- 当外部函数调用内部函数后就没有其他事情可做,因此程序也不需要保存任何关于该函数的栈信息,及尾调用不会消耗栈空间