Lua 学习笔记(三)—— 表达式

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 型的,非 truefalse

支持的操作符有:

< <= ~= == > >=

不支持 ! 操作符。

+++

对于 == 操作,运算时先比较两个操作数的类型,如果不一致则结果为 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 型

方便标记,--> 代表前面表达式的结果。

+++

userdatatable 的比较方式可以通过元方法 eq 进行改变。

大小比较中,数字和字符串的比较与 C 语言一致。如果是其他类型的值,Lua会尝试调用元方法 ltle

3 逻辑操作符

and,or,not

仅认为 falsenil 为假。

3.1 not

取反操作 not 的结果为 boolean 类型。(andor 的结果则不一定为 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 其他

andor 遵循短路原则,第二个操作数仅在需要的时候会进行求值操作。

例子


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 中闭包的概念)

    原文作者:tangyikejun
    原文地址: https://segmentfault.com/a/1190000002723520
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