第2章 类型与值
lua是一种动态类型的语言。在语言中没有类型定义的语法,每个值都携带了它自身的类型信息。
lua中有8种基础类型:nil(空)、boolean、number、string、userdata(自定义类型)、function、thread
和table。函数type可根据一个值返回其类型名称。
print(type("hello world")) -->string
print(type(10.4*3)) -->number
...
print(type(type(X))) -->string
最后一行将永远返回“string”,而无关乎X这个值的内容。只是因为type函数总是返回一个字符串。
变量没有预定义的类型,任何变量都可以包含任何类型的值:
print(type(a)) -->nil
a=10
print(type(a)) -->number
a="a string"
print(type(a)) -->string
a=print
a(type(a)) -->function
注意最后两行。在lua中,函数是作为“第一类值(first-class value)”来看待的,可以像操作其他值一样
来操作一个函数值。
将一个变量用于不同类型,通常会导致混乱的代码,但有时明智地使用这种特性会带来便利。例如,在异常情
况下,可以返回一个nil以区别于其他正常的返回值。
nil
nil是一种类型,它只有一个值nil,它的主要功能是用于区别其他任何值。就像之前所说的,一个全局变量在
第一次赋值前的默认值是nil,将nil赋予一个全局变量等同于删除它。lua将nil用于表示一种“无效之(non-
value)”的情况,即没有任何有效的情况。
boolean
boolean类型有两个可选值:false和true,这与传统的布尔值一样。然而boolean却不是一个条件值的唯一表达
方式。在lua中任何值都可以表示一个条件。lua将值false和nil视为假,而除此之外的其他值视为真。lua在条
件测试中,将数字零和空字符串也都视为真。
number
number类型用于表示实数。lua没有整数类型,因为没有必要。一直以来都存在着一个关于浮点数错误的误解。
有人会担心即使对浮点数进行一个简单的递增运算都可能导致错误的结果。而事实上,只要使用双精度来表示
一个整数,就不会出现“四舍吾入”的错误。因此,lua中的数字都可以表示任何32位整数,而不会产生四舍吾
入的错误。此外当今大多数cpu的浮点数运算速度和整数运算一样快,而有的cpu的浮点数运算可能还更快一点
。
通过重新编译lua也可以非常方便地使用其他类型来表示数字,例如使用长整数long或单精度浮点数float。这
对于某些没有浮点数硬件支持的平台来说尤为有用。见luaconf.h
书写一个数字常量时,可以使用普通写法,也可以使用科学计数法,一下是一些合法的数字常量:
4 0.4 4.57e-3 0.3e12 5e+20
string
lua中的字符串通常表示“一个字符序列”。lua完全采用8位编码,lua字符串中的字符可以具有任何数值编码
,包括数值0。也就是说,可以将任意二进制存储到字符串中。
lua字符串是不可变的值(immutalble values)。不能像在c语言中那样直接修改字符串的某个字符,而是应该
根据修改要求来创建一个新的字符串。
lua的字符串和其他lua对象(例如talbe或函数等)一样,都是自动内存管理机制所管理的对象。这表示无须担
心字符串的分配和释放,lua替你处理这些事情。一个字符串可以小到只包含一个字母,也可以大到包含整本书
。lua能够高效的处理长字符串。在lua程序中操作100k或1m的字符串是很常见的。
字面字符串(literal string)需要以一对匹配的单引号或双引号来界定:
a="a line"
b="another line"
根据编程风格,应该坚持在程序中使用相同类型的引号(单引号或双引号)。除非字符串本身包含引号,那么
可以使用另一种引号来界定字面字符串。或者使用反斜杠对引号进行转义。lua字符串中可以包含类似于c语言
中的转义序列。
另外,还可以用一对匹配的双方括号来界定一个字母字符串,就像写“块注释”那样。以这种形式写的字符串
可以延伸多行,Lua不会解释其中的转义序列。此外,如果字符串的第一个字符是一个换行符(new line),那
么Lua会忽略它。有时字符串中可能需要包含这样的内容:a=b[c[i]]。或者,可能需要包含已经被注释掉的代
码。为了应付这种情况,需要在两个左方括号间加上任意数量的等号,就像[===[。经过这样修改后,字面字符
串只有在遇到一个内嵌有相同数量等号的右时才会结束(就前例而言,即]===])。如果一组右左方括号中的等
号数量不等,那么Lua会忽略它。通过选择适当数量的等号,就可以不在加转义的情况下,直接嵌入任意的字符
串内容了。
这套机制同样适用于注释。例如,以“--[=[”开始的一个块注释将延伸至“]=]”结束。如此便简化了注释那
些“已经包含了注释块”的代码。
在lua5.1中,可以在字符串前面放置操作符“#”来获得该字符串的长度:
table表
table类型实现了“关联数组(associative array)”。“关联数组”是一种具有特殊索引方式的数组。不仅
可以通过整数来索引它,还可以使用字符串或其他类型的值(除了nil)来索引它。此外,table没有固定的大
小,可以动态地添加任意数量的元素到一个table中。table是Lua中主要的(事实上也是仅有的)数据结构机制
,具有强大的功能。基于table,可以以一种简单、统一和高效的方式来表示普通数组、符号表(symbol table
)、集合、记录、队列和其他数据结构。Lua也是通过table来表示module、package、和object的。当输入
io.read的时候,其含义是“io模块中的read函数”。对于Lua而言,这表示“使用字符串“read”作为key来索
引table io”。
在Lua中,table既不是“值”也不是变量而是“对象”。如果了解Java或Scheme中的数组,就会清楚明白其中
的意思了。可以将一个table想象成一种动态分配的对象,程序仅持有一个对它们的引用(或指针),Lua不会
暗中产生table的副本或创建新的table。此外,在Lua中也不需要声明一个table。事实上也没有办法可以声明
table。table的创建是通过“构造表达式(constructor expression)”完成的,最简单的构造表达式就是{
}。
a={}
k="x"
a[k]=10
a[20]="great"
print(a["x"])
k=20
print(a[k])
a["x"]=a["x"]+1
print(a["x"])
table永远是“匿名的(anonymous)”,一个持有table的变量与table自身之间没有固定的关联性。
a={}
a["x"]=10
b=a
print(b["x"])
b["x"]=20
print(a["x"])
a=nil
b=nil
当一个程序再也没有对一个table的应用时,Lua的垃圾收集器(garbage collector)最终会删除该table,并
服用它的内存。
所有table都可以用不用类型的索引来访问value值,当需要容纳新条目(entry)时,table会自动增长。
a={}
for i=1,1000 do a[i]=i*2 end
print(a[9])
a["x"]=10
print(a["x"])
print(a["y"])
上例中最后一行,这与全局变量一样,当table的某个元素没有初始化时,它的内容就为nil。另外还可以像全
局变量一样,将nil赋予table的某个元素来删除该元素。这种相似性是有原因的,因为Lua正是将全局变量存储
在一个普通table中。
为了表示一条记录,可以将字段名作为索引。Lua对于诸如a["name"]的写法提供了一种更简单的“语法糖
(syntacic sugar)”,可以直接输入a.name。因此,上例中的最后几行,可以更简单的写为:
a.x=10
print(a.x)
print(a.y)
对于Lua来说,这两种形式是等价的,可供自由选择使用。然而这两种形式对于一个读者来说,可能暗示了不同的意图。点的写法可能更明确暗示了读者,将table作为一条记录来使用,每条记录都有一组固定的、预定义的key。而字符串的写法可能暗示了该table会以任何字符串作为key,而现在由于某些原因,需要访问某个特定的key。
a.x和a[x]:前者表示a["x"],表示以字符串“x”来索引table。而后者是以变量x的值来索引table。
由于数组实际上是一个table,所以关于其大小的概念可能会有些模糊。例如,以下这个数组的大小算是多少呢?
a={}
a[10000]=1
请记住对于所有未初始化的元素的索引结果都是nil。Lua将nil作为界定数组结尾的标志。
如果真的需要处理那些含有“空隙”的数组,可以使用函数table。maxn,它将返回一个table的最大正索引数:
a={}
a[10000]=1
print(table.maxn(a)) -->10000
function函数
函数作为“第一类值”来看待的。这表示函数可以存储在变量中,可以通过参数传递给其他函数,还可以作为其他函数的返回值。这种特征使语言具有极大的灵活性。为了给一个函数添加新的功能,程序可以重新定义该函数。而在运行一些不熟信任的代码时,可以先删除某些函数,从而创建一个安全的运行环境。此外,Lua对于“函数式编程 functional programming”也提供了良好的支持。例如,允许在某些词法域(lexical scoping)中编写嵌套的函数。
Lua既可以调用以自身Lua语言编写的函数,又可以调用c语言编写的函数。Lua所有的标准库都是用c语言写的,标准库中包含对字符串的操作、table的操作、I/O、操作系统的功能调用、数学函数和调试函数。
userdata 和 thread
由于userdata类型可以将任意的c语言数据存储到Lua变量中。在Lua中,这种类型没有太多的预定义操作,只能进行赋值和相等性测试。userdata用于表示一种由应用程序或c语言库所创建的新类型,例如标准的I/O库就用userdata来表示文件。