第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來表示文件。