lua表达式(Expressions)
表达式其实表示的就是值。Lua中表达式包括:数字常量,字符常量,变量,一元和二元操作符号,函数呼叫(function calls)。表达式还包括一些非传统的函数定义,和表构造(table constructors)
3.1 算术运算符
Lua支持普遍的数学运算,其中包括: 二元运算符:+ - * / ^ (加减乘除)以及 一元运算符:- (负值)。上述所有的操作其操作对象都是实数,即操作数都是实数。
对于"^"(exponentiation)幂操作,Lua也提供了部分的支持。提供这种幂操作的主要用意是因为,Lua本身是一个很小的core解释器,而每次需要使用幂操作的时候,Lua都是通过调用C语言中的pow函数来实现的,而这种实现意味着程序员总是需要在Lua程序中链接C语言的的数学类库(mathematical library)。正是为了避免这种问题的发生,Lua在内核中提供了对幂操作的支持,且仅仅提供了对幂操作的操作的语句的解释。在Lua的语法优先级中,幂操作的优先级是最高的。通过这种对幂操作支持的数学类库(虽然幂操作被列入了标准类库中,但它并不是Lua内核的一部分,Lua内核只是提供了解释幂操作语句的功能。)很好的解决了上面出现的问题。
3.2 关系运算符
Lua提供了下面的关系操作:
1. < > <= >= == ~=
上述所有的操作符返回结果只有false或者true。
操作符==用于判断相等的操作;操作符~=用于判断两个值不相等的操作。我们可以在任意两个变量或者常量之间应用这两个操作。如果两个变量或者常量的类型不同,Lua认为两者不同。Lua的这种比较是根据它们的类型进行的。特别指出的是:nil只和自己相等。
Lua通过引用(reference)的方式比较tables,userdata,functions。也就是说当且仅当两者同时指向同一个对象时相等。
1. a = {}; a.x = 1; a.y = 0
2. b = {}; b.x = 1; b.y = 0
3. c = a
上述例子中,Lua认为a == c但是a ~=b
当我们比较两个数字或者字符的时候,我们通常是根据某一种排序的方式进行的。Lua比较数字按传统的数字大小进行,比较字符串按字母的顺序进行,但是字母顺序依赖于本地环境。因为不同的Lua设置,对于字母的排序是不同的。例如,采用European Latin-1的本地设置后,"acai" < "a?aí" < "acorde"是可以得到的,但是,如果我们采用其他的字符设置,可能上面三个字符串得到的结果可能就不对了。当我们比较两个类型不同的值的时候,请记住:"0"==0的返回结果是false。2<15
很明显也是返回真(true),但是,"2"<"15"返回的结果就是false了(按字母排序的!)。为了避免这种现象的发生,Lua将类型不同的值得比较定义为一个错误。
3.3 逻辑运算符
逻辑操作符包括:与(and),或(or),非(not)。逻辑运算符认为false和nil是假(false),除此以外所有的斗室真(true),0也是true。与(and)和或(or)的运算结果不是true和false,而是和它的两个操作数相关。例如:
1. a and b :如果a为false,则返回a,否则返回b
2. a or b :如果a为true,则返回a,否则返回b
3. print(4 and 5) --> 5
4. print(nil and 13) --> nil
5. print(false and 13) --> false
6. print(false or 5) --> 5
7. print(4 or 5) --> 4
与(and)和或(or)使用的是捷径赋值(short-cut evaluation)得方式进行运算的。即只有第一个表达式为true的时候,才会执行第二个表达式,否则,如果第一个表达式为false,则后面的表达式就不需要执行了。
一个很实用的技巧:如果x为false或者nil则给x赋初始值v,否则x保持原来的值不变。x = x or v 等价于 if not x then x = v end 。这个例子说明,设定x的默认值是v ,当x没有设定其他值为默认值的时候(包括当x没有设定什么状态为false的时候)。
另一个实用的技巧是:(a and b) or c(或者简写成:a and b or c,因为and的优先级比or姚高),它等价于C语言中的表达式:a ? b : c。假设b的值为非假(not false)。例如,我们可以得到两个数的最大数,这表达式可写成:
1. max = (x > y) and x or y
当x > y,则第一个表达式的返回值为真(true),此时判断第二个表达式(第二个表达式的返回值也是真(true),因为第二个表达式也是这个数字)所以or表达式的返回值是第一个表达式的返回值x。当x > y返回值为假(false)的时候,and表达式也是假(false),所以or返回第二个表达式的值y非(not)操作总是返回true或者false:
1. print(not nil) --> true
2. print(not false) --> true
3. print(not 0) --> false
4. print(not not nil) --> false
3.4 连接运算符
Lua提供的字符连接运算符为".." (两点)。如果操作对象是一个数字,则Lua会将该数字对象转换成字符,然后进行连接。
1. print("Hello " .. "World") --> Hello World
2. print(0 .. 1) --> 01
值得注意的是:Lua中的字符是不可变的常量。字符连接符总是创建一个新的字符空间,而不用改变原来的操作对象的。
1. a = "Hello"
2. print(a .. " World") --> Hello World
3. print(a) --> Hello
3.5 优先级
Lua中的操作优先级按照下表,优先级别高的在先的顺序排列如下:
从高到低的顺序:
1. ^
2. not - (unary)
3. * /
4. + -
5. ..
6. < > <= >= ~= ==
7. and
8. or
除了^(幂操作)和..(字符连接符)是右连接的外,所有的二元运算符都是左连接的。因此下面的表达式中,左边的表达式等价于右边的表达式:
1. a+i < b/2+1 <--> (a+i) < ((b/2)+1)
2. 5+x^2*8 <--> 5+((x^2)*8)
3. a < y and y <= z <--> (a < y) and (y <= z)
4. -x^2 <--> -(x^2)
5. x^y^z <--> x^(y^z)
当我们在写上述的表达式的时候,比较好的习惯是用圆括号“()”来区分其优先级,这样写,有利于我们查看和阅读。
3.6 表的构造(Table Constructors)
构造的过程是创建和初始化表的表达式。表(Table)是Lua提供的最具特别的功能之一,也是Lua强大和方便的重要机制之一。最简单的构造函数是{},用来创建一个空表.可以直接初始化数组:
1. days = {"Sunday", "Monday", "Tuesday", "Wednesday",
2. "Thursday", "Friday", "Saturday"}
Lua将用string "Sunday"初始化days[1](第一个元素索引为1,而不是0),用"Monday"初始化days[2]:
1. print(days[4]) --> Wednesday
构造器不仅可以使用常量,而且还可以使用任何表达式初始化:
1. tab = {sin(1), sin(2), sin(3), sin(4),
2. sin(5), sin(6), sin(7), sin(8)}
如果想初始化一个表作为record使用,可以这样:
1. a = {x=0, y=0}
上面的表达式等价于:
1. a = {}; a.x=0; a.y=0
不管用何种方式创建table,我们都可以在任何时候,向表中添加或者删除任何类型的字段(field)。例如:
1. w = {x=0, y=0, label="console"}
2. x = {sin(0), sin(1), sin(2)}
3. w[1] = "another field"
4. x.f = w
5. print(w["x"]) --> 0
6. print(w[1]) --> another field
7. print(x.f[1]) --> another field
8. w.x = nil -- remove field "x"
因为构造器仅仅影响表的初始化,故所有上面被创建的表都可以在我们需要的时候,对字段(field)进行新增和删除。每次调用构造器,Lua都会创建一个新的table,利用这种特性,我们可以使用table构造一个list:
1. list = nil
2. for line in io.lines() do
3. list = {next=list, value=line}
4. end
这段代码从标准输入读进每行,然后反序形成链表。每一个节点(node)在整个list中代表的是一个表(table),这个表有两个字段:一个是保存字符串的value,还有一个字段是一个引用(reference),它保存得是下一个节点的地址。下面的代码打印list的内容:
1. l = list
2. while l do
3. print(l.value)
4. l = l.next
5. end
(上面的这个应用,事实上我们是应用list作为一个栈(stack),所以,我们在打印的时候其顺序是反的。)虽然上面的例题是有用的,但是我们很难将上面的例题应用于实际的Lua程序当中。通常来说,我们将list作为数组(array)来使用会比作为栈(stack)要好。具体的使用方法请参照第11章节的内容。在同一个构造函数中可以将列表风格和record风格混在一起进行初始化,例如:
1. polyline = {color="blue", thickness=2, npoints=4,
2. {x=0, y=0},
3. {x=-10, y=0},
4. {x=-10, y=1},
5. {x=0, y=1}
6. }
这个例子也表明构造器可以嵌套来表示复杂的数据结构。而每一个元素,例如polyline[1], ..., polyline[4]代表着这个表(Table)的一条记录(Record):
1. print(polyline[2].x) --> -10
上面两种构造器的初始化方式存在其自身的局限。例如,你不能使用负索引(negative indices)或者字符串索引(string indice)来初始化你的字段(field),并且负索引(negative indices)或者字符串索引(string indice)也不能被恰当的表示,为了迎合这种需要,下面介绍一种更一般的初始化方式,我们明确指出索引的意义,并且用一个表达式来初始化这个索引:
1. opnames = {["+"] = "add", ["-"] = "sub",
2. ["*"] = "mul", ["/"] = "div"}
3. i = 20; s = "-"
4. a = {[i+0] = s, [i+1] = s..s, [i+2] = s..s..s}
5. print(opnames[s]) --> sub
6. print(a[22]) --> ---
虽然这种语句在书写的时候会非常的麻烦,但是这种方法有利于日后的扩展,而且写出来的代码,也比较容易阅读。list风格初始化和record风格初始化是这种一般初始化的特例:
{x=0, y=0}
它等价于:
1. {["x"]=0, ["y"]=0}
而构造器
1. {"red", "green", "blue"}
等价于:
1. {[1]="red", [2]="green", [3]="blue"}
如果真的想要数组下标从0开始,按照下面的书写方式其实也不是很困难,如下所示:
1. days = {[0]="Sunday", "Monday", "Tuesday", "Wednesday",
2. "Thursday", "Friday", "Saturday"}
从上面的例子,我们可以得到,"Sunday"的索引值为0。而这个索引将不会影响到其它的字段(field)。并且,很自然的"Monday"的索引值为1,但是这样做的结果是,构造器将认"Monday"是整个list的第一个节点(node)。所以,尽管我们可以很容易的指定下标为0的数组,我们还是不建议使用这种方法,因为在Lua中大部分的函数都认为数组的下标是从1开始的,所以,当这样的数组被建立后,Lua解释器在对其进行运算的时候,是不会得到正确的处理结果的。在构造函数的最后的","是可选的,可以方便以后的扩展。
1. a = {[1]="red", [2]="green", [3]="blue",}
上面的这个写法,给了我们很大的便利,通过这样的方法,可以让我们很容易的写出我们的程序,很容易的产生出一个Lua表(table),因为,有了这个逗号,Lua解释器将不会将最后一个成员作为一个特殊的情况进行处理了。在构造函数中域分隔符逗号(",")可以用分号(";")替代,通常我们使用分号用来分割不同类型的表元素。
1. {x=10, y=45; "one", "two", "three"}