1 数学运算操作符
1.1 %
操作符
Lua 中的 %
操作符与 C 语言中的操作符虽然都是取模的含义,但是取模的方式不一样。
在 C 语言中,取模操作是将两个操作数的绝对值取模后,在添加上第一个操作数的符号。
而在 Lua 中,仅仅是简单的对商相对负无穷向下取整后的余数。
+++
在 C 中,
a1 = abs(a);
b1 = abs(b);
c = a1 % b1 = a1 - floor(a1/b1)*b1;
a % b = (a >= 0) ? c : -c;
在 Lua 中,
a % b == a - math.floor(a/b)*b
Lua 是直接根据取模定义进行运算。 C 则对取模运算做了一点处理。
+++
举例:
在 C 中
int a = 5 % 6;
int b = 5 % -6;
int c = -5 % 6;
int d = -5 % -6;
printf("a,b,c,d");--5,5,-5,-5
在 Lua 中
a = 5 % 6
b = 5 % -6
c = -5 % 6
d = -5 % -6
x = {a,b,c,d}
for i,v in ipairs(x) do
print(i,v)
end
--> 5
--> -1
--> 1
--> -5
可以看到,仅当操作数同号时,两种语言的取模结果相同。异号时,取模结果的符号与数值均不相等。
在 Lua 中的取模运算总结为:a % b,如果 a,b 同号,结果取 a,b 绝对值的模;异号,结果取 b 绝对值与绝对值取模后的差。取模后值的符号与 b 相同。
2 比较操作符
比较操作的结果是 boolean
型的,非 true
即 false
。
支持的操作符有:
< <= ~= == > >=
不支持 !
操作符。
+++
对于 ==
操作,运算时先比较两个操作数的类型,如果不一致则结果为 false。此时数值与字符串之间并不会自动转换。
比较两个对象是否相等时,仅当指向同一内存区域时,判定为 true
。·
a = 123
b = 233
c = "123"
d = "123"
e = {1,2,3}
f = e
g = {1,2,3}
print(a == b) --> false
print(a == c) --> false -- 数字与字符串作为不同类型进行比较
print(c == d) --> true
print(e == f) --> true -- 引用指向相同的对象
print(e == g) --> false -- 虽然内容相同,但是是不同的对象
print(false == nil) --> false -- false 是 boolean,nil 是 nil 型
方便标记,-->
代表前面表达式的结果。
+++
userdata
与 table
的比较方式可以通过元方法 eq
进行改变。
大小比较中,数字和字符串的比较与 C 语言一致。如果是其他类型的值,Lua会尝试调用元方法 lt
和 le
。
3 逻辑操作符
and,or,not
仅认为 false
与 nil
为假。
3.1 not
取反操作 not
的结果为 boolean
类型。(and
和 or
的结果则不一定为 boolean
)
b = not a -- a 为 nil,b 为 true
c = not not a -- c 为 false
3.2 and
a and b
,如果 a
为假,返回 a
,如果 a
为真, 返回 b
。
注意,为什么 a
为假的时候要返回 a
呢?有什么意义?这是因为 a
可能是 false
或者 nil
,这两个值虽然都为假,但是是有区别的。
3.3 or
a or b
,如果 a
为假,返回 b
,如果 a
为真, 返回 a
。与 and
相反。
+++
提示: 当逻辑操作符用于得出一个 boolean
型结果时,不需要考虑逻辑运算后返回谁的问题,因为逻辑操作符的操作结果符合原本的逻辑含义。
举例
if (not (a > min and a < max)) then -- 如果 a 不在范围内,则报错
error()
end
+++
3.4 其他
and
与 or
遵循短路原则,第二个操作数仅在需要的时候会进行求值操作。
例子
a = 5
x = a or jjjj() -- 虽然后面的函数并没有定义,但是由于不会执行,因此不会报错。
print(a) -->5
print(x) -->5
通过上面这个例子,我们应当对于逻辑操作有所警觉,因为这可能会引入一些未能及时预料到的错误。
4 连接符
..
连接两个字符串(或者数字)成为新的字符串。对于其他类型,调用元方法 concat
。
5 取长度操作符
#
对于字符串,长度为字符串的字符个数。
对于表,通过寻找满足t[n] 不是 nil 而 t[n+1] 为 nil 的下标 n 作为表的长度。
~~对于其他类型呢?~~
5.1 例子
-- 字符串取长
print(#"abc\0") --> 4
-- 表取长
print(#{[1]=1,[2]=2,[3]=3,x=5,y=6}) --> 3
print(#{[1]=1,[2]=nil,[3]=3,x=5,y=6}) --> 1
6 优先级
由低到高:
or
and
< > <= >= ~= ==
..
+ -
* / %
not # - (unary)
^
幂运算>单目运算>四则运算>连接符>比较操作符>and>or
7 Table 构造
Table 构造的 BNF 定义
tableconstructor ::= `{´ [fieldlist] `}´
fieldlist ::= field {fieldsep field} [fieldsep]
field ::= `[´ exp `]´ `=´ exp | Name `=´ exp | exp
fieldsep ::= `,´ | `;´
BNF 定义参考 BNF范式简介 。
举例:
a = {}
b = {["price"] = 5; cost = 4; 2+5}
c = { [1] = 2+5, [2] = 2, 8, price = "abc", ["cost"] = 4} -- b 和 c 构造的表是等价的
print(b["price"]) --> 5
print(b.cost) --> 4
print(b[1]) --> 7 -- 未给出键值的,按序分配下标,下标从 1 开始
print(c["price"]) --> abc
print(c.cost) --> 4
print(c[1]) --> 8
print(c[2]) --> 2
注意:
- 未给出键值的,按序分配下标,下标从 1 开始
- 如果表中有相同的键,那么以靠后的那个值作为键对应的值
上面这两条的存在使得上面的例子中 c1 的输出值为 8。
+++
如果表中有相同的键,那么以靠后的那个值作为键对应的值。
a = {[1] = 5,[1] = 6} -- 那么 a[1] = 6
+++
如果表的最后一个域是表达式形式,并且是一个函数,那么这个函数的所有返回值都会加入到表中。
a = 1
function order()
a = a + 1
return 1,2,3,4
end
b = {order(); a; order(); }
c = {order(); a; (order());}
print(b[1]) --> 1
print(b[2]) --> 2 -- 表中的值并不是一次把表达式都计算结束后再赋值的
print(b[3]) --> 1
print(b[4]) --> 2 -- 表达式形式的多返回值函数
print(#b) --> 6 -- 表的长度为 6
print(#c) --> 3 -- 函数添加括号后表的长度为 3
8 函数
函数是一个表达式,其值为 function 类型的对象。函数每次执行都会被实例化。
8.1 函数定义
Lua 中实现一个函数可以有以下三种形式。
f = function() [block] end
local f; f = function() [block] end
a.f = function() [block] end
Lua 提供语法糖分别处理这三种函数定义。
function f() [block] end
local function f() [block] end
function a.f() [block] end
+++
上面 local
函数的定义之所以不是 local f = function() [block] end
,是为了避免如下错误:
local f = function()
print("local fun")
if i==0 then
f() -- 编译错误:attempt to call global 'f' (a nil value)
i = i + 1
end
end
8.2 函数的参数
形参会通过实参来初始化为局部变量。
参数列表的尾部添加 ...
表示函数能接受不定长参数。如果尾部不添加,那么函数的参数列表长度是固定的。
f(a,b)
g(a,b,...)
h(a,...,b) -- 编译错误
f(1) --> a = 1, b = nil
f(1,2) --> a = 1, b = 2
f(1,2,3) --> a = 1, b = 2
g(1,2) --> a = 1, b = 2, (nothing)
g(1,2,3) --> a = 1, b = 2, (3)
g(1,f(4,5),3) --> a = 1, b = 4, (3)
g(1,f(4,5)) --> a = 1, b = 4, (5)
+++
还有一种形参为self的函数的定义方式:
a.f = function (self, params) [block] end
其语法糖形式为:
function a:f(params) [block] end
使用举例:
a = {name = "唐衣可俊"}
function a:f()
print(self.name)
end
a:f() --> 唐衣可俊 -- 如果这里使用 a.f(),那么 self.name 的地方会报错 attempt to index local 'self';此时应该写为 a.f(a)
:
的作用在于函数定义与调用的时候可以少写一个 self
参数。这种形式是对方法
的模拟
8.3 函数调用
Lua 中的函数调用的BNF语法如下:
functioncall ::= prefixexp args
如果 prefixexp 的值的类型是 function, 那么这个函数就被用给出的参数调用。 否则 prefixexp 的元方法 “call” 就被调用, call 的第一个参数就是 prefixexp 的值,接下来的是 args 参数列表(参见 2.8 元表 | Metatable)。
函数调用根据是否传入 self
参数分为 .
调用和 :
调用。
函数调用根据传入参数的类型,可以分为参数列表调用、表调用、字符串调用。
[待完善]
8.4 函数闭包
如果一个函数访问了它的外部变量,那么它就是一个闭包。
由于函数内部的变量均为局部变量,外界无法对其进行访问。这时如果外界想要改变局部变量的值,那么就可以使用闭包来实现这一目的。
具体的实现过程大致是这样,函数内部有能够改变局部变量的子函数,函数将这个子函数返回,那么外界就可以通过使用这个子函数来操作局部变量了。
例子:利用闭包来实现对局部变量进行改变
-- 实现一个迭代器
function begin(i)
local cnt = i
return function () -- 这是一个匿名函数,实现了自增的功能;同时它也是一个闭包,因为访问了外部变量 cnt
cnt = cnt + 1
return cnt
end
end
iterator = begin(2) -- 设置迭代器的初值为 2 ,返回一个迭代器函数
print(iterator()) -- 执行迭代
print(iterator())
提示: 关于闭包的更多说明可参考JavaScript 闭包是如何工作的?——StackOverflow
参考链接
BNF范式简介 (简要介绍 BNF)
How do JavaScript closures work?——StackOverflow(详细介绍了 Javascript 中闭包的概念)