Lua 學習筆記(上)

1 簡介

由 clean C 實現。須要被宿主程序調用,能夠注入 C 函數。javascript

2 語法

採用基於 BNF 的語法規則。html

2.1 語法約定

Lua 對大小寫敏感。java

2.1.1 保留關鍵字

C 語言中沒有的關鍵字有:git

and elseif function
in nil local not or
repeat then until程序員

規範:全局變量如下劃線開頭。github

2.1.2 操做符

C 語言中沒有的操做符:segmentfault

^ 
~= 
//  -- 向下取整

Lua 中沒有的操做符:api

+=
-=

2.1.3 字符串定義

採用轉義符:經過轉義符表示那些有歧義的字符

字符表示數組

a           -- 表明字符 a
\97         -- 表明字符 a
\049        -- 表明數字字符 1

其餘轉義符表示數據結構

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

注意數字字符必須是三位。其餘字符則不能超過三位。

採用長括號:長括號內的全部內容都做爲普通字符處理。

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

2.2 值與類型

Lua 是動態語言,變量沒有類型,值纔有。值自身攜帶類型信息。

Lua 有八種基本數據類型:nil, boolean, number, string, function, userdata, thread, table

nilfalse 致使條件爲假,其餘均爲真。

userdata 類型變量用於保存 C 數據。 Lua 只能對該類數據進行使用,而不能進行建立或修改,保證宿主程序徹底掌握數據。

thread 用於實現協程(coroutine)。

table 用於實現關聯數組。table 容許任何類型的數據作索引,也容許任何類型作 table 域中的值(前述
任何類型 不包含 nil)。table 是 Lua 中惟一的數據結構。
因爲函數也是一種值,因此 table 中能夠存放函數。

function, userdata, thread, table 這些類型的值都是對象。這些類型的變量都只是保存變量的引用,而且在進行賦值,參數傳遞,函數返回等操做時不會進行任何性質的拷貝。

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

2.2.1 強制轉換

Lua 提供數字字符串間的自動轉換。
可使用 format 函數控制數字向字符串的轉換。

2.3 變量

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

函數外的變量默認爲全局變量,除非用 local 顯示聲明。函數內變量與函數的參數默認爲局部變量。

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

變量的默認值均爲 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

方便標記,--> 表明前面表達式的結果。

2.3.1 索引

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

t[i]
t.i                 -- 當索引爲字符串類型時的一種簡化寫法
gettable_event(t,i) -- 採用索引訪問本質上是一個相似這樣的函數調用

2.3.2 環境表

全部全局變量放在一個環境表裏,該表的變量名爲 _env 。對某個全局變量 a 的訪問即 _env.a_env_ 只是爲了方便說明)。

每一個函數做爲變量持有一個環境表的引用,裏面包含該函數可調用的全部變量。
子函數會從父函數繼承環境表。
能夠經過函數 getfenv / setfenv 來讀寫環境表。

2.4 語句 | statement

支持賦值,控制結構,函數調用,還有變量聲明。

不容許空的語句段,所以 ;; 是非法的。

2.4.1 語句組 | chuncks

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

([';'] 應該是表示語句組後面 ; 是可選項。)

2.4.2 語句塊 | blocks

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

能夠將一個語句塊顯式地寫成語句組,能夠用於控制局部變量的做用範圍。

2.4.3 賦值 | assignment

Lua 支持多重賦值。

多重賦值時,按序將右邊的表達式的值賦值給左值。右值不足補 nil,右值多餘捨棄。

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

+++

Lua 在進行賦值操做時,會一次性把右邊的表達式都計算出來後進行賦值。

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

特別地,有

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

+++

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

2.4.4 控制結構

條件語句

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

循環語句

while [exp]
    [block]
end

+++

repeat
    [block]
until [exp]

注意,因爲 repeat 語句到 until 還未結束,所以在 until 以後的表達式中可使用 block 中定義的局部變量。

例如:

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

+++

breakreturn

breakreturn 只能寫在語句塊的最後一句,若是實在須要寫在語句塊中間,那麼就在兩個關鍵詞外面包圍 do end 語句塊。

