?
This document uses PHP Chinese website manual Release
本節(jié)介紹該語言的基本概念。
Lua是一種動態(tài)類型的語言。這意味著變量沒有類型; 只有值才有。該語言中沒有類型定義。所有的值都有自己的類型。
Lua中的所有值都是第一類對象。這意味著所有值都可以存儲在變量中,作為參數傳遞給其他函數,并作為結果返回。
Lua有八種基本類型:nil,boolean,number,string,function,userdata,thread和table。類型nil有一個單一的值,nil,其主要屬性是不同于任何其他值; 它通常代表沒有有用的價值。布爾類型有兩個值,false和true。nil和false為假; 任何其他值都是真。類型number代表整數和實數(浮點數)。類型string表示不可變的字節(jié)序列。Lua是8位清理:字符串可以包含任何8位值,包括嵌入的零(' \0
')。Lua也是編碼不可知的; 它不會假定字符串的內容。
類型編號使用兩個內部表示,或兩個子類型,一個稱為整數,另一個稱為浮點。Lua明確規(guī)定了每個表示的使用時間,但它也根據需要自動進行轉換(請參閱第3.4.3節(jié))。因此,程序員可能會選擇忽略整數和浮點數之間的差異,或者完全控制每個數字的表示。標準Lua使用64位整數和雙精度(64位)浮點數,但您也可以編譯Lua,以便使用32位整數和/或單精度浮點數(32位)。對于小型機器和嵌入式系統,整數和浮點數均為32位的選項特別具有吸引力。(請參閱LUA_32BITS
文件中的宏luaconf.h
。)
Lua可以調用(和操作)用Lua編寫的函數和用C編寫的函數(參見§3.4.10)。兩者都由類型函數表示。
提供類型userdata以允許將任意C數據存儲在Lua變量中。用戶數據值表示一塊原始內存。有兩種用戶數據:完整用戶數據,它是由Lua管理的一塊內存的對象,以及輕量級用戶數據,它只是一個C指針值。用戶數據在Lua中沒有預定義的操作,除了賦值和身份測試。通過使用metatables,程序員可以定義完整的用戶數據值的操作(請參閱§2.4)。用戶數據值不能在Lua中創(chuàng)建或修改,只能通過C API。這保證了主機程序擁有的數據的完整性。
類型線程表示獨立的執(zhí)行線程,用于實現協程(請參閱第2.6節(jié))。Lua線程與操作系統線程無關。Lua支持所有系統上的協同程序,甚至是那些原生不支持線程的協程。
類型表實現了關聯數組,也就是說,不僅可以用數字對數組進行索引,而且還可以使用除nil和NaN 之外的任何Lua值。(不是數字是用于表示未定義或不可表示的數字結果的特殊值,例如0/0
。)表可以是異構的 ; 也就是說,它們可以包含所有類型的值(nil除外)。任何值為零的鍵都不被視為表格的一部分。相反,不屬于表的任何鍵都有相關的值nil。
表是Lua中唯一的數據結構化機制; 它們可以用來表示普通數組,列表,符號表,集合,記錄,圖形,樹等。為了表示記錄,Lua使用字段名稱作為索引。該語言通過提供a.name
作為語法糖來支持這種表示a["name"]
。在Lua中創(chuàng)建表格有幾種方便的方法(參見§3.4.9)。
與索引一樣,表字段的值可以是任何類型。特別是,因為函數是第一類值,所以表字段可以包含函數。因此表格也可以攜帶方法(見§3.4.11)。
表格的索引遵循語言中原始平等的定義。表達式a[i]
和a[j]
表示同一個表元素當且僅當i
和j
原始相等(即沒有元方法時相等)。具體而言,具有整數值的浮點數等于它們各自的整數(例如,1.0 == 1
)。為了避免歧義,任何用作鍵的積分值的浮點數都被轉換為相應的整數。例如,如果您編寫a[2.0] = true
,插入表中的實際密鑰將是整數2
。(另一方面,2和“ 2
”是不同的Lua值,因此表示不同的表項。)
表,函數,線程和(完整)用戶數據值都是對象:變量實際上不包含這些值,只包含對它們的引用。賦值,參數傳遞和函數返回總是處理對這些值的引用; 這些操作并不意味著任何形式的副本。
庫函數type
返回一個描述給定值類型的字符串(請參閱第6.1節(jié))。
正如將在§3.2和§3.3.3中討論的那樣,任何對自由名稱的引用(也就是說,沒有綁定到任何聲明的名稱)都會var
被語法轉換為_ENV.var
。而且,每個塊都被編譯在一個名為外部局部變量_ENV
(請參閱§3.3.2)的范圍內,所以_ENV
它本身永遠不會是塊中的空閑名稱。
盡管存在這個外部_ENV
變量和自由名稱的翻譯,但它_ENV
是一個完全正規(guī)的名稱。特別是,您可以使用該名稱定義新的變量和參數。每個對自由名稱的引用都_ENV
遵循Lua的通常可見性規(guī)則(參見§3.5),使用該程序中當前可見的內容。
任何用作值的表_ENV
稱為環(huán)境。
Lua擁有一個稱為全球環(huán)境的杰出環(huán)境。該值保存在C注冊表中的特殊索引處(請參閱第4.5節(jié))。在Lua中,全局變量_G
用這個相同的值進行初始化。(_G
從未在內部使用。)
當Lua加載一個塊時,其_ENV
最大值的默認值是全局環(huán)境(請參閱參考資料load
)。因此,默認情況下,Lua代碼中的自由名稱是指全局環(huán)境中的條目(因此它們也被稱為全局變量)。而且,所有標準庫都在全球環(huán)境中加載,并且在那里有一些功能在該環(huán)境中運行。您可以使用load
(或loadfile
)加載具有不同環(huán)境的塊。(在C中,您必須加載塊,然后更改其第一個upvalue的值。)
由于Lua是一種嵌入式擴展語言,所有Lua動作都從主機程序中的C代碼開始,從Lua庫中調用一個函數。(當你使用Lua standalone時,lua
應用程序就是宿主程序。)當編譯或執(zhí)行Lua塊時發(fā)生錯誤時,控制權返回給主機,主機可以采取適當的措施(如打印錯誤信息)。
通過調用error
函數,Lua代碼可以明確地產生一個錯誤。如果您需要在Lua中發(fā)現錯誤,則可以使用pcall
或xpcall
在保護模式下調用給定的函數。
每當出現錯誤時,錯誤對象(也稱為錯誤消息)將傳播有關錯誤的信息。Lua本身只會生成其錯誤對象是字符串的錯誤,但程序可能會將錯誤對象的任何值生成錯誤。由Lua程序或其主機來處理這些錯誤對象。
當你使用xpcall
或者時lua_pcall
,你可能會給一個消息處理程序以在發(fā)生錯誤時被調用。該函數與原始錯誤對象一起調用并返回一個新的錯誤對象。在錯誤展開堆棧之前調用它,以便它可以收集有關錯誤的更多信息,例如通過檢查堆棧并創(chuàng)建堆?;厮?。該消息處理程序仍受受保護呼叫的保護; 所以,消息處理程序中的錯誤將再次調用消息處理程序。如果這個循環(huán)持續(xù)太久,Lua會打破它并返回一個合適的消息。(消息處理程序僅為常規(guī)運行時錯誤而調用,不會在調用終結器時調用內存分配錯誤或錯誤。)
Lua中的每個值都可以有一個metatable。這個metatable是一個普通的Lua表,它定義了特定操作下原始值的行為。您可以通過在其metatable中設置特定字段來更改操作行為的某些方面。例如,當一個非數字值是一個加法的操作數時,Lua會__add
在值的metatable 的字段“ ”中檢查一個函數。如果它找到一個,Lua調用這個函數來執(zhí)行加法。
metatable中每個事件的關鍵字是一個事件名稱前面加上兩個下劃線的字符串; 相應的值稱為metamethods。在前面的例子中,鍵是“ __add
”,metamethod是執(zhí)行加法的函數。
您可以使用該getmetatable
函數查詢任何值的metatable 。Lua使用原始訪問查詢metatables中的元方法(請參閱參考資料rawget
)。因此,為了檢索ev
對象中的事件的metamethod o
,Lua會執(zhí)行與以下代碼等效的操作:
rawget(getmetatable(o) or {}, "__ev")
您可以使用該setmetatable
函數替換表格的元表。你不能從Lua代碼中改變其他類型的metatable(除了使用調試庫(§6.10)); 你應該使用C API。
表和完整的userdata具有單獨的metatables(盡管多個表和userdata可以共享它們的metatables)。所有其他類型的值每種類型共享一個單一的metatable; 也就是說,對于所有數字都有一個單一的metatable,對于所有的字符串都有一個metatable,默認情況下,一個值沒有metatable,但是字符串庫為字符串類型設置了一個metatable(見第6.4節(jié))。
metatable控制對象在算術運算,按位運算,順序比較,連接,長度操作,調用和索引中的表現。metatable也可以定義一個函數,當用戶數據或表被垃圾收集時(§2.5)被調用。
對于一元運算符(否定,長度和位非),元方法計算并用一個等于第一個操作數的虛擬第二操作數調用。這個額外的操作數僅僅是為了簡化Lua的內部操作(通過使這些操作符像二進制操作一樣)并且在將來的版本中可能會被刪除。(對于大多數用途,這個額外的操作數是不相關的。)
下面給出了由metatables控制的事件的詳細列表。每個操作都由其相應的密鑰標識。
__add
:加法(+
)操作。如果添加的操作數不是一個數字(也不是一個數字的強制字符串),Lua會嘗試調用一個metamethod。首先,Lua會檢查第一個操作數(即使它是有效的)。如果該操作數沒有定義metamethod __add
,那么Lua將檢查第二個操作數。如果Lua可以找到一個元方法,它將以兩個操作數作為參數來調用元方法,并且調用的結果(調整為一個值)是操作的結果。否則,它會引發(fā)錯誤。
__sub
:減法(-
)操作。與添加操作類似的行為。
__mul
:multiplication(*
)操作。與添加操作類似的行為。
__div
:division(/
)操作。與添加操作類似的行為。
__mod
:模(%
)操作。與添加操作類似的行為。
__pow
:指數運算(^
)。與添加操作類似的行為。
__unm
:否定(一元-
)操作。與添加操作類似的行為。
__idiv
:地板分區(qū)(//
)操作。與添加操作類似的行為。
__band
:按位AND(&
)操作。類似于加法操作的行為,除非Lua會嘗試元方法,如果任何操作數既不是整數也不是強制為整數的值(請參閱第3.4.3節(jié))。
__bor
:按位OR(|
)操作。類似于按位AND操作的行為。
__bxor
:按位異或(二進制~
)操作。類似于按位AND操作的行為。
__bnot
:按位NOT(一元~
)操作。類似于按位AND操作的行為。
__shl
:按位左移(<<
)操作。類似于按位AND操作的行為。
__shr
:按位右移(>>
)操作。類似于按位AND操作的行為。
__concat
:concatenation(..
)操作。類似于加法操作的行為,除非Lua會嘗試一個元方法,如果任何操作數既不是字符串也不是數字(它總是對字符串進行強制處理)。
__len
:length(#
)操作。如果對象不是字符串,Lua會嘗試它的元方法。如果有一個metamethod,Lua將該對象作為參數進行調用,并且調用的結果(始終調整為一個值)是操作的結果。如果沒有元方法,但對象是一個表,那么Lua將使用表長度操作(請參閱第3.4.7節(jié))。否則,Lua會產生一個錯誤。
__eq
:equal(==
)操作。行為類似于加法操作,除非Lua只會在比較的值都是表或全部用戶數據并且它們不是基本相等時才嘗試metamethod。調用的結果總是轉換為布爾值。
__lt
:小于(<
)操作。類似于加法操作的行為,除非Lua僅在比較的值既不是數字也不是兩個字符串時才嘗試metamethod。調用的結果總是轉換為布爾值。
__le
:不等于(<=
)的操作。與其他操作不同,不太平等的操作可以使用兩個不同的事件。首先,Lua __le
在兩個操作數中尋找metamethod,就像在少于操作中一樣。如果它找不到這樣的metamethod,那么它會嘗試__lt
metamethod,假設它a <= b
相當于not (b < a)
。與其他比較運算符一樣,結果始終是布爾值。(這個__lt
事件的使用可以在將來的版本中被刪除;它也比真實的__le
元方法慢)。
__index
:索引訪問table[key]
。這個事件發(fā)生在table
不是表格或者key
不存在時table
。metamethod被抬頭table
。盡管名稱,這個事件的metamethod可以是一個函數或表。如果它是一個函數,它被稱為與table
和key
作為參數,并且該呼叫(調整到一個值)的結果是該操作的結果。如果它是一個表格,最終結果就是將此表格編入索引的結果key
。(這個索引是規(guī)則的,不是原始的,因此可以觸發(fā)另一個元方法。)
__newindex
:索引分配table[key] = value
。和索引事件一樣,這個事件發(fā)生在table
不是表格或者key
不存在時table
。metamethod被抬頭table
。與索引一樣,此事件的元方法可以是函數也可以是表格。如果它是一個功能,它被稱為用table
,key
以及value
作為參數。如果它是一個表,Lua會使用相同的鍵和值對此表執(zhí)行索引分配。(這個任務是規(guī)則的,不是原始的,因此可以觸發(fā)另一個元方法。)每當有一個__newindex
元方法時,Lua就不執(zhí)行原語分配。(如有必要,metamethod本身可以打電話rawset
來完成作業(yè)。)
__call
:通話操作func(args)
。當Lua嘗試調用非函數值(即,func
不是函數)時,會發(fā)生此事件。metamethod被抬頭func
。如果存在,metamethod被稱為func
第一個參數,然后是原始調用(args
)的參數。調用的所有結果都是操作的結果。(這是允許多個結果的唯一元方法。)
在將其設置為某個對象的metatable之前,將所有需要的metamethods添加到表中是一種很好的做法。特別是,__gc
metamethod只有在遵循這個順序時才起作用(見§2.5.1)。
由于metatables是常規(guī)表,因此它們可以包含任意字段,而不僅僅是上面定義的事件名稱。標準庫中的一些功能(例如tostring
)使用metatables中的其他字段來實現自己的目的。
Lua執(zhí)行自動內存管理。這意味著您不必擔心為新對象分配內存或在不再需要對象時釋放內存。Lua通過運行垃圾回收器來自動管理內存以收集所有死對象(即,不再可從Lua訪問的對象)。Lua使用的所有內存都受自動管理:字符串,表格,用戶數據,函數,線程,內部結構等。
Lua實現了一個增量式標記和掃描收集器。它使用兩個數字來控制其垃圾收集周期:垃圾收集器暫停和垃圾收集器步驟乘數。兩者均以百分點為單位(例如,值100表示內部值為1)。
垃圾收集器暫停控制收集器在開始新循環(huán)之前等待的時間。較大的值使收集器不那么積極。小于100的值意味著收集器不會等待開始新的循環(huán)。值為200意味著收集器在開始新周期之前等待使用的總內存翻倍。
垃圾收集器步驟乘法器控制收集器相對于內存分配的相對速度。較大的值使收集器更具侵略性,但也會增加每個增量步驟的大小。您不應該使用小于100的值,因為它們會使收集器太慢,并可能導致收集器永遠不會完成一個循環(huán)。默認值是200,這意味著收集器以內存分配速度的“兩倍”運行。
如果您將步驟乘數設置為非常大的數字(大于程序可以使用的最大字節(jié)數的10%),則收集器的行為就像是一個停止世界的收集器。如果您將暫停設置為200,那么收集器的行為與舊版本的Lua版本相同,每當Lua將其內存使用量翻倍時,會執(zhí)行完整的收集。
您可以通過lua_gc
在C或collectgarbage
Lua中調用來更改這些數字。您也可以使用這些功能直接控制收集器(例如,停止并重新啟動它)。
您可以為表格設置垃圾收集器元方法,并使用C API為完整的用戶數據設置(請參閱第2.4節(jié))。這些metamethods也被稱為finalizer。終結器允許您將Lua的垃圾回收與外部資源管理(如關閉文件,網絡或數據庫連接,或釋放自己的內存)進行協調。
對于要收集的對象(表或用戶數據)進行最終確定時,您必須將其標記為最終確定。在設置其元數據時,您將對象標記為最終化,并且metatable具有由字符串“ __gc
” 索引的字段。請注意,如果您在沒有__gc
字段的情況下設置了metatable ,然后在metatable中創(chuàng)建該字段,則該對象將不會標記為最終化。
當一個標記的對象變成垃圾時,垃圾收集器不會立即收集它。相反,Lua把它列入清單。收集完成后,Lua會瀏覽該列表。對于列表中的每個對象,它檢查對象的__gc
元方法:如果它是一個函數,則Lua將該對象作為其單個參數進行調用; 如果metamethod不是函數,那么Lua會忽略它。
在每個垃圾收集周期結束時,以相反的順序調用對象的終結器,以便在該周期中收集的對象被標記為最終確定; 也就是說,要被調用的第一個終結器是與程序中最后標記的對象相關聯的終結器。每個終結器的執(zhí)行可能會在執(zhí)行常規(guī)代碼的任何時候發(fā)生。
因為所收集的對象必須仍然被終結使用,該對象(并且只能通過其可訪問的其他對象),必須復活由Lua中。通常,這種復活是暫時的,并且在下一次垃圾收集循環(huán)中釋放對象內存。但是,如果終結器將對象存儲在某個全局位置(例如全局變量)中,則復活是永久的。此外,如果終結器再次將終結對象標記為終結對象,則在下一個對象無法訪問的周期中將再次調用終結器。在任何情況下,只有在對象不可訪問且未標記為完成的GC循環(huán)中才釋放對象內存。
當你關閉一個狀態(tài)時(參見lua_close
),Lua調用所有標記為最終化的對象的終結器,按照它們被標記的相反順序。如果任何終結器在該階段標記用于收集的對象,則這些標記不起作用。
一個弱表是一個表,它的元素是弱引用。垃圾收集器忽略弱引用。換句話說,如果對象的唯一引用是弱引用,那么垃圾收集器將收集該對象。
弱表可以具有弱鍵,弱值或兩者。具有較弱值的表允許收集其值,但會阻止收集其密鑰。具有弱鍵和弱值的表格允許集合鍵和值。在任何情況下,如果收集密鑰或值,則將整個對從表中移除。桌子的弱點__mode
由其metatable字段控制。如果該__mode
字段是包含字符' k
' 的字符串,則表中的鍵很弱。如果__mode
包含“ v
',表中的值很弱。
具有弱鍵和強值的表格也被稱為ephemeron表格。在ephemeron表中,只有在密鑰可達的情況下才認為該值可訪問。特別是,如果只有一個鍵的引用通過它的值,那么這個鍵被刪除。
只有在下一個收集周期中,表中任何弱點的變化才會生效。特別是,如果您將弱點更改為更強大的模式,則Lua可能會在更改生效之前從該表中收集一些項目。
只有具有顯式構造的對象才會從弱表中刪除。值(如數字和亮C函數)不受垃圾回收處理,因此不會從弱表中刪除(除非收集其相關值)。雖然字符串需要垃圾回收,但它們沒有明確的構造,因此不會從弱表中刪除。
復活對象(即,正在完成的對象和只能通過正在完成的對象才可訪問的對象)在弱表中具有特殊行為。在運行終結器之前,它們將從弱值中移除,但是在運行終結器之后,只有在下一個集合中才從弱鍵中移除它們,而這些對象實際上已被釋放。此行為允許終結器通過弱表訪問與對象關聯的屬性。
如果收集周期中的復活對象中存在弱表,那么在下一個周期之前可能無法正確清除。
Lua支持協程,也稱為協作多線程。Lua中的協程表示一個獨立的執(zhí)行線程。然而,與多線程系統中的線程不同,協程只通過顯式調用yield函數來暫停執(zhí)行。
通過調用創(chuàng)建協程coroutine.create
。它的唯一參數是一個函數,它是協程的主要功能。該create
函數只創(chuàng)建一個新的協程并返回一個句柄(一個線程類型的對象); 它不啟動協程。
你通過調用來執(zhí)行協程coroutine.resume
。當您第一次調用時coroutine.resume
,將第一個參數作為返回的線程傳遞coroutine.create
,協程通過調用其主函數來開始執(zhí)行。傳遞給它的額外參數coroutine.resume
作為參數傳遞給該函數。協程開始運行后,它會一直運行,直到它終止或退出。
協程可以通過兩種方式終止它的執(zhí)行:通常,當主函數返回(顯式或隱式地,在最后一條指令之后)時; 并且在異常情況下,如果存在無保護的錯誤。在正常終止的情況下,coroutine.resume
返回真,再加上協程主函數返回的任何值。如果有錯誤,則coroutine.resume
返回false加錯誤對象。
協程通過調用產生收益coroutine.yield
。當協程產生時,coroutine.resume
即使產量發(fā)生在嵌套函數調用(即,不在主函數中,而是在由主函數直接或間接調用的函數中),也立即產生相應的返回。在yield的情況下,coroutine.resume
也返回true,加上傳遞給它的值coroutine.yield
。下一次恢復同一個協程時,它會繼續(xù)執(zhí)行它的執(zhí)行點,并調用coroutine.yield
返回傳遞給它的額外參數coroutine.resume
。
類似coroutine.create
的coroutine.wrap
功能還創(chuàng)建了一個協程,但不是返回協同程序本身,它返回一個函數,調用它時,恢復協程。傳遞給這個函數的任何參數都是作為額外的參數coroutine.resume
。coroutine.wrap
返回由coroutine.resume
第一個返回的所有值(布爾錯誤代碼)。不像coroutine.resume
,coroutine.wrap
不會發(fā)現錯誤; 任何錯誤都會傳播給調用者。
作為協程的工作原理的例子,請考慮以下代碼:
function foo (a) print("foo", a) return coroutine.yield(2*a)end co = coroutine.create(function (a,b) print("co-body", a, b) local r = foo(a+1) print("co-body", r) local r, s = coroutine.yield(a+b, a-b) print("co-body", r, s) return b, "end"end)print("main", coroutine.resume(co, 1, 10))print("main", coroutine.resume(co, "r"))print("main", coroutine.resume(co, "x", "y"))print("main", coroutine.resume(co, "x", "y"))
當你運行它時,它會產生以下輸出:
co-body 1 10foo 2main true 4co-body r main true 11 -9co-body x y main true 10 end main false cannot resume dead coroutine
您還可以創(chuàng)建和操作通過C API協同程序:看功能lua_newthread
,lua_resume
和lua_yield
。