表
Lua语言中的表本质上是一种辅助数组(associative array),这种数组不仅可以使用数值作为索引,也可以使用字符串或其他任意类型的值作为索引(nil除外)。
Lua语言中的表要么是值要么是变量,它们都是对象(object),表是一种动态分配的对象,程序只能操作指向表的引用(或指针)。除此以外,Lua语言不会进行隐藏的拷贝(hidden copies)或创建新的表
对于一个表而言,当程序中不再有指向它的引用时,垃圾收集器会最终删除这个表并重用其占用的内存。
当语句 b = a,指的是b这个引用也指向了a指向的表,指向的是同一张表
表索引值
同一个表中存储的值可以具有不同的类型索引,并可以按需增长以容纳新的元素
将nil赋值给表元素可以将其删除,Lua语言实际上就是使用表来存储全局变量的
当把表当作结构体使用时,可以把索引当作成员名称使用
a.x代表的是a["x"],即由字符串"x"索引的表;而a[x]则是指由变量x对应的值索引的表
2和2.0的值相等,所以当它们被当作表索引使用时指向的是同一个表元素,当被用作表索引时,任何能够被转换为整型的浮点数都会被转换成整型数
表构造器
表构造器(Table Constructor)是用来创建和初始化表的表达式
在同一个构造器中,可以混用记录式(record-style)和列表式(list-style)写法
通过方括号括起来的表达式显式的指定每一个索引
数组,列表和序列
如果想表示常见的数组(array)或列表(list),那么只需要使用整型作为索引的表即可
我们把这种所有元素都不为nil的数组称为序列(sequence),也就是整个数组是全部有效的,没有空洞,可以将末尾设置成nil来标记数组的长度
操作符 # 也可以用于表,返回表对应的序列的长度
对于中间存在空洞(nil值)的列表而言,序列长度操作时不可靠的
不包含数值类型的键的表就是长度为零的序列
遍历表
pairs 顺序是随机的,保证每个元素出现一次
ipairs 遍历,保证按顺序遍历
使用数值型for循环
安全访问
对表进行了6次访问,改写成下面这样
于表达式a or{},当a为nil时其结果是一个空表
表中的每个字段名都只是用了一次,从而保证了尽可能少的对表进行访问,本例中访问了三次,同时避免了引入新的操作符
表标准函数
函数table.insert向序列的指定位置插入一个元素,其他元素依次后移
对于列表t={10,20,30},在调用table.insert(t,1,15)后它会变成{15,10,20,30}
调用时不指定位置的话会直接插在末尾不移动任何元素
函数table.remove删除并返回序列指定位置的元素,然后将其后的元素向前移动填充删除元素后造成的空洞。如果在调用该函数时不指定位置,该函数会删除序列的最后一个元素
函数table.move(a,f,e,t),调用该函数可以将表a中从索引f到e的元素(包含索引f和索引e对应的元素本身)移动到位置t上,这样就可以空出第一个元素的位置,方便模拟栈
函数
当函数只有一个参数且该参数是字符串常量或表构造器时,括号是可选的:
Lua语言也为面向对象风格的调用(object-oriented call)提供了一种特殊的语法,即冒号操作符。形如o:foo(x)的表达式意为调用对象o的foo方法
一个Lua程序既可以调用Lua语言编写的函数,也可以调用C语言(或者宿主程序使用的其他任意语言)编写的函数
调用函数时使用的参数个数可以与定义函数时使用的参数个数不一致。Lua语言会通过抛弃多余参数和将不足的参数设为nil的方式来调整参数的个数。
多返回值
使用多重赋值(multipleassignment)可以同时获取到这两个结果:
Lua语言编写的函数同样可以返回多个结果,只需在return关键字后列出所有要返回的值即可
Lua语言根据函数的被调用情况调整返回值的数量。当函数被作为一条单独语句调用时,其所有返回值都会被丢弃;当函数被作为表达式(例如,加法的操作数)调用时,将只保留函数的第一个返回值。只有当函数调用是一系列表达式中的最后一个表达式(或是唯一一个表达式)时,其所有的返回值才能被获取到
在多重赋值中,如果一个函数调用是一系列表达式中的最后(或者是唯一)一个表达式,则该函数调用将产生尽可能多的返回值以匹配待赋值变量,否则只返回一个结果
当一个函数调用是另一个函数调用的最后一个(或者是唯一)实参时,第一个函数的所有返回值都会被作为实参传给第二个函数。我们已经见到过很多这样的代码结构,例如函数print。由于函数print能够接收可变数量的参数,所以print(g())会打印出g返回的所有结果。
表构造器会完整地接收函数调用的所有返回值,而不会调整返回值的个数
不过,这种行为只有当函数调用是表达式列表中的最后一个时才有效,在其他位置上的函数调用总是只返回一个结果:
将函数调用用一对圆括号括起来可以强制其只返回一个结果
可变长参数函数
Lua语言中的函数可以是可变长参数函数(variadic),即可以支持数量可变的参数
我们将三个点组成的表达式称为可变长参数表达式(vararg expression)
具有可变长参数的函数也可以具有任意数量的固定参数,但固定参数必须放在变长参数之前。Lua语言会先将前面的参数赋给固定参数,然后将剩余的参数(如果有)作为可变长参数。
样。不过,在某些罕见的情况下,如果可变长参数中包含无效的nil,那么{...}获得的表可能不再是一个有效的序列。此时,就没有办法在表中判断原始参数究竟是不是以nil结尾的。对于这种情况,Lua语言提供了函数table.pack。该函数像表达式{...}一样保存所有的参数,然后将其放在一个表中返回,但是这个表还有一个保存了参数个数的额外字段"n"
另一种遍历函数的可变长参数的方法是使用函数select。函数select总是具有一个固定的参数selector,以及数量可变的参数。如果selector是数值n,那么函数select则返回第n个参数后的所有参数;否则,selector应该是字符串"#",以便函数select返回额外参数的总数。
函数table.unpack
该函数的参数是一个数组,返回值为数组内的所有元素:
函数table.unpack与函数table.pack的功能相反。pack把参数列表转换成Lua语言中一个真实的列表(一个表),而unpack则把Lua语言中的真实的列表(一个表)转换成一组返回值,进而可以作为另一个函数的参数被使用。
如果我们想通过数组a传入可变的参数来调用函数f,那么可以写成:
正确的尾调用
Lua语言是支持尾调用消除(tail-call elimination)的。这意味着Lua语言可以正确地(properly)尾递归(tail recursive)
尾调用(tail call)是被当作函数调用使用的跳转。当一个函数的最后一个动作是调用另一个函数而没有再进行其他工作时,就形成了尾调用
当函数f调用完函数g之后,f不再需要进行其他的工作。这样,当被调用的函数执行结束后,程序就不再需要返回最初的调用者。因此,在尾调用之后,程序也就不需要在调用栈中保存有关调用函数的任何信息。当g返回时,程序的执行路径会直接返回到调用f的位置。使得在进行尾调用时不使用任何额外的栈空间。我们就将这种实现称为尾调用消除(tail-call elimination)。
由于尾调用不会使用栈空间,所以一个程序中能够嵌套的尾调用的数量是无限的。例如,下列函数支持任意的数字作为参数
该函数永远不会栈溢出
很多函数调用之所以不是尾调用,是由于这些函数在调用之后还进行了其他工作。例如,下例中调用g就不是尾调用:
这个示例的问题在于,当调用完g后,f在返回前还不得不丢弃g返回的所有结果。类似的,以下的所有调用也都不符合尾调用的定义:
在Lua语言中,只有形如returnfunc(args)的调用才是尾调用。不过,由于Lua语言会在调用前对func及其参数求值,所以func及其参数都可以是复杂的表达式。例如,下面的例子就是尾调用:
--print(returnWithoutFirst(1,2,3,4,5))
---练习6.3 请编写一个函数,该函数的参数为可变数量的一组值,返回除最后元素之外的其他所有值
local function returnWithoutLast(...)
local t = table.pack(...)
table.remove(t)
return table.unpack(t)
end
--print(returnWithoutLast(1,2,3,4,5))