do break end

2.4.5 For 循環

for 循環的用法比較多,單獨拎出來說。

for 中的表達式會在循環開始前一次性求值,在循環過程當中再也不更新。

數字形式

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

三個 exp 分別表明初值,結束值,步進。exp 的值均須要是一個數字。
第三個 exp 默認爲 1,能夠省略。

a = 0

for i = 1,6,2 do
    a = a + i
end

等價於

int a = 0;
for (int i = 1; i <= 6;i += 2){ // 取到等號,若是步進是負的,那麼會取 i >= 6
    a += i;
}

迭代器形式

迭代器形式輸出一個表時,若是表中有函數,則輸出的順序及個數不肯定(筆者測試得出的結果,具體緣由未知)。

迭代器形式的 for 循環的實質

-- 依次返回 迭代器、狀態表、迭代器初始值
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

-- 一個表
t = {[1]="1",[2]="2"}

-- 迭代形式 for 語句的 等價形式
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 循環中添加的語句
        print(var1,var2)

    end
end

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

--> 1   1
--> 2   2
--> 1   1
--> 2   2
數組形式
ary = {[1]=1,[2]=2,[5]=5}
for i,v in ipairs(ary) do
    print(v)                    --> 1 2
end

從1開始,直到數值型下標結束或者值爲 nil 時結束。

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

遍歷整個表的鍵值對。

關於迭代器的更多內容,可參考Lua 迭代器和泛型 for

2.5 表達式

2.5.1 數學運算操做符

% 操做符

Lua 中的 % 操做符與 C 語言中的操做符雖然都是取模的含義,可是取模的方式不同。
在 C 語言中,取模操做是將兩個操做數的絕對值取模後,在添加上第一個操做數的符號。
而在 Lua 中,僅僅是簡單的對商相對負無窮向下取整後的餘數。

+++

在 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 是直接根據取模定義進行運算。 C 則對取模運算作了一點處理。

+++

舉例:

在 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

能夠看到,僅當操做數同號時,兩種語言的取模結果相同。異號時,取模結果的符號與數值均不相等。

在 Lua 中的取模運算總結爲:a % b,若是 a,b 同號,結果取 a,b 絕對值的模;異號,結果取 b 絕對值與絕對值取模後的差。取模後值的符號與 b 相同。

2.5.2 比較操做符

比較操做的結果是 boolean 型的,非 truefalse

支持的操做符有:

< <= ~= == > >=

不支持 ! 操做符。

+++

對於 == 操做,運算時先比較兩個操做數的類型,若是不一致則結果爲 false。此時數值與字符串之間並不會自動轉換。

比較兩個對象是否相等時,僅當指向同一內存區域時,斷定爲 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      -- 數字與字符串做爲不一樣類型進行比較
print(c == d)       --> true       
print(e == f)       --> true       -- 引用指向相同的對象
print(e == g)       --> false      -- 雖然內容相同,可是是不一樣的對象
print(false == nil) --> false      -- false 是 boolean,nil 是 nil 型

方便標記,--> 表明前面表達式的結果。

+++

userdatatable 的比較方式能夠經過元方法 eq 進行改變。

大小比較中,數字和字符串的比較與 C 語言一致。若是是其餘類型的值,Lua會嘗試調用元方法 ltle

2.5.3 邏輯操做符

and,or,not

僅認爲 falsenil 爲假。

not

取反操做 not 的結果爲 boolean 類型。(andor 的結果則不必定爲 boolean)

b = not a           -- a 爲 nil,b 爲 true
c = not not a       -- c 爲 false

and

a and b,若是 a 爲假,返回 a,若是 a 爲真, 返回 b

注意,爲何 a 爲假的時候要返回 a 呢?有什麼意義?這是由於 a 多是 false 或者 nil,這兩個值雖然都爲假,可是是有區別的。

or

a or b,若是 a 爲假,返回 b,若是 a 爲真, 返回 a。與 and 相反。

+++

提示: 當邏輯操做符用於得出一個 boolean 型結果時,不須要考慮邏輯運算後返回誰的問題,由於邏輯操做符的操做結果符合本來的邏輯含義。

