中文字幕理论片,69视频免费在线观看,亚洲成人app,国产1级毛片,刘涛最大尺度戏视频,欧美亚洲美女视频,2021韩国美女仙女屋vip视频

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項超值服

開通VIP
Lua筆記

1 簡介

由 clean C 實(shí)現(xiàn)。需要被宿主程序調(diào)用,可以注入 C 函數(shù)。

2 語法

采用基于 BNF 的語法規(guī)則。

2.1 語法約定

Lua 對大小寫敏感。

2.1.1 保留關(guān)鍵字

C 語言中沒有的關(guān)鍵字有:

and elseif function
in nil local not or
repeat then until

規(guī)范:全局變量以下劃線開頭。

2.1.2 操作符

C 語言中沒有的操作符:

^ ~= //  -- 向下取整

Lua 中沒有的操作符:

+=-=

2.1.3 字符串定義

采用轉(zhuǎn)義符:通過轉(zhuǎn)義符表示那些有歧義的字符

字符表示

a           -- 代表字符 a\97         -- 代表字符 a\049        -- 代表數(shù)字字符 1 

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

\\n         -- 代表字符串 \n\n          -- 代表換行

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

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

[[]]        -- 0級長括號[==[]==]    -- 2級長括號

2.2 值與類型

Lua 是動態(tài)語言,變量沒有類型,值才有。值自身攜帶類型信息。

Lua 有八種基本數(shù)據(jù)類型:nil, boolean, number, string, function, userdata, thread, table。

僅 nil 和 false 導(dǎo)致條件為假,其他均為真。

