2.类型和值(Types and Values)
Lua是动态类型语言,变量不要类型定义. 变量的值就决定了变量的类型。
Lua中有8个基本类型:nil, boolean, number, string, userdata, function, thread, and table.
type函数可以返回给定值的类型。例如:
- print(type("Hello world")) --> string
- print(type(10.4*3)) --> number
- print(type(print)) --> function
- print(type(type)) --> function
- print(type(true)) --> boolean
- print(type(nil)) --> nil
- print(type(type(X))) --> string
最后这句中,结果肯定是“string”,因为无论X的取值如何,type(type(X))都会返回一个字符串。
变量没有预定义的类型,每一个变量都可能包含任一种类型的值.
- print(type(a)) --> nil (`a' is not initialized)
- a = 10
- print(type(a)) --> number
- a = "a string!!"
- print(type(a)) --> string
- a = print -- yes, this is valid!
- a(type(a)) --> function
注意上面最后两行,我们可以使用function像使用其他值一样使用(第6章中会作详细说明)。
一般情况下同一变量代表不同类型的值会造成混乱,最好不要用,特殊情况下可以带来便利,比如nil.
2.1 空类型(nil)
Lua中特殊的类型,它的主要作用是让一个变量的值和其它的值区分开。我们已经知道,全局变量在被赋值前,其初始值是nil。给全局变量赋nil可以删除该变量。Lua通过nil来表现一种“无值”的概念。
2.2 布尔型(booleans)
两个取值false和true。但要注意Lua中所有的值都可以作为条件.在控制结构的条件中除了false和nil为假,其他值都为真.所以Lua认为0和空串都是真。这和某些脚本语言是不同的。
2.3 数字型(numbers)
表示实数,Lua中没有整数.一般有个错误的看法CPU运算浮点数比整数慢.事实不是如此,用实数代替整数不会有什么误差(除非数字大于100,000,000,000,000).Lua的numbers可以处理任何长整数不用担心误差。你也可以在编译Lua的时候使用长整型或者单精度符点型代替numbers。这在不支持硬件浮点运算的平台上很有用。Lua可以以科学计数法来表示数字,下面是数字常量的例子:
- 4 0.4 4.57e-3 0.3e12 5e+20
2.4 字符串(strings)
string表示字符串,就像很多语言中的string一样,它指一组字符序列。lua
是8位字节,所以字符串可以包含任何数值字符,包括嵌入的0。
这意味着你可以存储任意的2进制数据在一个字符串里.Lua中字符串是不可以修改的(这点和C语言不同),你可以创建一个新的变量存放你要的字符串,比如:
- a = "one string"
- b = string.gsub(a, "one", "another") -- 修改部分字符串
print(a) --> one string- print(b) --> another string
string和其他对象一样,Lua自动进行内存分配和释放,你不必关心为字符串申请内存空间或释放它的问题。一个string可以只包含一个字母也可以包含一本书,Lua可以高效的处理长字符串,100K或1M的string在Lua中是很常见的。可以使用单引号或者双引号表示字符串
a = "a line"- b = 'another line'
为了风格统一,最好使用一种,除非两种引号嵌套情况.对于字符串中含有引号的情况还可以使用转义符\来表示.Lua中的转义序列有:
\a |
bell |
\b |
back space |
\f |
form feed |
\n |
newline |
\r |
carriage return |
\t |
horizontal tab |
\v |
vertical tab |
\\ |
backslash |
\" |
double quote |
\' |
single quote |
\[ |
left square bracket |
\] |
right square bracket |
例子:
- print("one line\nnext line\n\"in quotes\", 'in quotes'")
- one line
- next line
- "in quotes", 'in quotes'
- > print('a backslash inside quotes: \'\\\'')
- a backslash inside quotes: '\'
- > print("a simpler way: '\\'")
- a simpler way: '\'
还可以在字符串中使用\ddd(ddd为三位十进制数字)方式表示字母。"alo\n123\"" 和 '\97lo\10\04923"'是相同的。还可以使用[[...]]表示字符串.这种形式的字符串可以包含多行,可以嵌套,不会解释转义序列,如果第一个字符是换行符会被自动忽略掉.这种形式的字符串用来包含一段代码是非常方便的.
- page = [[
- An HTML Page
- >
- [[a text between double brackets]]
--注意:上面这行代码在Lua5.0.2版本中是可以执行的,但是在Lua5.1版本中就不行了。
- ]]
- io.write(page)
运行时,Lua会在string和numbers之间自动进行类型转换,当一个字符串使用算术操作符时,string会被转成数字。
- print("10" + 1) --> 11
- print("10 + 1") --> 10 + 1
- print("-5.3e - 10"*"2") --> -1.06e-09
- print("hello" + 1) -- ERROR (cannot convert "hello")
反过来,当Lua期望一个string而碰到数字时,会将数字转成string。
- print(10 .. 20) --> 1020
(".."在Lua中是字符串连接符,当在一个数字后面写..时必须加上空格防止被解释错(如果不加,Lua会将第一个.解释为小数点,这样就错了)。尽管字符串和数字可以自动转换,两者是不同的,像 10 == "10"这样的比较永远都是错的。如果需要显式将string转成数字可以使用函数tonumber(),如果string不是正确的数字该函数返回nil。
- line = io.read() -- read a line
- n = tonumber(line) -- try to convert it to a number
- if n == nil then
- error(line .. " is not a valid number")
- else
- print(n*2)
- end
反之,可以调用tostring()将数字转成字符串,或者,在一个数字后连接一个空字符串,也可以实现这种转换:
- print(tostring(10) == "10") --> true
- print(10 .. "" == "10") --> true
这种转换始终会有效的。
2.5 表(tables)
Lua
的tables实现了关联数组,关联数组指不仅可以通过数字检索数据,还可以通过别的类型的值检索数据.Lua中除了nil外的类型都可以作为
tables的索引下标.tables可以进行动态内存分配。tables是Lua主要的也是唯一的数据结构,我们可以通过他实现传统数组, 符号表,
集合, 记录(pascal), 队列, 以及其他的数据结构.
Lua的包(package)也是使用tables来描述的,io.read意味着调用io包中的read函数,对Lua而言意味着使用字符串read作
为下标访问io表.
Lua中tables不是变量也不是值,而是对象。如果你
对数组的印象来自于C或者Pascal,就需要你改变一下观念。
你可以把tables当作自动分配的对象,程序中只需要操纵表的引用(指针)即可.Lua中不需要声明表(也无法声明),使用最简单的{}表达式语句即可
创建表.
- a = {} -- 创建一个表,并赋值给
a- k = "x"
- a[k] = 10 -- 加入一个成员, key="x" and value=10
- a[20] = "great" --加入一个成员,key=20 and value="great"
- print(a["x"]) --> 10
- k = 20
- print(a[k]) --> "great"
- a["x"] = a["x"] + 1 -- 改变 "x" 成员的值
- print(a["x"]) --> 11
表是匿名的,意味着表和持有表的变量没有必然的关系。
- a = {}
- a["x"] = 10
- b = a -- b和a引用同一个
table- print(b["x"]) --> 10
- b["x"] = 20
- print(a["x"]) --> 20
- a = nil -- 现在,只有b引用这个table了
- b = nil -- 现在没有变量再引用这个table
当程序中不再引用表时,这个表将被删除,内存可以重新被利用.表可以使用不同的索引类型存储值。索引大小随着表中元素个数增加而增加。
- a = {} -- empty table
- -- create 1000 new entries
- for i=1,1000 do a[i] = i*2 end
- print(a[9]) --> 18
- a["x"] = 10
- print(a["x"]) --> 10
- print(a["y"]) --> nil
注意最后一行,表对应的域没有被初始化,所以为nil,这和全局变量一样。你也可以通过将某个元素赋值为nil来删除这个元素。实际上,Lua的全局变量存储正是使用表来存储的。更多关于这个问题的讨论见第14章。
可以使用域名作为索引下标访问表中元素,Lua也支持a.name代替a["name"],所以我们可以用更清晰的方式重写上面的例子:
- a.x = 10 -- same as a["x"] = 10
- print(a.x) -- same as print(a["x"])
- print(a.y) -- same as print(a["y"])
两种方式可以混合使用,对于Lua来说,两种方式相同,但对于读者来说单一的风格更易理解。
常见的错误:混淆a.x 和a[x];第一种表示a["x"],即访问域为字符串"x"的表中元素,第二种表示使用变量x作为索引下标访问表中元素。
- a = {}
- x = "y"
- a[x] = 10 -- put 10 in field "y"
- print(a[x]) --> 10 -- value of field "y"
- print(a.x) --> nil -- value of field "x" (undefined)
- print(a.y) --> 10 -- value of field "y"
只要使用整数作为索引下标就可以表示传统的数组了,不需要指定数组大小:
- -- read 10 lines storing them in a table
- a = {}
- for i=1,10 do
- a[i] = io.read()
- end
当遍历数组元素时,第一个没有初始化的元素返回nil,可以用这个当作数组下标的边界标志.可以用下面的代码打印出上个例子读入的行:
- -- print the lines
- for i,line in ipairs(a) do
- print(line)
- end
既然可以使用任意值作为表的下标,你可以以任何数字作为数组下标的开始,但是Lua中一般以1开始而不是0(和c语言不同).Lua标准库也是以这个设计的.
有一点需要特别注意,否则你的代码中可能引入很多难以发现的bug.因为我们可以使用任意类型的值作为索引下标,要注意:number 0 和string "0"是不同的,同样strings "+1", "01", and "1"也是不同的.
- i = 10; j = "10"; k = "+10"
- a = {}
- a[i] = "one value"
- a[j] = "another value"
- a[k] = "yet another value"
- print(a[j]) --> another value
- print(a[k]) --> yet another value
- print(a[tonumber(j)]) --> one value
- print(a[tonumber(k)]) --> one value
当对你检索的类型有疑问时,请使用显示类型转换。
2.6 函数(Functions)
函
数是第一类值(和其他变量相同),意味着函数可以存储在变量中,可以作为函数的参数,也可以作为函数的返回值。这个特性给了语言很大的灵活性:一个程序可
以重新定义函数增加新的功能,或者为了避免运行不可靠代码而删除掉一些函数,另外这个特性在Lua实现面向对象中也起了重要作用。(详细讨论见第16章)
Lua可以调用lua或者C实现的函数,Lua所有标准库都是用C实现的.标准库包括string库,table库,I/O库,OS库,算术库,debug库。
2.7 用户数据和线程(Userdata and Threads)
userdata 可以将C数据存放在Lua变量中,userdata 在Lua中除了赋值和相等比较外没有预定义的操作.userdata 用来描述应用程序或者使用C实现的库创建的新类型.例如:标准I/O库用来描述文件.下面在C API章节中我们将详细讨论。
在第九章讨论协同操作的时候,我们介绍线程。