舉例

if (not (a > min and a < max)) then  -- 若是 a 不在範圍內,則報錯
    error() 
end

+++

其餘

andor 遵循短路原則,第二個操做數僅在須要的時候會進行求值操做。

例子


a = 5 x = a or jjjj() -- 雖而後面的函數並無定義,可是因爲不會執行,所以不會報錯。 print(a) -->5 print(x) -->5

經過上面這個例子,咱們應當對於邏輯操做有所警覺,由於這可能會引入一些未能及時預料到的錯誤。

2.5.4 鏈接符

..
鏈接兩個字符串(或者數字)成爲新的字符串。對於其餘類型,調用元方法 concat

2.5.5 取長度操做符

#

對於字符串,長度爲字符串的字符個數。

對於表,經過尋找知足t[n] 不是 nil 而 t[n+1] 爲 nil 的下標 n 做爲表的長度。

~~對於其餘類型呢?~~

例子

-- 字符串取長
print(#"abc\0")                         --> 4
-- 表取長
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 優先級

由低到高:

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

冪運算>單目運算>四則運算>鏈接符>比較操做符>and>or

2.5.7 Table 構造

Table 構造的 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 構造的表是等價的


print(b["price"])   --> 5
print(b.cost)       --> 4
print(b[1])         --> 7       -- 未給出鍵值的,按序分配下標,下標從 1 開始

print(c["price"])   --> abc
print(c.cost)       --> 4
print(c[1])         --> 8       
print(c[2])         --> 2

注意:

  • 未給出鍵值的,按序分配下標,下標從 1 開始
  • 若是表中有相同的鍵,那麼以靠後的那個值做爲鍵對應的值

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

+++

若是表中有相同的鍵,那麼以靠後的那個值做爲鍵對應的值。

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

+++

若是表的最後一個域是表達式形式,而且是一個函數,那麼這個函數的全部返回值都會加入到表中。

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       -- 表中的值並非一次把表達式都計算結束後再賦值的
print(b[3])                     --> 1       
print(b[4])                     --> 2       -- 表達式形式的多返回值函數

print(#b)                       --> 6       -- 表的長度爲 6                 
print(#c)                       --> 3       -- 函數添加括號後表的長度爲 3

2.5.8 函數定義

函數是一個表達式,其值爲 function 類型的對象。函數每次執行都會被實例化。

Lua 中實現一個函數能夠有如下三種形式。

f = function() [block] end
local f; f = function() [block] end
a.f = function() [block] end

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

function f() [block] end
local function f() [block] end
function a.f() [block] end

+++

上面 local 函數的定義之因此不是 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
    end
end

函數的參數

形參會經過實參來初始化爲局部變量。

參數列表的尾部添加 ... 表示函數能接受不定長參數。若是尾部不添加,那麼函數的參數列表長度是固定的。

f(a,b)
g(a,b,...)
h(a,...,b)              -- 編譯錯誤
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的函數的定義方式:

a.f = function (self, params) [block] end

其語法糖形式爲:

function a:f(params) [block] end

使用舉例:

a = {name = "唐衣可俊"}
function a:f()
    print(self.name)
end
a:f()                       --> 唐衣可俊   -- 若是這裏使用 a.f(),那麼 self.name 的地方會報錯 attempt to index local 'self';此時應該寫爲 a.f(a)

: 的做用在於函數定義與調用的時候能夠少寫一個 self 參數。這種形式是對方法的模擬

2.5.9 函數調用

Lua 中的函數調用的BNF語法以下:

functioncall ::= prefixexp args

若是 prefixexp 的值的類型是 function, 那麼這個函數就被用給出的參數調用。 不然 prefixexp 的元方法 "call" 就被調用, call 的第一個參數就是 prefixexp 的值,接下來的是 args 參數列表(參見 2.8 元表 | Metatable)。

函數調用根據是否傳入 self 參數分爲 . 調用和 : 調用。
函數調用根據傳入參數的類型,能夠分爲參數列表調用、表調用、字符串調用

[待完善]

2.5.10 函數閉包

若是一個函數訪問了它的外部變量,那麼它就是一個閉包。

因爲函數內部的變量均爲局部變量,外界沒法對其進行訪問。這時若是外界想要改變局部變量的值,那麼就可使用閉包來實現這一目的。
具體的實現過程大體是這樣,函數內部有可以改變局部變量的子函數,函數將這個子函數返回,那麼外界就能夠經過使用這個子函數來操做局部變量了。

例子:利用閉包來實現對局部變量進行改變

-- 實現一個迭代器

function begin(i)
    local cnt = i

    return function ()      -- 這是一個匿名函數,實現了自增的功能;同時它也是一個閉包,由於訪問了外部變量 cnt
        cnt = cnt + 1
        return cnt
    end
end


iterator = begin(2)     -- 設置迭代器的初值爲 2 ,返回一個迭代器函數

print(iterator())           -- 執行迭代
print(iterator())

提示: 關於閉包的更多說明可參考JavaScript 閉包是如何工做的?——StackOverflow

2.6 可視規則

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

2.7 錯誤處理

[待補充]

2.8 元表 | Metatable

咱們可使用操做符對 Lua 的值進行運算,例如對數值類型的值進行加減乘除的運算操做以及對字符串的鏈接、取長操做等(在 2.5 表達式 這一節中介紹了許多相似的運算)。元表正是定義這些操做行爲的地方。

元表本質上是一個普通 Lua 表。元表中的鍵用來指定操做,稱爲「事件名」;元表中鍵所關聯的值稱爲「元方法」,定義操做的行爲。

2.8.1 事件名與元方法

僅表(table)類型值對應的元表可由用戶自行定義。其餘類型的值所對應的元表僅能經過 Debug 庫進行修改。

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

__index     -- 'table[key]',取下標操做,用於訪問表中的域
__newindex  -- 'table[key] = value',賦值操做,增改表中的域
__call      -- 'func(args)',函數調用,參見 [2.5.9 函數調用](#2-5-9)

-- 數學運算操做符
__add       -- '+'
__sub       -- '-'
__mul       -- '*'
__div       -- '/'
__mod       -- '%'
__pow       -- '^'
__unm       -- '-'

-- 鏈接操做符
__concat    -- '..'

-- 取長操做符
__len       -- '#'

-- 比較操做符
__eq        -- '=='
__lt        -- '<'      -- a > b 等價於 b < a
__le        -- '<='     -- a >= b 等價於 b <= a

還有一些其餘的事件,例如 __tostring__gc 等。

下面進行詳細介紹。

2.8.2 元表與值

每一個值均可以擁有一個元表。對 userdata 和 table 類型而言,其每一個值均可以擁有獨立的元表,也能夠幾個值共享一個元表。對於其餘類型,一個類型的值共享一個元表。例如全部數值類型的值會共享一個元表。除了字符串類型,其餘類型的值默認是沒有元表的。

使用 getmetatable 函數能夠獲取任意值的元表。getmetatable (object)
使用 setmetatable 函數能夠設置表類型值的元表。setmetatable (table, metatable)

例子

只有字符串類型的值默認擁有元表:

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

2.8.3 事件的具體介紹

事先提醒 Lua 使用 raw 前綴的函數來操做元方法,避免元方法的循環調用。

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

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

元方法 index

index 是元表中最經常使用的事件,用於值的下標訪問 -- table[key]

事件 index 的值能夠是函數也能夠是表。當使用表進行賦值時,元方法可能引起另外一次元方法的調用,具體可見下面僞碼介紹。
當用戶經過鍵值來訪問表時,若是沒有找到鍵對應的值,則會調用對應元表中的此事件。若是 index 使用表進行賦值,則在該表中查找傳入鍵的對應值;若是 index 使用函數進行賦值,則調用該函數,並傳入表和鍵。

Lua 對取下標操做的處理過程用僞碼錶示以下:

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

        -- 訪問不成功則嘗試調用元表的 index
        h = metatable(table).__index

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

        -- 不是對錶進行訪問則直接嘗試元表
        h = metatable(table).__index

        -- 沒法處理致使出錯
        if h == nil then
            error(···);
        end
    end

    -- 根據 index 的值類型處理
    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

使用函數賦值:

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 的值能夠是函數也能夠是表。當使用表進行賦值時,元方法可能引起另外一次元方法的調用,具體可見下面僞碼介紹。

當操做類型不是表或者表中尚不存在傳入的鍵時,會調用 newindex 的元方法。若是 newindex 關聯的是一個函數類型之外的值,則再次對該值進行賦值操做。反之,直接調用函數。

~~不是太懂:一旦有了 "newindex" 元方法, Lua 就再也不作最初的賦值操做。 (若是有必要,在元方法內部能夠調用 rawset 來作賦值。)~~

Lua 進行賦值操做時的僞碼以下:

function settable_event (table, key, value)
    local h
    if type(table) == "table" then

        -- 修改表中的 key 對應的 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)    -- 調用處理器
    else 


        h[key] = value             -- 或是重複上述操做
    end
end

例子:

元方法爲表類型:

t = {}
mt = {}

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

經過兩次調用 newindex 元方法將新的域添加到了表 mt 。

+++

元方法爲函數:

-- 對不一樣類型的 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 事件用於函數調用 -- function(args)

Lua 進行函數調用操做時的僞代碼:

function function_event (func, ...)

  if type(func) == "function" then
      return func(...)   -- 原生的調用
  else
      -- 若是不是函數類型,則使用 call 元方法進行函數調用
      local h = metatable(func).__call

      if h then
        return h(func, ...)
      else
        error(···)
      end
  end
end

例子:

因爲用戶只能爲表類型的值綁定自定義元表,所以,咱們能夠對錶進行函數調用,而不能把其餘類型的值當函數使用。

-- 把數據記錄到表中,並返回數據處理結果
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

運算操做符相關元方法

運算操做符相關元方法天然是用來定義運算的。

以 add 爲例,Lua 在實現 add 操做時的僞碼以下:

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

代碼中的 getbinhandler 函數定義了 Lua 怎樣選擇一個處理器來做二元操做。 在該函數中,首先,Lua 嘗試第一個操做數。若是這個操做數所屬類型沒有定義這個操做的處理器,而後 Lua 會嘗試第二個操做數。

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

+++

對於一元操做符,例如取負,Lua 在實現 unm 操做時的僞碼:

function unm_event (op)
  local o = tonumber(op)
  if o then  -- 操做數是數字?
    return -o  -- 這裏的 '-' 是一個原生的 'unm'
  else  -- 操做數不是數字。
    -- 嘗試從操做數中獲得處理器
    local h = metatable(op).__unm
    if h then
      -- 以操做數爲參數調用處理器
      return h(op)
    else  -- 沒有處理器:缺省行爲
      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

取負的例子:

t = {}
setmetatable(t, {__unm = function (a)
  return -a.num
end})

t.num = 5

print(-t)  --> -5

其餘事件的元方法

對於鏈接操做,當操做數中存在數值或字符串之外的類型時調用該元方法。

對於取長操做,若是操做數不是字符串類型,也不是表類型,則嘗試使用元方法(這致使自定義的取長基本沒有,在以後的版本中彷佛作了改進)。

對於三種比較類操做,均須要知足兩個操做數爲同類型,且關聯同一個元表時才能使用元方法。

對於 eq (等於)比較操做,若是操做數所屬類型沒有原生的等於比較,則調用元方法。

對於 lt (小於)與 le (小於等於)兩種比較操做,若是兩個操做數同爲數值或者同爲字符串,則直接進行比較,不然使用元方法。

對於 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 cnt
end})

-- 結果是 6 而不是預期中的 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)              -- 必需要關聯同一個元表才能比較
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 環境表

