日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

Lua筆記

 擱淺的鯊魚renb 2015-12-04

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           -- 代表字符 a
\97         -- 代表字符 a
\049        -- 代表數(shù)字字符 1 

其他轉(zhuǎn)義符表示

\\n         -- 代表字符串 \n
\n          -- 代表?yè)Q行

注意數(shù)字字符必須是三位。其他字符則不能超過(guò)三位。

采用長(zhǎng)括號(hào):長(zhǎng)括號(hào)內(nèi)的所有內(nèi)容都作為普通字符處理。

[[]]        -- 0級(jí)長(zhǎng)括號(hào)
[==[]==]    -- 2級(jí)長(zhǎng)括號(hào)

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) --> nil nil do local a = 6 -- 局部變量 b = 6 -- 全局變量 print(a,b); --> 6 6 end print(a,b) --> 5 6

方便標(biāo)記,--> 代表前面表達(dá)式的結(jié)果。

2.3.1 索引

對(duì) table 的索引使用方括號(hào) []。Lua使用語(yǔ)法糖提供 . 操作。

t[i]
t.i                 -- 當(dāng)索引為字符串類型時(shí)的一種簡(jiǎn)化寫法
gettable_event(t,i) -- 采用索引訪問(wèn)本質(zhì)上是一個(gè)類似這樣的函數(shù)調(diào)用

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ǔ)句組 | chuncks

chunck ::= {stat[';']}

([';'] 應(yīng)該是表示語(yǔ)句組后面 ; 是可選項(xiàng)。)

2.4.2 語(yǔ)句塊 | blocks

block ::= chunck
stat ::= do block end

可以將一個(gè)語(yǔ)句塊顯式地寫成語(yǔ)句組,可以用于控制局部變量的作用范圍。

2.4.3 賦值 | assignment

Lua 支持多重賦值。

多重賦值時(shí),按序?qū)⒂疫叺谋磉_(dá)式的值賦值給左值。右值不足補(bǔ) nil,右值多余舍棄。

b = 1
a,b = 4 -- a = 4,b = nil 

+++

Lua 在進(jìn)行賦值操作時(shí),會(huì)一次性把右邊的表達(dá)式都計(jì)算出來(lái)后進(jìn)行賦值。

i = 5
i,a[i] = i+1, 7 -- i = 6 ,a[5] = 7

特別地,有

x,y = y,x -- 交換 x,y 的值

+++

對(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){ // 取到等號(hào),如果步進(jìn)是負(fù)的,那么會(huì)取 i >= 6
    a += i;
}

迭代器形式

迭代器形式輸出一個(gè)表時(shí),如果表中有函數(shù),則輸出的順序及個(gè)數(shù)不確定(筆者測(cè)試得出的結(jié)果,具體原因未知)。

迭代器形式的 for 循環(huán)的實(shí)質(zhì)

-- 依次返回 迭代器、狀態(tài)表、迭代器初始值
function mypairs(t)

    function iterator(t,i)
        i = i + 1
        i = t[i] and i      -- 如果 t[i] == nil 則 i = nil;否則 i = i
        return i,t[i]
    end

    return iterator,t,0

end

-- 一個(gè)表
t = {[1]="1",[2]="2"}

-- 迭代形式 for 語(yǔ)句的 等價(jià)形式
do
local f, s, var = mypairs(t)
    while true do
        local var1, var2 = f(s, var)
        var = var1
        if var == nil then break end

        -- for 循環(huán)中添加的語(yǔ)句
        print(var1,var2)

    end
end

-- 迭代形式 for 語(yǔ)句
for var1,var2 in mypairs(t) do
    print(var1,var2)
end

