Lua学习笔记(v5.3)
[TOC]
Lua是一门扩展式、动态类型的程序设计语言,它没有
main
程序的概念只能嵌入一个宿主程序中工作,特点是轻量、可扩展。
数据类型
-
nil类型,值为
nil
。 -
boolean类型,值为
true
和false
(nil 和 ***false***都会导致条件判断为假,其他任何值都为真,包括0和空字符串)。 -
number类型,值为
整数
和符点数
。 -
string类型,值为一个不可变的字节序列。
-
function类型,由 C 或 Lua 编写的函数。
形式一: function 函数名 ...函数体... end 形式二: 函数名 = function ...函数体... end
函数可以通过三点
...
来接收可变参数:function study(a,b,...) print(a,b) for i,v in ipairs{...} do print('可变参数',v) end end study(1,2,'study',3,4,5)
同样,Lua函数也可有多个返回值用逗号
,
分割,也可在函数定义前使local
关键字定义局部函数。 -
userdata类型,表示任意存储在变量中的 C 数据结构(通常是 struct 和 指针)。
-
thread类型,表示了一个独立的执行序列,被用于实现协程。
-
table类型,表示一个关联数组,除了
nil
和NaN
(Not a Number 是一个特殊的数字,它用于表示未定义或表示不了的运算结果,比如 0/0。) 之外的所有 Lua 值 都可以做索引。除了[ ]
的方方式外,Lua也提供了.
操作符的方式访问表元素的语法糖。表中的值也可以是任意类型,即使是一个函数。-- 表索引遵循直接比较规则原则,即1.0==1为true,所以 a[1.0]=a[1] > a={} -- 初始化表 > a[1.0] = 4 > a[1] 4
table其实就是一个Key Value的数据结构,它的元素形式可有多种,如:
a = { [f(1)] = g; "x", "y"; x = 1, f(x), [30] = 23; 45 } 其形式等价于: a = { [f(1)] = g; [1] = "x",[2] = "y"; ["x"] = 1, [3] = f(x), [30] = 23; [4]=45 }
由上可以发现,Lua的索引下标是从
1
开始的,并且表中没有键的值会默认从下标1开始作为键。
语法
注释
– 单行注释
–[[
多行注释
]]
变量
Lua中的标识符,是由非数字开头的任意字母下划线和数字构成的非保留关键字的字符串,且大小写敏感。作为一个约定,程序应避免创建以下划线加一个或多个大写字母构成的名字 (例如 _VERSION)。
字符串可以用单引号
和双引号
括起,并且支持C类型的转义:’\a’ (响铃), ‘\b’ (退格), ‘\f’ (换页), ‘\n’ (换行), ‘\r’ (回车), ‘\t’ (横项制表), ‘\v’ (纵向制表), ‘\’ (反斜杠), ‘"’ (双引号), 以及 ‘’’ (单引号)
多行字符串通过[[ ]]
来定义,如下几种定义完全相同:
a = 'alo\n123"'
a = "alo\n123\""
a = '\97lo\10\04923"' -- \10 为换行编码
a = [[alo
123"]] -- 采用多行定义语法
a = [==[
alo
123"]==] -- 注意:开括号( [[ )后的换行符会被忽略
-
全局变量
所有没有显示声明为局部变量的变量全部为全局变量。
-
局部变量
变量前加
local
关键字为局部变量。 -
table 中的域
没有赋值的变量,默认值均为
nil
表达式
-
数学操作符
+
加法
-
减法
*
乘法
/
浮点除法
//
向下取整除法
%
取模
^
乘方
-
取负乘方和浮点除法总是将整数转换为浮点数。
-
位操作符
&
按位与
|
按位或
~
按位异或
>>
右移
<<
左移
~
按位非所有的位操作都将操作数先转换为整数 , 然后按位操作,其结果是一个整数。
对于右移和左移,均用零来填补空位。 移动的位数若为负,则向反方向位移; 若移动的位数的绝对值大于等于 整数本身的位数,其结果为零 (所有位都被移出)。 -
比较操作符
==
等于
~=
不等于
<
小于
>
大于
<=
小于等于
>=
大于等于 -
逻辑操作符
and
、or
、not
所有的逻辑操作符把 false 和 nil 都作为假, 而其它的一切都当作真。 -
字符串连接操作符
字符串的连接操作符写作两个点
..
。如果两个操作元素都是字符串或是数字,连接操作符将会把其转换成字符串,否则会调用元方法__concat()
。 -
取长度操作符
取长度操作符写作一元前置符
#
local t = { 1,2,3,4,5 } print(#t) 5
语句
-
代码块
Lua可以采用
;
分割语句,或开始一个代码块,或者连续使用两个分号表示一个空语句
。函数调用和赋值语句都能以小括号开头,这可能让Lua语法产生歧义:
a = b + c (print or io.write)('done')
从语法上说,可能有两种解释方式:
a = b + c(print or io.write)('done') 或 a = b + c; (print or io.write)('done')
解析器总是用第一种结构来解析, 它会将括号看成函数调用的参数传递开始处。 为了避免这种二义性, 在一条语句以小括号开头时,建议在前面放一个分号。
;(print or io.write)('done')
一个代码块可以通过
do ... end
显示的被定界为单条语句,这种做法通常是为了控制内部变量声明的作用域,或是在一个语句块中间插入return
。Lua把代码块当成一个拥有不定参数的匿名函数,因此代码块内可以定义局部变量,它可以接收参数,返回若干值。
-
赋值
Lua允许同时对多个变量赋值,等号左边放一个变量列表,右边放一个值列表,两边列表元素用,
隔开,如果
值列表的数量多于变量列表,多余值将剔除;反之,则多余变量将被赋值为nil
。
> i = 3
> i,a[i],c = i+1,20
> print(c)
nil
if...else
语句
if 表达式 then
...代码块...
elseif 表达式 then
...代码块...
else
...代码块...
end
while
循环
while 表达式 do
...代码块...
end
repeat
循环
repeat
...代码块...
util 表达式
for
循环
for 变量=表达式1, 表达式2 [,表达式3] do
...代码块...
end
for语句将循环变量。 从表达式1
值开始起,直到表达式2
的值为止, 其步长为表达式3
,默认步长为1
。
for a=1,10,2 do
print(a)
end
for语句还有一种迭代方式for...in
,每次迭代,迭代器函数都会被调用以产生一个新的值, 当这个值为 nil 时,循环停止。
for 变量 in 列表表达式 do
...代码块...
end
循环语句可以通过
break
、return
、goto
来退出。只要goto
没有进入一个新的局部变量的作用域,它可以跳转到任意可见标签
(::标签名::
)处。
元表及元方法
Lua中的每个值都可以有一个元表(metatable)
,这个表就是一个普通的表(table)
,它用于定义在特定操作下的行为。当想要改变一个值在特定操作下的行为时,可以在它的元表中设置对应键(事件)的元方法(metamethod)
。
事件 | 描述 |
---|---|
__add | (+)加操作,如果任何不是数字的值(包括不能转换为数字的字符串)做加法, Lua 就会尝试调用元方法。 |
__sub | (-)减操作 |
__mul | (*)乘操作 |
__div | (/)除操作 |
__idiv | (//)向下取整操作 |
__pow | (^)次方操作 |
__mod | (%)余操作 |
__unm | (-)取负操作 |
__band | (&)按位与操作 |
__bor | (|)按位或操作 |
__bxor | (~)按位异或操作 |
__bnot | (~)按位非操作 |
__shl | (<<)左位移操作 |
__shr | (>>)右位移操作 |
__concat | (…)连接操作 |
__len | (#)取长度操作 |
__eq | (==)等于操作 |
__lt | (<)小于操作 |
__le | (<=)小于等于操作 |
__index | 索引 table[key]。 当 table 不是表或是表 table 中不存在 key 这个键时,这个事件被触发。 此时,会读出 table 相应的元方法。 |
__newindex | 索引赋值 table[key] = value 。 和索引事件类似,它发生在 table 不是表或是表 table 中不存在 key 这个键的时候。 此时,会读出 table 相应的元方法。 |
__call | 函数调用操作func(args) 。当 Lua 尝试调用一个非函数的值的时候会触发这个事件 (即 func 不是一个函数)。 查找 func 的元方法, 如果找得到,就调用这个元方法, func 作为第一个参数传入,原来调用的参数(args)后依次排在后面。 |
__tostring | 字符串输出 |
__metatable | 保护元表。当调用setmetatable 方法时,如果元表中包括此事件,则会抛出一个错误。同样,当调用getmetatable 时,会返回此事件关联的值。 |
元表的使用
__add
Set = {} -- 声明一个全局集合对象
local mt = {} -- 声明一个局部元表
--[[
-- 创建新集合,并设置元表为mt方法
--]]
function Set.new(dataSet)
local reSet = {}
setmetatable(reSet,mt) -- 设新集合元表为mt
-- 遍历传入数据集并将数据写入新集合中(键值相同)
for i,v in pairs(dataSet) do
reSet[i] = v
end
return reSet;
end
--[[
-- 获取传入集合的并集方法
--]]
function Set.union(dataSetA,dataSetB)
local reSet = Set.new{} -- 创建新集合,相当于Set.new({})
-- 遍历A数据集
for i,v in pairs(dataSetA) do
reSet[i] = v
end
-- 遍历B数据集
for i,v in pairs(dataSetB) do
reSet[i] = v
end
return reSet
end
-- 测试new方法创建集合元表是为相同
local set1 = Set.new({10,20,30})
local set2 = Set.new({[3]=1,[4]=2})
assert(getmetatable(set1) ~= getmetatable(set2)) -- 返回table: 0x7fa2a8c072e0,assertion failed! 说明两元表相同
-- 以加操作为例,原本table没有加操作,通过元表附予table加操作
mt.__add = Set.union -- 给元表增加__add事件,并将事件的行赋予Set.union
local set3 = set1 + set2 -- 表之间可以相加,并且set3内容为set1和set2的并集 {10,20,30,1,2}
上例中
Set.union
就是元表的元方法
,Lua中不同元素都有相同或不同的元表,在执行不同操作时选择元哪个元表,Lua按照以下步骤进行:
1、对于二元操作符,如果第一个操作数有元表,并且元表中有所需要的事件定义,如__add
事件的定义,Lua就以这个元表的事件方法为元方法,而与第二个操作数无关;
2、对于二元操作符,如果第一个操作数有元表,但是元表中没有所需要的事件定义,如__add
元方法定义,Lua就去查找第二个操作数的元表;
3、如果两个操作数都没有元表,或者都没有对应的元方法定义,Lua就抛出一个错误。
__tostring
Lua中当表调用tostring()
、print()
等方法时会,会去寻找__tostring
事件的元方法,可以通过自定义元方法实现个性打印。
--[[
-- 定义自符串输出方法
--]]
function Set.toString(dataSet)
local tb = {}
for i,v in pairs(dataSet) do
tb[i] = v
end
return '{' .. table.concat(tb,',') .. '}' -- table.concat()返回列表的元素连接的字符串
end
-- 设置元表mt的__tostring的元方法
mt.__tostring = Set.toString
-- 直接打印集合set3
print(set3)
> {10,20,30,1,2}
__metatable
Lua元素元表可以随时通过setmetatable
方法修改,灵活方便同时也增加了风险,可以通__metatable
事件,使元素元表受到保护不可被修改。当再次调用setmetatable
方法时,将会抛出一个受保护的错误;当调用getmetatable
方法时,会返回事件关联的值。
-- 设置元表__matatable事件
mt.__metatable = '不允许修改元表'
print(getmetatable(set1)) -- 获取元表返回设置的内容
>> 不允许修改元表
setmetatable(set1,{}) -- 修改元表提示受保护元表不可以被改变
>> cannot change a protected metatable
模块
持续更新中…