類型 threadfunctionuserdata 的對象除了能與元表創建關聯外,還能關聯一個環境表。

關聯在線程上的環境表稱爲全局環境。
全局環境做爲子線程及子函數的默認環境。
全局環境可以直接被 C 調用。

關聯在 Lua 函數上的環境表接管函數對全局變量的全部訪問。而且做爲子函數的默認環境。

關聯在 C 函數上的環境能直接被 C 調用。

關聯在 userdata 上的環境沒有實際的用途,只是爲了方便程序員把一個表關聯到 userdata 上。

2.10 垃圾回收

2.10.1 垃圾收集的元方法

[待補充]

2.10.2 弱表

弱表是包含弱引用的表。

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

能夠經過元表中的 __mode 域來設置一個表是否有弱引用,以及弱引用的方式。

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

垃圾回收機制會把弱引用的部分回收。可是不管是哪一種弱引用,回收機制都會把整個鍵值對從弱表中移除。

3 程序接口 (API)

這部分描述 Lua 的 C API,即用來與 Lua 進行通訊的 C 函數,全部的函數和常量都定義在 lua.h 頭文件裏面。

有一部分 C 函數是用宏來實現的。~~爲何?:因爲全部的宏只會使用他們的參數一次(除了第一個參數,即 Lua 狀態機),因此沒必要擔憂宏展開帶來的反作用。~~