--> 1   1
--> 2   2
--> 1   1
--> 2   2
數(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


--> 5
--> -1
--> 1
--> -5

可以看到,僅當(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(#"abc\0")                         --> 4
-- 表取長(zhǎng)
print(#{[1]=1,[2]=2,[3]=3,x=5,y=6})     --> 3
print(#{[1]=1,[2]=nil,[3]=3,x=5,y=6})   --> 1

2.5.6 優(yōu)先級(jí)

由低到高:

or
and
 <     >     <=    >=    ~=    ==
 ..
 +     -
 *     /     %
 not   #     - (unary)
 ^

冪運(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} -- 那么 a[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(#b)                       --> 6       -- 表的長(zhǎng)度為 6                 
print(#c)                       --> 3       -- 函數(shù)添加括號(hào)后表的長(zhǎng)度為 3

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)              -- 編譯錯(cuò)誤
f(1)                    --> a = 1, b = nil
f(1,2)                  --> a = 1, b = 2
f(1,2,3)                --> a = 1, b = 2

g(1,2)                  --> a = 1, b = 2, (nothing)
g(1,2,3)                --> a = 1, b = 2, (3)
g(1,f(4,5),3)           --> a = 1, b = 4, (3)
g(1,f(4,5))             --> a = 1, b = 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)行改變

-- 實(shí)現(xiàn)一個(gè)迭代器

function begin(i)
    local cnt = i

    return function ()      -- 這是一個(gè)匿名函數(shù),實(shí)現(xiàn)了自增的功能;同時(shí)它也是一個(gè)閉包,因?yàn)樵L問(wèn)了外部變量 cnt
        cnt = cnt + 1
        return cnt
    end
end


iterator = begin(2)     -- 設(shè)置迭代器的初值為 2 ,返回一個(gè)迭代器函數(shù)

print(iterator())           -- 執(zhí)行迭代
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     -- 'table[key]',取下標(biāo)操作,用于訪問(wèn)表中的域
__newindex  -- 'table[key] = value',賦值操作,增改表中的域
__call      -- 'func(args)',函數(shù)調(diào)用,參見(jiàn) [2.5.9 函數(shù)調(diào)用](#2-5-9)

-- 數(shù)學(xué)運(yùn)算操作符
__add       -- '+'
__sub       -- '-'
__mul       -- '*'
__div       -- '/'
__mod       -- '%'
__pow       -- '^'
__unm       -- '-'

-- 連接操作符
__concat    -- '..'

-- 取長(zhǎng)操作符
__len       -- '#'

-- 比較操作符
__eq        -- '=='
__lt        -- '<'      -- a > b 等價(jià)于 b < a
__le        -- '<='     -- a >= b 等價(jià)于 b <= a 

還有一些其他的事件,例如 __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)

元方法 index

index 是元表中最常用的事件,用于值的下標(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)
    -- h 代表元表中 index 的值
    local h     
    if type(table) == "table" then

        -- 訪問(wèn)成功
        local v = rawget(table, key)
        if v ~= nil then return v end

        -- 訪問(wèn)不成功則嘗試調(diào)用元表的 index
        h = metatable(table).__index

        -- 元表不存在返回 nil
        if h == nil then return nil end
    else

        -- 不是對(duì)表進(jìn)行訪問(wèn)則直接嘗試元表
        h = metatable(table).__index

        -- 無(wú)法處理導(dǎo)致出錯(cuò)
        if h == nil then
            error(···);
        end
    end

    -- 根據(jù) index 的值類型處理
    if type(h) == "function" then
        return h(table, key)            -- 調(diào)用處理器
    else 
        return h[key]                   -- 或是重復(fù)上述操作
    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

元方法 newindex

newindex 用于賦值操作 -- 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

        -- 修改表中的 key 對(duì)應(yīng)的 value
        local v = rawget(table, key)
        if v ~= nil then rawset(table, key, value); return end

        -- 
        h = metatable(table).__newindex

        -- 不存在元表,則直接添加一個(gè)域
        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)    -- 調(diào)用處理器
    else 


        h[key] = value             -- 或是重復(fù)上述操作
    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

元方法 call

call 事件用于函數(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)   --> 6 

等于比較操作:

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 的類型(例如booleantable,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)用)


    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多