userdata 類型變量用于保存 C 數(shù)據(jù)。 Lua 只能對該類數(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 這些類型的值都是對象。這些類型的變量都只是保存變量的引用,并且在進(jìn)行賦值,參數(shù)傳遞,函數(shù)返回等操作時不會進(jìn)行任何性質(zhì)的拷貝。

庫函數(shù) type() 返回變量的類型描述信息。

2.2.1 強(qiáng)制轉(zhuǎn)換

Lua 提供數(shù)字字符串間的自動轉(zhuǎn)換。
可以使用 format 函數(shù)控制數(shù)字向字符串的轉(zhuǎn)換。

2.3 變量

變量有三種類型:全局變量、局部變量、表中的域

函數(shù)外的變量默認(rèn)為全局變量,除非用 local 顯示聲明。函數(shù)內(nèi)變量與函數(shù)的參數(shù)默認(rèn)為局部變量。

局部變量的作用域為從聲明位置開始到所在語句塊結(jié)束(或者是直到下一個同名局部變量的聲明)。

變量的默認(rèn)值均為 nil。


a = 5 -- 全局變量local b = 5 -- 局部變量function joke() c = 5 -- 局部變量 local d = 6 -- 局部變量endprint(c,d) --> nil nildo local a = 6 -- 局部變量 b = 6 -- 全局變量 print(a,b); --> 6 6endprint(a,b) --> 5 6

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

2.3.1 索引

對 table 的索引使用方括號 []。Lua使用語法糖提供 . 操作。

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

2.3.2 環(huán)境表

所有全局變量放在一個環(huán)境表里,該表的變量名為 _env 。對某個全局變量 a 的訪問即 _env.a (_env_ 只是為了方便說明)。

每個函數(shù)作為變量持有一個環(huán)境表的引用,里面包含該函數(shù)可調(diào)用的所有變量。
子函數(shù)會從父函數(shù)繼承環(huán)境表。
可以通過函數(shù) getfenv / setfenv 來讀寫環(huán)境表。

2.4 語句 | statement

支持賦值,控制結(jié)構(gòu),函數(shù)調(diào)用,還有變量聲明。

不允許空的語句段,因此 ;; 是非法的。

2.4.1 語句組 | chuncks

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

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

2.4.2 語句塊 | blocks

block ::= chunckstat ::= do block end

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

2.4.3 賦值 | assignment

Lua 支持多重賦值。

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

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

+++

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

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

特別地,有

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

+++

對全局變量以及表的域的賦值操作含義可以在元表中更改。

2.4.4 控制結(jié)構(gòu)

條件語句

if [exp]    [block]elseif [exp]    [block]else    [block]end

循環(huán)語句

while [exp]    [block]end

+++

repeat    [block]until [exp]

注意,由于 repeat 語句到 until 還未結(jié)束,因此在 until 之后的表達(dá)式中可以使用 block 中定義的局部變量。

例如:

a = 1c = 5repeat    b = a + c    c = c * 2until b > 20print(c)            -->     40

+++

break 和 return

break 和 return 只能寫在語句塊的最后一句,如果實(shí)在需要寫在語句塊中間,那么就在兩個關(guān)鍵詞外面包圍do end 語句塊。

do break end

2.4.5 For 循環(huán)

for 循環(huán)的用法比較多,單獨(dú)拎出來講。

for 中的表達(dá)式會在循環(huán)開始前一次性求值,在循環(huán)過程中不再更新。

數(shù)字形式

for [Name] = [exp],[exp],[exp] do [block] end

三個 exp 分別代表初值,結(jié)束值,步進(jìn)。exp 的值均需要是一個數(shù)字。
第三個 exp 默認(rèn)為 1,可以省略。

a = 0for i = 1,6,2 do    a = a + iend

等價于

int a = 0;for (int i = 1; i <= 6;i += 2){ // 取到等號,如果步進(jìn)是負(fù)的,那么會取 i >= 6    a += i;}

迭代器形式

迭代器形式輸出一個表時,如果表中有函數(shù),則輸出的順序及個數(shù)不確定(筆者測試得出的結(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,0end-- 一個表t = {[1]="1",[2]="2"}-- 迭代形式 for 語句的 等價形式dolocal 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)中添加的語句        print(var1,var2)    endend-- 迭代形式 for 語句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 2end

從1開始,直到數(shù)值型下標(biāo)結(jié)束或者值為 nil 時結(jié)束。

表遍歷
table = {[1]=1,[2]=2,[5]=5}for k,v in pairs(table) do    print(v)                    --> 1 2 5end

遍歷整個表的鍵值對。

關(guān)于迭代器的更多內(nèi)容,可參考Lua 迭代器和泛型 for。

2.5 表達(dá)式

2.5.1 數(shù)學(xué)運(yùn)算操作符

% 操作符

Lua 中的 % 操作符與 C 語言中的操作符雖然都是取模的含義,但是取模的方式不一樣。
在 C 語言中,取模操作是將兩個操作數(shù)的絕對值取模后,在添加上第一個操作數(shù)的符號。
而在 Lua 中,僅僅是簡單的對商相對負(fù)無窮向下取整后的余數(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 則對取模運(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 % 6b = 5 % -6c = -5 % 6d = -5 % -6x = {a,b,c,d}for i,v in ipairs(x) do    print(i,v)end--> 5--> -1--> 1--> -5

可以看到,僅當(dāng)操作數(shù)同號時,兩種語言的取模結(jié)果相同。異號時,取模結(jié)果的符號與數(shù)值均不相等。

在 Lua 中的取模運(yùn)算總結(jié)為:a % b,如果 a,b 同號,結(jié)果取 a,b 絕對值的模;異號,結(jié)果取 b 絕對值與絕對值取模后的差。取模后值的符號與 b 相同。

2.5.2 比較操作符

比較操作的結(jié)果是 boolean 型的,非 true 即 false

支持的操作符有:

< <= ~= == > >=

不支持 ! 操作符。

+++

對于 == 操作,運(yùn)算時先比較兩個操作數(shù)的類型,如果不一致則結(jié)果為 false。此時數(shù)值與字符串之間并不會自動轉(zhuǎn)換。

比較兩個對象是否相等時,僅當(dāng)指向同一內(nèi)存區(qū)域時,判定為 true?!?/p>

a = 123b = 233c = "123"d = "123"e = {1,2,3}f = eg = {1,2,3}print(a == b)       --> falseprint(a == c)       --> false      -- 數(shù)字與字符串作為不同類型進(jìn)行比較print(c == d)       --> true       print(e == f)       --> true       -- 引用指向相同的對象print(e == g)       --> false      -- 雖然內(nèi)容相同,但是是不同的對象print(false == nil) --> false      -- false 是 boolean,nil 是 nil 型

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

+++

userdata 與 table 的比較方式可以通過元方法 eq 進(jìn)行改變。

大小比較中,數(shù)字和字符串的比較與 C 語言一致。如果是其他類型的值,Lua會嘗試調(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 為 truec = not not a       -- c 為 false

and

a and b,如果 a 為假,返回 a,如果 a 為真, 返回 b。

注意,為什么 a 為假的時候要返回 a 呢?有什么意義?這是因為 a 可能是 false 或者 nil,這兩個值雖然都為假,但是是有區(qū)別的。

or

a or b,如果 a 為假,返回 b,如果 a 為真, 返回 a。與 and 相反。

+++

提示: 當(dāng)邏輯操作符用于得出一個 boolean 型結(jié)果時,不需要考慮邏輯運(yùn)算后返回誰的問題,因為邏輯操作符的操作結(jié)果符合原本的邏輯含義。

舉例

if (not (a > min and a < max)) then  -- 如果 a 不在范圍內(nèi),則報錯    error() end

+++

其他

and 與 or 遵循短路原則,第二個操作數(shù)僅在需要的時候會進(jìn)行求值操作。

例子


a = 5x = a or jjjj() -- 雖然后面的函數(shù)并沒有定義,但是由于不會執(zhí)行,因此不會報錯。print(a) -->5print(x) -->5

通過上面這個例子,我們應(yīng)當(dāng)對于邏輯操作有所警覺,因為這可能會引入一些未能及時預(yù)料到的錯誤。

2.5.4 連接符

..
連接兩個字符串(或者數(shù)字)成為新的字符串。對于其他類型,調(diào)用元方法 concat。

2.5.5 取長度操作符

#

對于字符串,長度為字符串的字符個數(shù)。

對于表,通過尋找滿足t[n] 不是 nil 而 t[n+1] 為 nil 的下標(biāo) n 作為表的長度。

~~對于其他類型呢?~~

例子

-- 字符串取長print(#"abc\0")                         --> 4-- 表取長print(#{[1]=1,[2]=2,[3]=3,x=5,y=6})     --> 3print(#{[1]=1,[2]=nil,[3]=3,x=5,y=6})   --> 1

2.5.6 優(yōu)先級

由低到高:

orand <     >     <=    >=    ~=    == .. +     - *     /     % 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 | expfieldsep ::= `,′ | `;′

舉例:

a = {}b = {["price"] = 5; cost = 4; 2+5}c = { [1] = 2+5, [2] = 2, 8, price = "abc", ["cost"] = 4} -- b 和 c 構(gòu)造的表是等價的print(b["price"])   --> 5print(b.cost)       --> 4print(b[1])         --> 7       -- 未給出鍵值的,按序分配下標(biāo),下標(biāo)從 1 開始print(c["price"])   --> abcprint(c.cost)       --> 4print(c[1])         --> 8       print(c[2])         --> 2       

注意:

  • 未給出鍵值的,按序分配下標(biāo),下標(biāo)從 1 開始
  • 如果表中有相同的鍵,那么以靠后的那個值作為鍵對應(yīng)的值

上面這兩條的存在使得上面的例子中 c1 的輸出值為 8。

+++

如果表中有相同的鍵,那么以靠后的那個值作為鍵對應(yīng)的值。

a = {[1] = 5,[1] = 6} -- 那么 a[1] = 6

+++

如果表的最后一個域是表達(dá)式形式,并且是一個函數(shù),那么這個函數(shù)的所有返回值都會加入到表中。

a = 1function order()    a = a + 1    return 1,2,3,4endb = {order(); a; order(); }c = {order(); a; (order());}print(b[1])                     --> 1       print(b[2])                     --> 2       -- 表中的值并不是一次把表達(dá)式都計算結(jié)束后再賦值的print(b[3])                     --> 1       print(b[4])                     --> 2       -- 表達(dá)式形式的多返回值函數(shù)print(#b)                       --> 6       -- 表的長度為 6                 print(#c)                       --> 3       -- 函數(shù)添加括號后表的長度為 3

2.5.8 函數(shù)定義

函數(shù)是一個表達(dá)式,其值為 function 類型的對象。函數(shù)每次執(zhí)行都會被實(shí)例化。

Lua 中實(shí)現(xiàn)一個函數(shù)可以有以下三種形式。

f = function() [block] endlocal f; f = function() [block] enda.f = function() [block] end

Lua 提供語法糖分別處理這三種函數(shù)定義。

function f() [block] endlocal function f() [block] endfunction a.f() [block] end

+++

上面 local 函數(shù)的定義之所以不是 local f = function() [block] end,是為了避免如下錯誤:

local f = function()    print("local fun")    if i==0 then         f()             -- 編譯錯誤:attempt to call global 'f' (a nil value)        i = i + 1    endend

函數(shù)的參數(shù)

形參會通過實(shí)參來初始化為局部變量。

參數(shù)列表的尾部添加 ... 表示函數(shù)能接受不定長參數(shù)。如果尾部不添加,那么函數(shù)的參數(shù)列表長度是固定的。

f(a,b)g(a,b,...)h(a,...,b)              -- 編譯錯誤
f(1)                    --> a = 1, b = nilf(1,2)                  --> a = 1, b = 2f(1,2,3)                --> a = 1, b = 2g(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

其語法糖形式為:

function a:f(params) [block] end

使用舉例:

a = {name = "唐衣可俊"}function a:f()    print(self.name)enda:f()                       --> 唐衣可俊   -- 如果這里使用 a.f(),那么 self.name 的地方會報錯 attempt to index local 'self';此時應(yīng)該寫為 a.f(a)

: 的作用在于函數(shù)定義與調(diào)用的時候可以少寫一個 self 參數(shù)。這種形式是對方法的模擬

2.5.9 函數(shù)調(diào)用

Lua 中的函數(shù)調(diào)用的BNF語法如下:

functioncall ::= prefixexp args

如果 prefixexp 的值的類型是 function, 那么這個函數(shù)就被用給出的參數(shù)調(diào)用。 否則 prefixexp 的元方法 "call" 就被調(diào)用, call 的第一個參數(shù)就是 prefixexp 的值,接下來的是 args 參數(shù)列表(參見 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ù)閉包

如果一個函數(shù)訪問了它的外部變量,那么它就是一個閉包。

由于函數(shù)內(nèi)部的變量均為局部變量,外界無法對其進(jìn)行訪問。這時如果外界想要改變局部變量的值,那么就可以使用閉包來實(shí)現(xiàn)這一目的。
具體的實(shí)現(xiàn)過程大致是這樣,函數(shù)內(nèi)部有能夠改變局部變量的子函數(shù),函數(shù)將這個子函數(shù)返回,那么外界就可以通過使用這個子函數(shù)來操作局部變量了。

例子:利用閉包來實(shí)現(xiàn)對局部變量進(jìn)行改變

-- 實(shí)現(xiàn)一個迭代器function begin(i)    local cnt = i    return function ()      -- 這是一個匿名函數(shù),實(shí)現(xiàn)了自增的功能;同時它也是一個閉包,因為訪問了外部變量 cnt        cnt = cnt + 1        return cnt    endenditerator = begin(2)     -- 設(shè)置迭代器的初值為 2 ,返回一個迭代器函數(shù)print(iterator())           -- 執(zhí)行迭代print(iterator())

提示: 關(guān)于閉包的更多說明可參考JavaScript 閉包是如何工作的?——StackOverflow

2.6 可視規(guī)則

即變量的作用域,見 2.3 變量 部分。

2.7 錯誤處理

[待補(bǔ)充]

2.8 元表 | Metatable

我們可以使用操作符對 Lua 的值進(jìn)行運(yùn)算,例如對數(shù)值類型的值進(jìn)行加減乘除的運(yùn)算操作以及對字符串的連接、取長操作等(在 2.5 表達(dá)式 這一節(jié)中介紹了許多類似的運(yùn)算)。元表正是定義這些操作行為的地方。

元表本質(zhì)上是一個普通 Lua 表。元表中的鍵用來指定操作,稱為“事件名”;元表中鍵所關(guān)聯(lián)的值稱為“元方法”,定義操作的行為。

2.8.1 事件名與元方法

僅表(table)類型值對應(yīng)的元表可由用戶自行定義。其他類型的值所對應(yīng)的元表僅能通過 Debug 庫進(jìn)行修改。

元表中的事件名均以兩條下劃線 __ 作為前綴,元表支持的事件名有如下幾個:

__index     -- 'table[key]',取下標(biāo)操作,用于訪問表中的域__newindex  -- 'table[key] = value',賦值操作,增改表中的域__call      -- 'func(args)',函數(shù)調(diào)用,參見 [2.5.9 函數(shù)調(diào)用](#2-5-9)-- 數(shù)學(xué)運(yùn)算操作符__add       -- '+'__sub       -- '-'__mul       -- '*'__div       -- '/'__mod       -- '%'__pow       -- '^'__unm       -- '-'-- 連接操作符__concat    -- '..'-- 取長操作符__len       -- '#'-- 比較操作符__eq        -- '=='__lt        -- '<'      -- a > b 等價于 b < a__le        -- '<='     -- a >= b 等價于 b <= a 

還有一些其他的事件,例如 __tostring 和 __gc 等。

下面進(jìn)行詳細(xì)介紹。

2.8.2 元表與值

每個值都可以擁有一個元表。對 userdata 和 table 類型而言,其每個值都可以擁有獨(dú)立的元表,也可以幾個值共享一個元表。對于其他類型,一個類型的值共享一個元表。例如所有數(shù)值類型的值會共享一個元表。除了字符串類型,其他類型的值默認(rèn)是沒有元表的。

使用 getmetatable 函數(shù)可以獲取任意值的元表。getmetatable (object)
使用 setmetatable 函數(shù)可以設(shè)置表類型值的元表。setmetatable (table, metatable)

例子

只有字符串類型的值默認(rèn)擁有元表:

a = "5"b = 5c = {5}print(getmetatable(a))      --> table: 0x7fe221e06890print(getmetatable(b))      --> nilprint(getmetatable(c))      --> nil

2.8.3 事件的具體介紹

事先提醒 Lua 使用 raw 前綴的函數(shù)來操作元方法,避免元方法的循環(huán)調(diào)用。

例如 Lua 獲取對象 obj 中元方法的過程如下:

rawget(getmetatable(obj)or{}, "__"..event_name)

元方法 index

index 是元表中最常用的事件,用于值的下標(biāo)訪問 -- table[key]。

事件 index 的值可以是函數(shù)也可以是表。當(dāng)使用表進(jìn)行賦值時,元方法可能引發(fā)另一次元方法的調(diào)用,具體可見下面?zhèn)未a介紹。
當(dāng)用戶通過鍵值來訪問表時,如果沒有找到鍵對應(yīng)的值,則會調(diào)用對應(yīng)元表中的此事件。如果 index 使用表進(jìn)行賦值,則在該表中查找傳入鍵的對應(yīng)值;如果 index 使用函數(shù)進(jìn)行賦值,則調(diào)用該函數(shù),并傳入表和鍵。

Lua 對取下標(biāo)操作的處理過程用偽碼表示如下:

function gettable_event (table, key)    -- h 代表元表中 index 的值    local h         if type(table) == "table" then        -- 訪問成功        local v = rawget(table, key)        if v ~= nil then return v end        -- 訪問不成功則嘗試調(diào)用元表的 index        h = metatable(table).__index        -- 元表不存在返回 nil        if h == nil then return nil end    else        -- 不是對表進(jìn)行訪問則直接嘗試元表        h = metatable(table).__index        -- 無法處理導(dǎo)致出錯        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ù)上述操作    endend

例子:

使用表賦值:

t = {[1] = "cat",[2] = "dog"}print(t[3])             --> nilsetmetatable(t, {__index = {[3] = "pig", [4] = "cow", [5] = "duck"}})print(t[3])             --> pig

使用函數(shù)賦值:

t = {[1] = "cat",[2] = "dog"}print(t[3])             --> nilsetmetatable(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)行賦值時,元方法可能引發(fā)另一次元方法的調(diào)用,具體可見下面?zhèn)未a介紹。

當(dāng)操作類型不是表或者表中尚不存在傳入的鍵時,會調(diào)用 newindex 的元方法。如果 newindex 關(guān)聯(lián)的是一個函數(shù)類型以外的值,則再次對該值進(jìn)行賦值操作。反之,直接調(diào)用函數(shù)。

~~不是太懂:一旦有了 "newindex" 元方法, Lua 就不再做最初的賦值操作。 (如果有必要,在元方法內(nèi)部可以調(diào)用 rawset 來做賦值。)~~

Lua 進(jìn)行賦值操作時的偽碼如下:

function settable_event (table, key, value)    local h    if type(table) == "table" then        -- 修改表中的 key 對應(yīng)的 value        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)    -- 調(diào)用處理器    else         h[key] = value             -- 或是重復(fù)上述操作    endend

例子:

元方法為表類型:

t = {}mt = {}setmetatable(t, {__newindex = mt})t.a = 5print(t.a)      --> nilprint(mt.a)     --> 5

通過兩次調(diào)用 newindex 元方法將新的域添加到了表 mt 。

+++

元方法為函數(shù):

-- 對不同類型的 key 使用不同的賦值方式t = {}setmetatable(t, {__newindex = function (table,key,value)    if type(key) == "number" then        rawset(table, key, value*value)    else        rawset(table, key, value)    endend})t.name = "product"t[1] = 5print(t.name)       --> productprint(t[1])         --> 25

元方法 call

call 事件用于函數(shù)調(diào)用 -- function(args)。

Lua 進(jìn)行函數(shù)調(diào)用操作時的偽代碼:

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  endend

例子:

由于用戶只能為表類型的值綁定自定義元表,因此,我們可以對表進(jìn)行函數(shù)調(diào)用,而不能把其他類型的值當(dāng)函數(shù)使用。

-- 把數(shù)據(jù)記錄到表中,并返回數(shù)據(jù)處理結(jié)果t = {}setmetatable(t, {__call = function (t,a,b,factor)  t.a = 1;t.b = 2;t.factor = factor  return (a + b)*factorend})print(t(1,2,0.1))       --> 0.3print(t.a)              --> 1print(t.b)              --> 2print(t.factor)         --> 0.1

運(yùn)算操作符相關(guān)元方法

運(yùn)算操作符相關(guān)元方法自然是用來定義運(yùn)算的。

以 add 為例,Lua 在實(shí)現(xiàn) add 操作時的偽碼如下:

function add_event (op1, op2)  -- 參數(shù)可轉(zhuǎn)化為數(shù)字時,tonumber 返回數(shù)字,否則返回 nil  local o1, o2 = tonumber(op1), tonumber(op2)  if o1 and o2 then  -- 兩個操作數(shù)都是數(shù)字?    return o1 + o2   -- 這里的 '+' 是原生的 'add'  else  -- 至少一個操作數(shù)不是數(shù)字時    local h = getbinhandler(op1, op2, "__add") -- 該函數(shù)的介紹在下面    if h then      -- 以兩個操作數(shù)來調(diào)用處理器      return h(op1, op2)    else  -- 沒有處理器:缺省行為      error(···)    end  endend

代碼中的 getbinhandler 函數(shù)定義了 Lua 怎樣選擇一個處理器來作二元操作。 在該函數(shù)中,首先,Lua 嘗試第一個操作數(shù)。如果這個操作數(shù)所屬類型沒有定義這個操作的處理器,然后 Lua 會嘗試第二個操作數(shù)。

 function getbinhandler (op1, op2, event)   return metatable(op1)[event] or metatable(op2)[event] end

+++

對于一元操作符,例如取負(fù),Lua 在實(shí)現(xiàn) unm 操作時的偽碼:

function unm_event (op)  local o = tonumber(op)  if o then  -- 操作數(shù)是數(shù)字?    return -o  -- 這里的 '-' 是一個原生的 'unm'  else  -- 操作數(shù)不是數(shù)字。    -- 嘗試從操作數(shù)中得到處理器    local h = metatable(op).__unm    if h then      -- 以操作數(shù)為參數(shù)調(diào)用處理器      return h(op)    else  -- 沒有處理器:缺省行為      error(···)    end  endend

例子:

加法的例子:

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  endend})t.num = 5print(t + 3)  --> 8

取負(fù)的例子:

t = {}setmetatable(t, {__unm = function (a)  return -a.numend})t.num = 5print(-t)  --> -5

其他事件的元方法

對于連接操作,當(dāng)操作數(shù)中存在數(shù)值或字符串以外的類型時調(diào)用該元方法。

對于取長操作,如果操作數(shù)不是字符串類型,也不是表類型,則嘗試使用元方法(這導(dǎo)致自定義的取長基本沒有,在之后的版本中似乎做了改進(jìn))。

對于三種比較類操作,均需要滿足兩個操作數(shù)為同類型,且關(guān)聯(lián)同一個元表時才能使用元方法。

對于 eq (等于)比較操作,如果操作數(shù)所屬類型沒有原生的等于比較,則調(diào)用元方法。

對于 lt (小于)與 le (小于等于)兩種比較操作,如果兩個操作數(shù)同為數(shù)值或者同為字符串,則直接進(jìn)行比較,否則使用元方法。

對于 le 操作,如果元方法 "le" 沒有提供,Lua 就嘗試 "lt",它假定 a <= b 等價于 not (b < a) 。

對于 tostring 操作,元方法定義了值的字符串表示方式。

例子:

取長操作:

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 cntend})-- 結(jié)果是 6 而不是預(yù)期中的 3print(#t)   --> 6 

等于比較操作:

t = {name="number",1,2,3}t2 = {name = "number",4,5,6}mt = {__eq = function (a,b)    return a.name == b.nameend}setmetatable(t,mt)              -- 必須要關(guān)聯(lián)同一個元表才能比較setmetatable(t2,mt)print(t==t2)   --> true

tostring 操作:

t = {num = "a table"}print(t)              --> table: 0x7f8e83c0a820mt = {__tostring = function(t)  return t.numend}setmetatable(t, mt)print(tostring(t))    --> a tableprint(t)              --> a table

2.9 環(huán)境表

類型 thread、function 和 userdata 的對象除了能與元表建立關(guān)聯(lián)外,還能關(guān)聯(lián)一個環(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ù)對全局變量的所有訪問。并且作為子函數(shù)的默認(rèn)環(huán)境。

關(guān)聯(lián)在 C 函數(shù)上的環(huán)境能直接被 C 調(diào)用。

關(guān)聯(lián)在 userdata 上的環(huán)境沒有實(shí)際的用途,只是為了方便程序員把一個表關(guān)聯(lián)到 userdata 上。

2.10 垃圾回收

2.10.1 垃圾收集的元方法

[待補(bǔ)充]

2.10.2 弱表

弱表是包含弱引用的表。

弱表的弱引用方式有三種。鍵弱引用,值弱引用,鍵和值均弱引用。

可以通過元表中的 __mode 域來設(shè)置一個表是否有弱引用,以及弱引用的方式。

a = {}b = { __mode = "k"}  -- 引號中添加 k 表示 key 弱引用,v 表示 value 弱引用, kv 表示均弱引用。setmetable(a,b)     -- b 是 a 的元表,綁定后就不能在更改 __mode 的值。

垃圾回收機(jī)制會把弱引用的部分回收。但是不論是哪種弱引用,回收機(jī)制都會把整個鍵值對從弱表中移除。

3 程序接口 (API)

這部分描述 Lua 的 C API,即用來與 Lua 進(jìn)行通信的 C 函數(shù),所有的函數(shù)和常量都定義在 lua.h 頭文件里面。

有一部分 C 函數(shù)是用宏來實(shí)現(xiàn)的。~~為什么?:由于所有的宏只會使用他們的參數(shù)一次(除了第一個參數(shù),即Lua 狀態(tài)機(jī)),所以不必?fù)?dān)心宏展開帶來的副作用。~~

默認(rèn)情況下 Lua 在進(jìn)行函數(shù)調(diào)用時不會檢查函數(shù)的有效性和堅固性,如果想要進(jìn)行檢查,則使用 luaconf.h 中的 luai_apicheck() 函數(shù)開啟。

3.1 堆棧

Lua 調(diào)用 C API 時使用一個虛擬棧來傳遞參數(shù),棧中的所有元素都是 Lua 的類型(例如boolean,tablenil等)。

Lua 調(diào)用 C 函數(shù)的時候都會新建一個虛擬棧,而不是使用舊?;蛘咂渌臈!M瑫r在 C 函數(shù)中,對 Lua API 調(diào)用時,只能使用當(dāng)前調(diào)用所對應(yīng)棧中的元素,其他棧的元素是無法訪問的。
虛擬棧中包含 C 函數(shù)所需的所有參數(shù),函數(shù)的返回值也都放在該棧中。

這里所謂的棧概念并不是嚴(yán)格意義上的棧,可以通過下標(biāo)對棧中的元素進(jìn)行訪問。1表示棧底,-1表示棧頂,又例如 3 表示從棧底開始的第三個元素。

3.2 堆棧尺寸

由于 Lua 的 C API 默認(rèn)不做有效性和堅固性(魯棒性)檢測,因此開發(fā)人員有責(zé)任保證堅固性。特別要注意的是,不能讓堆棧溢出。Lua 只保證棧大小會大于 LUA_MINSTACK(一般是 20)。開發(fā)人員可以使用lua_checkstack 函數(shù)來手動設(shè)置棧的大小。

3.3 偽索引

除了用索引訪問函數(shù)堆棧的 Lua 元素,C 代碼還可以使用偽索引來訪問堆棧以外的 Lua 元素,例如線程的環(huán)境、注冊表、函數(shù)的環(huán)境 以及 C函數(shù)的 upvalue(上值)??梢酝ㄟ^特別聲明來禁用偽索引。

線程的環(huán)境放在偽索引 LUA_GLOBALSINDEX 處,函數(shù)的環(huán)境放在偽索引 LUA_ENVIRONINDEX 處。

訪問環(huán)境的方式跟訪問表的方式是一致的,例如要訪問全局變量的值,可以使用:

lua_getfield(L,LUA_GLOBALSINDEX,varname)

3.4 C 閉包

當(dāng)我們把創(chuàng)建出來的函數(shù)和一些值關(guān)聯(lián)在一起,就得到了一個閉包。那些關(guān)聯(lián)起來的值稱為 upvalue (上值)。

函數(shù)的上值都放在特定的偽索引處,可以通過 lua_upvalueindex 獲取上值的偽索引。例如lua_upvalueindex(3) 表示獲取第三個關(guān)聯(lián)值(按照關(guān)聯(lián)順序排列)對應(yīng)的偽索引。

3.5 注冊表

Lua 提供了一個注冊表,C 代碼可以用來存放想要存放的 Lua 值。注冊表用偽索引 LUA_REGISTRYINDEX 定位。

為了避免命名沖突,一般采用包含庫名的字符串作為鍵名。~~什么東西?:或者可以取你自己 C 代碼 中的一個地址,以 light userdata 的形式做鍵。~~

注冊表中的整數(shù)鍵有特定用途(用于實(shí)現(xiàn)補(bǔ)充庫的引用系統(tǒng)),不建議用于其他用途。

3.6 C 中的錯誤處理

[待補(bǔ)充]

3.7 函數(shù)和類型

本節(jié)介紹 C API 中的函數(shù)和類型。

余下部分見 Lua 學(xué)習(xí)筆記(下)


參考鏈接

BNF范式簡介 (簡要介紹 BNF)
Lua入門系列-果凍想(對Lua進(jìn)行了較為全面的介紹)
Lua快速入門(介紹 Lua 中最為重要的幾個概念,為 C/C++ 程序員準(zhǔn)備)
Lua 5.1 中文手冊(全面的 Lua5.1 中文手冊)
Lua 5.3 中文手冊(云風(fēng)花了6天寫的,天哪,我看都要看6天的節(jié)奏呀)
Lua迭代器和泛型for(介紹 Lua 迭代器的詳細(xì)原理以及使用)
How do JavaScript closures work?——StackOverflow(詳細(xì)介紹了 Javascript 中閉包的概念)
Lua模式匹配(參考了此文中對 %b 的使用)
LuaSocket(LuaSocket 官方手冊)
Lua loadfile的用法, 與其他函數(shù)的比較(loadfile的介紹部分引用了此文)
Lua 的元表(對元表的描述比較有條理,通俗易懂,本文元表部分參考了此文)
設(shè)置函數(shù)環(huán)境——setfenv(解釋了如何方便地設(shè)置函數(shù)的環(huán)境,以及為什么要那樣設(shè)置)
lua5.1中的setfenv使用(介紹了該環(huán)境的設(shè)置在實(shí)際中的一個應(yīng)用)


本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊舉報。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
Step By Step(Lua元表與元方法)(轉(zhuǎn))
Lua1
Lua腳本語言——Lua腳本基礎(chǔ)語法
Lua中函數(shù)的幾個特別之處探究_Lua_腳本之家
lua元表
Lua中的元表和元方法
更多類似文章 >>
生活服務(wù)
熱點(diǎn)新聞
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服