默認狀況下 Lua 在進行函數調用時不會檢查函數的有效性和堅固性,若是想要進行檢查,則使用 luaconf.h 中的 luai_apicheck() 函數開啓。

3.1 堆棧

Lua 調用 C API 時使用一個虛擬棧來傳遞參數,棧中的全部元素都是 Lua 的類型(例如 booleantablenil等)。

Lua 調用 C 函數的時候都會新建一個虛擬棧,而不是使用舊棧或者其餘的棧。同時在 C 函數中,對 Lua API 調用時,只能使用當前調用所對應棧中的元素,其餘棧的元素是沒法訪問的。
虛擬棧中包含 C 函數所需的全部參數,函數的返回值也都放在該棧中。

這裏所謂的棧概念並非嚴格意義上的棧,能夠經過下標對棧中的元素進行訪問。1表示棧底,-1表示棧頂,又例如 3 表示從棧底開始的第三個元素。

3.2 堆棧尺寸

因爲 Lua 的 C API 默認不作有效性和堅固性(魯棒性)檢測,所以開發人員有責任保證堅固性。特別要注意的是,不能讓堆棧溢出。Lua 只保證棧大小會大於 LUA_MINSTACK(通常是 20)。開發人員可使用 lua_checkstack 函數來手動設置棧的大小。

