1 簡(jiǎn)介由 clean C 實(shí)現(xiàn)。需要被宿主程序調(diào)用,可以注入 C 函數(shù)。 2 語(yǔ)法采用基于 BNF 的語(yǔ)法規(guī)則。 2.1 語(yǔ)法約定Lua 對(duì)大小寫敏感。 2.1.1 保留關(guān)鍵字C 語(yǔ)言中沒(méi)有的關(guān)鍵字有: and elseif function
in nil local not or
repeat then until
規(guī)范:全局變量以下劃線開頭。 2.1.2 操作符C 語(yǔ)言中沒(méi)有的操作符: ^
~=
Lua 中沒(méi)有的操作符: +=
-=
2.1.3 字符串定義采用轉(zhuǎn)義符:通過(guò)轉(zhuǎn)義符表示那些有歧義的字符字符表示 a
\97
\049
其他轉(zhuǎn)義符表示 \\n
\n
注意數(shù)字字符必須是三位。其他字符則不能超過(guò)三位。 采用長(zhǎng)括號(hào):長(zhǎng)括號(hào)內(nèi)的所有內(nèi)容都作為普通字符處理。[[]]
[==[]==]
2.2 值與類型Lua 是動(dòng)態(tài)語(yǔ)言,變量沒(méi)有類型,值才有。值自身攜帶類型信息。 Lua 有八種基本數(shù)據(jù)類型:nil, boolean, number, string, function, userdata, thread, table 。 僅 nil 和 false 導(dǎo)致條件為假,其他均為真。 userdata 類型變量用于保存 C 數(shù)據(jù)。 Lua 只能對(duì)該類數(shù)據(jù)進(jìn)行使用,而不能進(jìn)行創(chuàng)建或修改,保證宿主程序完全掌握數(shù)據(jù)。
thread 用于實(shí)現(xiàn)協(xié)程(coroutine)。
table 用于實(shí)現(xiàn)關(guān)聯(lián)數(shù)組。table 允許任何類型的數(shù)據(jù)做索引,也允許任何類型做 table 域中的值(前述
任何類型 不包含 nil)。table 是 Lua 中唯一的數(shù)據(jù)結(jié)構(gòu)。 由于函數(shù)也是一種值,所以 table 中可以存放函數(shù)。
function, userdata, thread, table 這些類型的值都是對(duì)象。這些類型的變量都只是保存變量的引用,并且在進(jìn)行賦值,參數(shù)傳遞,函數(shù)返回等操作時(shí)不會(huì)進(jìn)行任何性質(zhì)的拷貝。
庫(kù)函數(shù) type() 返回變量的類型描述信息。 2.2.1 強(qiáng)制轉(zhuǎn)換Lua 提供數(shù)字與字符串間的自動(dòng)轉(zhuǎn)換。 可以使用 format 函數(shù)控制數(shù)字向字符串的轉(zhuǎn)換。 2.3 變量變量有三種類型:全局變量、局部變量、表中的域。 函數(shù)外的變量默認(rèn)為全局變量,除非用 local 顯示聲明。函數(shù)內(nèi)變量與函數(shù)的參數(shù)默認(rèn)為局部變量。 局部變量的作用域?yàn)閺穆暶魑恢瞄_始到所在語(yǔ)句塊結(jié)束(或者是直到下一個(gè)同名局部變量的聲明)。 變量的默認(rèn)值均為 nil。 a = 5
local b = 5
function joke()
c = 5
local d = 6
end
print(c,d)
do
local a = 6
b = 6
print(a,b);
end
print(a,b)
方便標(biāo)記,--> 代表前面表達(dá)式的結(jié)果。 2.3.1 索引對(duì) table 的索引使用方括號(hào) [] 。Lua使用語(yǔ)法糖提供 . 操作。 t[i]
t.i
gettable_event(t,i)
2.3.2 環(huán)境表所有全局變量放在一個(gè)環(huán)境表里,該表的變量名為 _env 。對(duì)某個(gè)全局變量 a 的訪問(wèn)即 _env.a (_env_ 只是為了方便說(shuō)明)。 每個(gè)函數(shù)作為變量持有一個(gè)環(huán)境表的引用,里面包含該函數(shù)可調(diào)用的所有變量。 子函數(shù)會(huì)從父函數(shù)繼承環(huán)境表。 可以通過(guò)函數(shù) getfenv / setfenv 來(lái)讀寫環(huán)境表。 2.4 語(yǔ)句 | statement支持賦值,控制結(jié)構(gòu),函數(shù)調(diào)用,還有變量聲明。 不允許空的語(yǔ)句段,因此 ;; 是非法的。 2.4.1 語(yǔ)句組 | chunckschunck ::= {stat[';']}
([';'] 應(yīng)該是表示語(yǔ)句組后面 ; 是可選項(xiàng)。) 2.4.2 語(yǔ)句塊 | blocksblock ::= chunck
stat ::= do block end
可以將一個(gè)語(yǔ)句塊顯式地寫成語(yǔ)句組,可以用于控制局部變量的作用范圍。 2.4.3 賦值 | assignmentLua 支持多重賦值。 多重賦值時(shí),按序?qū)⒂疫叺谋磉_(dá)式的值賦值給左值。右值不足補(bǔ) nil,右值多余舍棄。 b = 1
a,b = 4
+++ Lua 在進(jìn)行賦值操作時(shí),會(huì)一次性把右邊的表達(dá)式都計(jì)算出來(lái)后進(jìn)行賦值。 i = 5
i,a[i] = i+1, 7
特別地,有 x,y = y,x
+++ 對(duì)全局變量以及表的域的賦值操作含義可以在元表中更改。 2.4.4 控制結(jié)構(gòu)條件語(yǔ)句if [exp]
[block]
elseif [exp]
[block]
else
[block]
end
循環(huán)語(yǔ)句while [exp]
[block]
end
+++ repeat
[block]
until [exp]
注意,由于 repeat 語(yǔ)句到 until 還未結(jié)束,因此在 until 之后的表達(dá)式中可以使用 block 中定義的局部變量。 例如: a = 1
c = 5
repeat
b = a + c
c = c * 2
until b > 20
print(c) --> 40
+++ break 和 return
break 和 return 只能寫在語(yǔ)句塊的最后一句,如果實(shí)在需要寫在語(yǔ)句塊中間,那么就在兩個(gè)關(guān)鍵詞外面包圍do end 語(yǔ)句塊。
do break end
2.4.5 For 循環(huán)for 循環(huán)的用法比較多,單獨(dú)拎出來(lái)講。
for 中的表達(dá)式會(huì)在循環(huán)開始前一次性求值,在循環(huán)過(guò)程中不再更新。
數(shù)字形式for [Name] = [exp],[exp],[exp] do [block] end
三個(gè) exp 分別代表初值,結(jié)束值,步進(jìn)。exp 的值均需要是一個(gè)數(shù)字。 第三個(gè) exp 默認(rèn)為 1,可以省略。 a = 0
for i = 1,6,2 do
a = a + i
end
等價(jià)于 int a = 0;
for (int i = 1; i <= 6;i += 2){
a += i;
}
迭代器形式迭代器形式輸出一個(gè)表時(shí),如果表中有函數(shù),則輸出的順序及個(gè)數(shù)不確定(筆者測(cè)試得出的結(jié)果,具體原因未知)。 迭代器形式的 for 循環(huán)的實(shí)質(zhì)
function mypairs(t)
function iterator(t,i)
i = i + 1
i = t[i] and i
return i,t[i]
end
return iterator,t,0
end
t = {[1]="1",[2]="2"}
do
local f, s, var = mypairs(t)
while true do
local var1, var2 = f(s, var)
var = var1
if var == nil then break end
print(var1,var2)
end
end
for var1,var2 in mypairs(t) do
print(var1,var2)
end
數(shù)組形式ary = {[1]=1,[2]=2,[5]=5}
for i,v in ipairs(ary) do
print(v) --> 1 2
end
從1開始,直到數(shù)值型下標(biāo)結(jié)束或者值為 nil 時(shí)結(jié)束。 表遍歷table = {[1]=1,[2]=2,[5]=5}
for k,v in pairs(table) do
print(v) --> 1 2 5
end
遍歷整個(gè)表的鍵值對(duì)。 關(guān)于迭代器的更多內(nèi)容,可參考Lua 迭代器和泛型 for。 2.5 表達(dá)式2.5.1 數(shù)學(xué)運(yùn)算操作符% 操作符
Lua 中的 % 操作符與 C 語(yǔ)言中的操作符雖然都是取模的含義,但是取模的方式不一樣。 在 C 語(yǔ)言中,取模操作是將兩個(gè)操作數(shù)的絕對(duì)值取模后,在添加上第一個(gè)操作數(shù)的符號(hào)。 而在 Lua 中,僅僅是簡(jiǎn)單的對(duì)商相對(duì)負(fù)無(wú)窮向下取整后的余數(shù)。 +++ 在 C 中, a1 = abs(a);
b1 = abs(b);
c = a1 % b1 = a1 - floor(a1/b1)*b1;
a % b = (a >= 0) ? c : -c;
在 Lua 中, a % b == a - math.floor(a/b)*b
Lua 是直接根據(jù)取模定義進(jìn)行運(yùn)算。 C 則對(duì)取模運(yùn)算做了一點(diǎn)處理。 +++ 舉例: 在 C 中 int a = 5 % 6;
int b = 5 % -6;
int c = -5 % 6;
int d = -5 % -6;
printf("a,b,c,d");--5,5,-5,-5
在 Lua 中 a = 5 % 6
b = 5 % -6
c = -5 % 6
d = -5 % -6
x = {a,b,c,d}
for i,v in ipairs(x) do
print(i,v)
end
可以看到,僅當(dāng)操作數(shù)同號(hào)時(shí),兩種語(yǔ)言的取模結(jié)果相同。異號(hào)時(shí),取模結(jié)果的符號(hào)與數(shù)值均不相等。 在 Lua 中的取模運(yùn)算總結(jié)為:a % b,如果 a,b 同號(hào),結(jié)果取 a,b 絕對(duì)值的模;異號(hào),結(jié)果取 b 絕對(duì)值與絕對(duì)值取模后的差。取模后值的符號(hào)與 b 相同。 2.5.2 比較操作符比較操作的結(jié)果是 boolean 型的,非 true 即 false 。 支持的操作符有: < <= ~= == > >=
不支持 ! 操作符。 +++ 對(duì)于 == 操作,運(yùn)算時(shí)先比較兩個(gè)操作數(shù)的類型,如果不一致則結(jié)果為 false。此時(shí)數(shù)值與字符串之間并不會(huì)自動(dòng)轉(zhuǎn)換。 比較兩個(gè)對(duì)象是否相等時(shí),僅當(dāng)指向同一內(nèi)存區(qū)域時(shí),判定為 true 。· a = 123
b = 233
c = "123"
d = "123"
e = {1,2,3}
f = e
g = {1,2,3}
print(a == b) --> false
print(a == c) --> false -- 數(shù)字與字符串作為不同類型進(jìn)行比較
print(c == d) --> true
print(e == f) --> true -- 引用指向相同的對(duì)象
print(e == g) --> false -- 雖然內(nèi)容相同,但是是不同的對(duì)象
print(false == nil) --> false -- false 是 boolean,nil 是 nil 型
方便標(biāo)記,--> 代表前面表達(dá)式的結(jié)果。 +++ userdata 與 table 的比較方式可以通過(guò)元方法 eq 進(jìn)行改變。
大小比較中,數(shù)字和字符串的比較與 C 語(yǔ)言一致。如果是其他類型的值,Lua會(huì)嘗試調(diào)用元方法 lt 和 le 。 2.5.3 邏輯操作符and,or,not
僅認(rèn)為 false 與 nil 為假。 not
取反操作 not 的結(jié)果為 boolean 類型。(and 和 or 的結(jié)果則不一定為 boolean ) b = not a -- a 為 nil,b 為 true
c = not not a -- c 為 false
and
a and b ,如果 a 為假,返回 a ,如果 a 為真, 返回 b 。
注意,為什么 a 為假的時(shí)候要返回 a 呢?有什么意義?這是因?yàn)?nbsp;a 可能是 false 或者 nil ,這兩個(gè)值雖然都為假,但是是有區(qū)別的。 or
a or b ,如果 a 為假,返回 b ,如果 a 為真, 返回 a 。與 and 相反。
+++ 提示: 當(dāng)邏輯操作符用于得出一個(gè) boolean 型結(jié)果時(shí),不需要考慮邏輯運(yùn)算后返回誰(shuí)的問(wèn)題,因?yàn)檫壿嫴僮鞣牟僮鹘Y(jié)果符合原本的邏輯含義。 舉例 if (not (a > min and a < max)) then -- 如果 a 不在范圍內(nèi),則報(bào)錯(cuò)
error()
end
+++ 其他and 與 or 遵循短路原則,第二個(gè)操作數(shù)僅在需要的時(shí)候會(huì)進(jìn)行求值操作。
例子 a = 5
x = a or jjjj() -- 雖然后面的函數(shù)并沒(méi)有定義,但是由于不會(huì)執(zhí)行,因此不會(huì)報(bào)錯(cuò)。
print(a) -->5
print(x) -->5
通過(guò)上面這個(gè)例子,我們應(yīng)當(dāng)對(duì)于邏輯操作有所警覺(jué),因?yàn)檫@可能會(huì)引入一些未能及時(shí)預(yù)料到的錯(cuò)誤。 2.5.4 連接符.. 連接兩個(gè)字符串(或者數(shù)字)成為新的字符串。對(duì)于其他類型,調(diào)用元方法 concat 。
2.5.5 取長(zhǎng)度操作符#
對(duì)于字符串,長(zhǎng)度為字符串的字符個(gè)數(shù)。 對(duì)于表,通過(guò)尋找滿足t[n] 不是 nil 而 t[n+1] 為 nil 的下標(biāo) n 作為表的長(zhǎng)度。 ~~對(duì)于其他類型呢?~~ 例子-- 字符串取長(zhǎng)
print(
-- 表取長(zhǎng)
print(
print(
2.5.6 優(yōu)先級(jí)由低到高: or
and
< > <= >= ~= ==
..
+ -
* / %
not
^
冪運(yùn)算>單目運(yùn)算>四則運(yùn)算>連接符>比較操作符>and>or 2.5.7 Table 構(gòu)造Table 構(gòu)造的 BNF 定義 tableconstructor ::= `{′ [fieldlist] `}′
fieldlist ::= field {fieldsep field} [fieldsep]
field ::= `[′ exp `]′ `=′ exp | Name `=′ exp | exp
fieldsep ::= `,′ | `;′
舉例: a = {}
b = {["price"] = 5; cost = 4; 2+5}
c = { [1] = 2+5, [2] = 2, 8, price = "abc", ["cost"] = 4} -- b 和 c 構(gòu)造的表是等價(jià)的
print(b["price"]) --> 5
print(b.cost) --> 4
print(b[1]) --> 7 -- 未給出鍵值的,按序分配下標(biāo),下標(biāo)從 1 開始
print(c["price"]) --> abc
print(c.cost) --> 4
print(c[1]) --> 8
print(c[2]) --> 2
注意: - 未給出鍵值的,按序分配下標(biāo),下標(biāo)從 1 開始
- 如果表中有相同的鍵,那么以靠后的那個(gè)值作為鍵對(duì)應(yīng)的值
上面這兩條的存在使得上面的例子中 c1 的輸出值為 8。 +++ 如果表中有相同的鍵,那么以靠后的那個(gè)值作為鍵對(duì)應(yīng)的值。 a = {[1] = 5,[1] = 6}
+++ 如果表的最后一個(gè)域是表達(dá)式形式,并且是一個(gè)函數(shù),那么這個(gè)函數(shù)的所有返回值都會(huì)加入到表中。 a = 1
function order()
a = a + 1
return 1,2,3,4
end
b = {order(); a; order(); }
c = {order(); a; (order());}
print(b[1]) --> 1
print(b[2]) --> 2 -- 表中的值并不是一次把表達(dá)式都計(jì)算結(jié)束后再賦值的
print(b[3]) --> 1
print(b[4]) --> 2 -- 表達(dá)式形式的多返回值函數(shù)
print(
print(
2.5.8 函數(shù)定義函數(shù)是一個(gè)表達(dá)式,其值為 function 類型的對(duì)象。函數(shù)每次執(zhí)行都會(huì)被實(shí)例化。 Lua 中實(shí)現(xiàn)一個(gè)函數(shù)可以有以下三種形式。 f = function() [block] end
local f; f = function() [block] end
a.f = function() [block] end
Lua 提供語(yǔ)法糖分別處理這三種函數(shù)定義。 function f() [block] end
local function f() [block] end
function a.f() [block] end
+++ 上面 local 函數(shù)的定義之所以不是 local f = function() [block] end ,是為了避免如下錯(cuò)誤: local f = function()
print("local fun")
if i==0 then
f() -- 編譯錯(cuò)誤:attempt to call global 'f' (a nil value)
i = i + 1
end
end
函數(shù)的參數(shù)形參會(huì)通過(guò)實(shí)參來(lái)初始化為局部變量。 參數(shù)列表的尾部添加 ... 表示函數(shù)能接受不定長(zhǎng)參數(shù)。如果尾部不添加,那么函數(shù)的參數(shù)列表長(zhǎng)度是固定的。 f(a,b)
g(a,b,...)
h(a,...,b)
f(1)
f(1,2)
f(1,2,3)
g(1,2)
g(1,2,3)
g(1,f(4,5),3)
g(1,f(4,5))
+++ 還有一種形參為self的函數(shù)的定義方式: a.f = function (self, params) [block] end
其語(yǔ)法糖形式為: function a:f(params) [block] end
使用舉例: a = {name = "唐衣可俊"}
function a:f()
print(self.name)
end
a:f() --> 唐衣可俊 -- 如果這里使用 a.f(),那么 self.name 的地方會(huì)報(bào)錯(cuò) attempt to index local 'self';此時(shí)應(yīng)該寫為 a.f(a)
: 的作用在于函數(shù)定義與調(diào)用的時(shí)候可以少寫一個(gè) self 參數(shù)。這種形式是對(duì)方法 的模擬
2.5.9 函數(shù)調(diào)用Lua 中的函數(shù)調(diào)用的BNF語(yǔ)法如下: functioncall ::= prefixexp args
如果 prefixexp 的值的類型是 function, 那么這個(gè)函數(shù)就被用給出的參數(shù)調(diào)用。 否則 prefixexp 的元方法 "call" 就被調(diào)用, call 的第一個(gè)參數(shù)就是 prefixexp 的值,接下來(lái)的是 args 參數(shù)列表(參見(jiàn) 2.8 元表 | Metatable)。 函數(shù)調(diào)用根據(jù)是否傳入 self 參數(shù)分為 . 調(diào)用和 : 調(diào)用。 函數(shù)調(diào)用根據(jù)傳入?yún)?shù)的類型,可以分為參數(shù)列表調(diào)用、表調(diào)用、字符串調(diào)用。 [待完善] 2.5.10 函數(shù)閉包如果一個(gè)函數(shù)訪問(wèn)了它的外部變量,那么它就是一個(gè)閉包。 由于函數(shù)內(nèi)部的變量均為局部變量,外界無(wú)法對(duì)其進(jìn)行訪問(wèn)。這時(shí)如果外界想要改變局部變量的值,那么就可以使用閉包來(lái)實(shí)現(xiàn)這一目的。 具體的實(shí)現(xiàn)過(guò)程大致是這樣,函數(shù)內(nèi)部有能夠改變局部變量的子函數(shù),函數(shù)將這個(gè)子函數(shù)返回,那么外界就可以通過(guò)使用這個(gè)子函數(shù)來(lái)操作局部變量了。 例子:利用閉包來(lái)實(shí)現(xiàn)對(duì)局部變量進(jìn)行改變
function begin(i)
local cnt = i
return function ()
cnt = cnt + 1
return cnt
end
end
iterator = begin(2)
print(iterator())
print(iterator())
提示: 關(guān)于閉包的更多說(shuō)明可參考JavaScript 閉包是如何工作的?——StackOverflow 2.6 可視規(guī)則即變量的作用域,見(jiàn) 2.3 變量 部分。 2.7 錯(cuò)誤處理[待補(bǔ)充] 2.8 元表 | Metatable我們可以使用操作符對(duì) Lua 的值進(jìn)行運(yùn)算,例如對(duì)數(shù)值類型的值進(jìn)行加減乘除的運(yùn)算操作以及對(duì)字符串的連接、取長(zhǎng)操作等(在 2.5 表達(dá)式 這一節(jié)中介紹了許多類似的運(yùn)算)。元表正是定義這些操作行為的地方。 元表本質(zhì)上是一個(gè)普通 Lua 表。元表中的鍵用來(lái)指定操作,稱為“事件名”;元表中鍵所關(guān)聯(lián)的值稱為“元方法”,定義操作的行為。 2.8.1 事件名與元方法僅表(table)類型值對(duì)應(yīng)的元表可由用戶自行定義。其他類型的值所對(duì)應(yīng)的元表僅能通過(guò) Debug 庫(kù)進(jìn)行修改。 元表中的事件名均以兩條下劃線 __ 作為前綴,元表支持的事件名有如下幾個(gè): __index
__newindex
__call
__add
__sub
__mul
__div
__mod
__pow
__unm
__concat
__len
__eq
__lt
__le
還有一些其他的事件,例如 __tostring 和 __gc 等。 下面進(jìn)行詳細(xì)介紹。 2.8.2 元表與值每個(gè)值都可以擁有一個(gè)元表。對(duì) userdata 和 table 類型而言,其每個(gè)值都可以擁有獨(dú)立的元表,也可以幾個(gè)值共享一個(gè)元表。對(duì)于其他類型,一個(gè)類型的值共享一個(gè)元表。例如所有數(shù)值類型的值會(huì)共享一個(gè)元表。除了字符串類型,其他類型的值默認(rèn)是沒(méi)有元表的。 使用 getmetatable 函數(shù)可以獲取任意值的元表。getmetatable (object) 使用 setmetatable 函數(shù)可以設(shè)置表類型值的元表。setmetatable (table, metatable) 例子只有字符串類型的值默認(rèn)擁有元表: a = "5"
b = 5
c = {5}
print(getmetatable(a)) --> table: 0x7fe221e06890
print(getmetatable(b)) --> nil
print(getmetatable(c)) --> nil
2.8.3 事件的具體介紹事先提醒 Lua 使用 raw 前綴的函數(shù)來(lái)操作元方法,避免元方法的循環(huán)調(diào)用。 例如 Lua 獲取對(duì)象 obj 中元方法的過(guò)程如下: rawget(getmetatable(obj)or{}, "__"..event_name)
元方法 indexindex 是元表中最常用的事件,用于值的下標(biāo)訪問(wèn) -- table[key] 。 事件 index 的值可以是函數(shù)也可以是表。當(dāng)使用表進(jìn)行賦值時(shí),元方法可能引發(fā)另一次元方法的調(diào)用,具體可見(jiàn)下面?zhèn)未a介紹。 當(dāng)用戶通過(guò)鍵值來(lái)訪問(wèn)表時(shí),如果沒(méi)有找到鍵對(duì)應(yīng)的值,則會(huì)調(diào)用對(duì)應(yīng)元表中的此事件。如果 index 使用表進(jìn)行賦值,則在該表中查找傳入鍵的對(duì)應(yīng)值;如果 index 使用函數(shù)進(jìn)行賦值,則調(diào)用該函數(shù),并傳入表和鍵。 Lua 對(duì)取下標(biāo)操作的處理過(guò)程用偽碼表示如下: function gettable_event (table, key)
local h
if type(table) == "table" then
local v = rawget(table, key)
if v ~= nil then return v end
h = metatable(table).__index
if h == nil then return nil end
else
h = metatable(table).__index
if h == nil then
error(···);
end
end
if type(h) == "function" then
return h(table, key)
else
return h[key]
end
end
例子: 使用表賦值: t = {[1] = "cat",[2] = "dog"}
print(t[3]) --> nil
setmetatable(t, {__index = {[3] = "pig", [4] = "cow", [5] = "duck"}})
print(t[3]) --> pig
使用函數(shù)賦值: t = {[1] = "cat",[2] = "dog"}
print(t[3]) --> nil
setmetatable(t, {__index = function (table,key)
key = key % 2 + 1
return table[key]
end})
print(t[3]) --> dog
元方法 newindexnewindex 用于賦值操作 -- talbe[key] = value 。 事件 newindex 的值可以是函數(shù)也可以是表。當(dāng)使用表進(jìn)行賦值時(shí),元方法可能引發(fā)另一次元方法的調(diào)用,具體可見(jiàn)下面?zhèn)未a介紹。 當(dāng)操作類型不是表或者表中尚不存在傳入的鍵時(shí),會(huì)調(diào)用 newindex 的元方法。如果 newindex 關(guān)聯(lián)的是一個(gè)函數(shù)類型以外的值,則再次對(duì)該值進(jìn)行賦值操作。反之,直接調(diào)用函數(shù)。 ~~不是太懂:一旦有了 "newindex" 元方法, Lua 就不再做最初的賦值操作。 (如果有必要,在元方法內(nèi)部可以調(diào)用 rawset 來(lái)做賦值。)~~ Lua 進(jìn)行賦值操作時(shí)的偽碼如下: function settable_event (table, key, value)
local h
if type(table) == "table" then
local v = rawget(table, key)
if v ~= nil then rawset(table, key, value); return end
h = metatable(table).__newindex
if h == nil then rawset(table, key, value); return end
else
h = metatable(table).__newindex
if h == nil then
error(···);
end
end
if type(h) == "function" then
return h(table, key,value)
else
h[key] = value
end
end
例子: 元方法為表類型: t = {}
mt = {}
setmetatable(t, {__newindex = mt})
t.a = 5
print(t.a) --> nil
print(mt.a) --> 5
通過(guò)兩次調(diào)用 newindex 元方法將新的域添加到了表 mt 。 +++ 元方法為函數(shù): -- 對(duì)不同類型的 key 使用不同的賦值方式
t = {}
setmetatable(t, {__newindex = function (table,key,value)
if type(key) == "number" then
rawset(table, key, value*value)
else
rawset(table, key, value)
end
end})
t.name = "product"
t[1] = 5
print(t.name) --> product
print(t[1]) --> 25
元方法 callcall 事件用于函數(shù)調(diào)用 -- function(args) 。 Lua 進(jìn)行函數(shù)調(diào)用操作時(shí)的偽代碼: function function_event (func, ...)
if type(func) == "function" then
return func(...) -- 原生的調(diào)用
else
-- 如果不是函數(shù)類型,則使用 call 元方法進(jìn)行函數(shù)調(diào)用
local h = metatable(func).__call
if h then
return h(func, ...)
else
error(···)
end
end
end
例子: 由于用戶只能為表類型的值綁定自定義元表,因此,我們可以對(duì)表進(jìn)行函數(shù)調(diào)用,而不能把其他類型的值當(dāng)函數(shù)使用。 -- 把數(shù)據(jù)記錄到表中,并返回?cái)?shù)據(jù)處理結(jié)果
t = {}
setmetatable(t, {__call = function (t,a,b,factor)
t.a = 1;t.b = 2;t.factor = factor
return (a + b)*factor
end})
print(t(1,2,0.1)) --> 0.3
print(t.a) --> 1
print(t.b) --> 2
print(t.factor) --> 0.1
運(yùn)算操作符相關(guān)元方法運(yùn)算操作符相關(guān)元方法自然是用來(lái)定義運(yùn)算的。 以 add 為例,Lua 在實(shí)現(xiàn) add 操作時(shí)的偽碼如下: function add_event (op1, op2)
-- 參數(shù)可轉(zhuǎn)化為數(shù)字時(shí),tonumber 返回?cái)?shù)字,否則返回 nil
local o1, o2 = tonumber(op1), tonumber(op2)
if o1 and o2 then -- 兩個(gè)操作數(shù)都是數(shù)字?
return o1 + o2 -- 這里的 '+' 是原生的 'add'
else -- 至少一個(gè)操作數(shù)不是數(shù)字時(shí)
local h = getbinhandler(op1, op2, "__add") -- 該函數(shù)的介紹在下面
if h then
-- 以兩個(gè)操作數(shù)來(lái)調(diào)用處理器
return h(op1, op2)
else -- 沒(méi)有處理器:缺省行為
error(···)
end
end
end
代碼中的 getbinhandler 函數(shù)定義了 Lua 怎樣選擇一個(gè)處理器來(lái)作二元操作。 在該函數(shù)中,首先,Lua 嘗試第一個(gè)操作數(shù)。如果這個(gè)操作數(shù)所屬類型沒(méi)有定義這個(gè)操作的處理器,然后 Lua 會(huì)嘗試第二個(gè)操作數(shù)。 function getbinhandler (op1, op2, event)
return metatable(op1)[event] or metatable(op2)[event]
end
+++ 對(duì)于一元操作符,例如取負(fù),Lua 在實(shí)現(xiàn) unm 操作時(shí)的偽碼: function unm_event (op)
local o = tonumber(op)
if o then -- 操作數(shù)是數(shù)字?
return -o -- 這里的 '-' 是一個(gè)原生的 'unm'
else -- 操作數(shù)不是數(shù)字。
-- 嘗試從操作數(shù)中得到處理器
local h = metatable(op).__unm
if h then
-- 以操作數(shù)為參數(shù)調(diào)用處理器
return h(op)
else -- 沒(méi)有處理器:缺省行為
error(···)
end
end
end
例子: 加法的例子: t = {}
setmetatable(t, {__add = function (a,b)
if type(a) == "number" then
return b.num + a
elseif type(b) == "number" then
return a.num + b
else
return a.num + b.num
end
end})
t.num = 5
print(t + 3) --> 8
取負(fù)的例子: t = {}
setmetatable(t, {__unm = function (a)
return -a.num
end})
t.num = 5
print(-t) --> -5
其他事件的元方法對(duì)于連接操作,當(dāng)操作數(shù)中存在數(shù)值或字符串以外的類型時(shí)調(diào)用該元方法。 對(duì)于取長(zhǎng)操作,如果操作數(shù)不是字符串類型,也不是表類型,則嘗試使用元方法(這導(dǎo)致自定義的取長(zhǎng)基本沒(méi)有,在之后的版本中似乎做了改進(jìn))。 對(duì)于三種比較類操作,均需要滿足兩個(gè)操作數(shù)為同類型,且關(guān)聯(lián)同一個(gè)元表時(shí)才能使用元方法。 對(duì)于 eq (等于)比較操作,如果操作數(shù)所屬類型沒(méi)有原生的等于比較,則調(diào)用元方法。 對(duì)于 lt (小于)與 le (小于等于)兩種比較操作,如果兩個(gè)操作數(shù)同為數(shù)值或者同為字符串,則直接進(jìn)行比較,否則使用元方法。 對(duì)于 le 操作,如果元方法 "le" 沒(méi)有提供,Lua 就嘗試 "lt",它假定 a <= b 等價(jià)于 not (b < a) 。 對(duì)于 tostring 操作,元方法定義了值的字符串表示方式。 例子: 取長(zhǎng)操作: t = {1,2,3,"one","two","three"}
setmetatable(t, {__len = function (t)
local cnt = 0
for k,v in pairs(t) do
if type(v) == "number" then
cnt = cnt + 1
print(k,v)
end
end
return cnt
end})
-- 結(jié)果是 6 而不是預(yù)期中的 3
print(
等于比較操作: t = {name="number",1,2,3}
t2 = {name = "number",4,5,6}
mt = {__eq = function (a,b)
return a.name == b.name
end}
setmetatable(t,mt) -- 必須要關(guān)聯(lián)同一個(gè)元表才能比較
setmetatable(t2,mt)
print(t==t2) --> true
tostring 操作: t = {num = "a table"}
print(t) --> table: 0x7f8e83c0a820
mt = {__tostring = function(t)
return t.num
end}
setmetatable(t, mt)
print(tostring(t)) --> a table
print(t) --> a table
2.9 環(huán)境表類型 thread 、function 和 userdata 的對(duì)象除了能與元表建立關(guān)聯(lián)外,還能關(guān)聯(lián)一個(gè)環(huán)境表。 關(guān)聯(lián)在線程上的環(huán)境表稱為全局環(huán)境。 全局環(huán)境作為子線程及子函數(shù)的默認(rèn)環(huán)境。 全局環(huán)境能夠直接被 C 調(diào)用。 關(guān)聯(lián)在 Lua 函數(shù)上的環(huán)境表接管函數(shù)對(duì)全局變量的所有訪問(wèn)。并且作為子函數(shù)的默認(rèn)環(huán)境。 關(guān)聯(lián)在 C 函數(shù)上的環(huán)境能直接被 C 調(diào)用。 關(guān)聯(lián)在 userdata 上的環(huán)境沒(méi)有實(shí)際的用途,只是為了方便程序員把一個(gè)表關(guān)聯(lián)到 userdata 上。 2.10 垃圾回收2.10.1 垃圾收集的元方法[待補(bǔ)充] 2.10.2 弱表弱表是包含弱引用的表。 弱表的弱引用方式有三種。鍵弱引用,值弱引用,鍵和值均弱引用。 可以通過(guò)元表中的 __mode 域來(lái)設(shè)置一個(gè)表是否有弱引用,以及弱引用的方式。 a = {}
b = { __mode = "k"} -- 引號(hào)中添加 k 表示 key 弱引用,v 表示 value 弱引用, kv 表示均弱引用。
setmetable(a,b) -- b 是 a 的元表,綁定后就不能在更改 __mode 的值。
垃圾回收機(jī)制會(huì)把弱引用的部分回收。但是不論是哪種弱引用,回收機(jī)制都會(huì)把整個(gè)鍵值對(duì)從弱表中移除。 3 程序接口 (API)這部分描述 Lua 的 C API,即用來(lái)與 Lua 進(jìn)行通信的 C 函數(shù),所有的函數(shù)和常量都定義在 lua.h 頭文件里面。 有一部分 C 函數(shù)是用宏來(lái)實(shí)現(xiàn)的。~~為什么?:由于所有的宏只會(huì)使用他們的參數(shù)一次(除了第一個(gè)參數(shù),即Lua 狀態(tài)機(jī)),所以不必?fù)?dān)心宏展開帶來(lái)的副作用。~~ 默認(rèn)情況下 Lua 在進(jìn)行函數(shù)調(diào)用時(shí)不會(huì)檢查函數(shù)的有效性和堅(jiān)固性,如果想要進(jìn)行檢查,則使用 luaconf.h 中的 luai_apicheck() 函數(shù)開啟。 3.1 堆棧Lua 調(diào)用 C API 時(shí)使用一個(gè)虛擬棧來(lái)傳遞參數(shù),棧中的所有元素都是 Lua 的類型(例如boolean ,table ,nil 等)。 Lua 調(diào)用 C 函數(shù)的時(shí)候都會(huì)新建一個(gè)虛擬棧,而不是使用舊?;蛘咂渌臈!M瑫r(shí)在 C 函數(shù)中,對(duì) Lua API 調(diào)用時(shí),只能使用當(dāng)前調(diào)用所對(duì)應(yīng)棧中的元素,其他棧的元素是無(wú)法訪問(wèn)的。 虛擬棧中包含 C 函數(shù)所需的所有參數(shù),函數(shù)的返回值也都放在該棧中。 這里所謂的棧概念并不是嚴(yán)格意義上的棧,可以通過(guò)下標(biāo)對(duì)棧中的元素進(jìn)行訪問(wèn)。1表示棧底,-1表示棧頂,又例如 3 表示從棧底開始的第三個(gè)元素。 3.2 堆棧尺寸由于 Lua 的 C API 默認(rèn)不做有效性和堅(jiān)固性(魯棒性)檢測(cè),因此開發(fā)人員有責(zé)任保證堅(jiān)固性。特別要注意的是,不能讓堆棧溢出。Lua 只保證棧大小會(huì)大于 LUA_MINSTACK (一般是 20)。開發(fā)人員可以使用lua_checkstack 函數(shù)來(lái)手動(dòng)設(shè)置棧的大小。 3.3 偽索引除了用索引訪問(wèn)函數(shù)堆棧的 Lua 元素,C 代碼還可以使用偽索引來(lái)訪問(wèn)堆棧以外的 Lua 元素,例如線程的環(huán)境、注冊(cè)表、函數(shù)的環(huán)境 以及 C函數(shù)的 upvalue (上值)??梢酝ㄟ^(guò)特別聲明來(lái)禁用偽索引。 線程的環(huán)境放在偽索引 LUA_GLOBALSINDEX 處,函數(shù)的環(huán)境放在偽索引 LUA_ENVIRONINDEX 處。 訪問(wèn)環(huán)境的方式跟訪問(wèn)表的方式是一致的,例如要訪問(wèn)全局變量的值,可以使用: lua_getfield(L,LUA_GLOBALSINDEX,varname)
3.4 C 閉包當(dāng)我們把創(chuàng)建出來(lái)的函數(shù)和一些值關(guān)聯(lián)在一起,就得到了一個(gè)閉包。那些關(guān)聯(lián)起來(lái)的值稱為 upvalue (上值)。 函數(shù)的上值都放在特定的偽索引處,可以通過(guò) lua_upvalueindex 獲取上值的偽索引。例如lua_upvalueindex(3) 表示獲取第三個(gè)關(guān)聯(lián)值(按照關(guān)聯(lián)順序排列)對(duì)應(yīng)的偽索引。 3.5 注冊(cè)表Lua 提供了一個(gè)注冊(cè)表,C 代碼可以用來(lái)存放想要存放的 Lua 值。注冊(cè)表用偽索引 LUA_REGISTRYINDEX 定位。 為了避免命名沖突,一般采用包含庫(kù)名的字符串作為鍵名。~~什么東西?:或者可以取你自己 C 代碼 中的一個(gè)地址,以 light userdata 的形式做鍵。~~ 注冊(cè)表中的整數(shù)鍵有特定用途(用于實(shí)現(xiàn)補(bǔ)充庫(kù)的引用系統(tǒng)),不建議用于其他用途。 3.6 C 中的錯(cuò)誤處理[待補(bǔ)充] 3.7 函數(shù)和類型本節(jié)介紹 C API 中的函數(shù)和類型。 余下部分見(jiàn) Lua 學(xué)習(xí)筆記(下)
參考鏈接BNF范式簡(jiǎn)介 (簡(jiǎn)要介紹 BNF) Lua入門系列-果凍想(對(duì)Lua進(jìn)行了較為全面的介紹) Lua快速入門(介紹 Lua 中最為重要的幾個(gè)概念,為 C/C++ 程序員準(zhǔn)備) Lua 5.1 中文手冊(cè)(全面的 Lua5.1 中文手冊(cè)) Lua 5.3 中文手冊(cè)(云風(fēng)花了6天寫的,天哪,我看都要看6天的節(jié)奏呀) Lua迭代器和泛型for(介紹 Lua 迭代器的詳細(xì)原理以及使用) How do JavaScript closures work?——StackOverflow(詳細(xì)介紹了 Javascript 中閉包的概念) Lua模式匹配(參考了此文中對(duì) %b 的使用) LuaSocket(LuaSocket 官方手冊(cè)) Lua loadfile的用法, 與其他函數(shù)的比較(loadfile的介紹部分引用了此文) Lua 的元表(對(duì)元表的描述比較有條理,通俗易懂,本文元表部分參考了此文) 設(shè)置函數(shù)環(huán)境——setfenv(解釋了如何方便地設(shè)置函數(shù)的環(huán)境,以及為什么要那樣設(shè)置) lua5.1中的setfenv使用(介紹了該環(huán)境的設(shè)置在實(shí)際中的一個(gè)應(yīng)用)
|