3.3 僞索引

除了用索引訪問函數堆棧的 Lua 元素,C 代碼還可使用僞索引來訪問堆棧之外的 Lua 元素,例如線程的環境、註冊表、函數的環境 以及 C函數的 upvalue(上值)。能夠經過特別聲明來禁用僞索引。

線程的環境放在僞索引 LUA_GLOBALSINDEX 處,函數的環境放在僞索引 LUA_ENVIRONINDEX 處。

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

lua_getfield(L,LUA_GLOBALSINDEX,varname)

3.4 C 閉包

當咱們把建立出來的函數和一些值關聯在一塊兒,就獲得了一個閉包。那些關聯起來的值稱爲 upvalue (上值)。

函數的上值都放在特定的僞索引處,能夠經過 lua_upvalueindex 獲取上值的僞索引。例如 lua_upvalueindex(3) 表示獲取第三個關聯值(按照關聯順序排列)對應的僞索引。

3.5 註冊表

Lua 提供了一個註冊表,C 代碼能夠用來存放想要存放的 Lua 值。註冊表用僞索引 LUA_REGISTRYINDEX 定位。

爲了不命名衝突,通常採用包含庫名的字符串做爲鍵名。~~什麼東西?:或者能夠取你本身 C 代碼 中的一個地址,以 light userdata 的形式作鍵。~~

註冊表中的整數鍵有特定用途(用於實現補充庫的引用系統),不建議用於其餘用途。

3.6 C 中的錯誤處理

[待補充]

3.7 函數和類型

本節介紹 C API 中的函數和類型。

餘下部分見 Lua 學習筆記(下)


參考連接

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

相關文章
相關標籤/搜索