由 clean C 實現。須要被宿主程序調用,能夠注入 C 函數。javascript
採用基於 BNF 的語法規則。html
Lua 對大小寫敏感。java
C 語言中沒有的關鍵字有:git
and
elseif
function
in
nil
local
not
or
repeat
then
until
程序員
規範:全局變量如下劃線開頭。github
C 語言中沒有的操做符:segmentfault
^ ~= // -- 向下取整
Lua 中沒有的操做符:api
+= -=
字符表示數組
a -- 表明字符 a \97 -- 表明字符 a \049 -- 表明數字字符 1
其餘轉義符表示數據結構
\\n -- 表明字符串 \n \n -- 表明換行
注意數字字符必須是三位。其餘字符則不能超過三位。
[[]] -- 0級長括號 [==[]==] -- 2級長括號
Lua 是動態語言,變量沒有類型,值纔有。值自身攜帶類型信息。
Lua 有八種基本數據類型:nil, boolean, number, string, function, userdata, thread, table
。
僅 nil
和 false
致使條件爲假,其餘均爲真。
userdata
類型變量用於保存 C 數據。 Lua 只能對該類數據進行使用,而不能進行建立或修改,保證宿主程序徹底掌握數據。
thread
用於實現協程(coroutine)。
table
用於實現關聯數組。table
容許任何類型的數據作索引,也容許任何類型作 table
域中的值(前述任何類型
不包含 nil)。table
是 Lua 中惟一的數據結構。
因爲函數也是一種值,因此 table
中能夠存放函數。
function, userdata, thread, table
這些類型的值都是對象。這些類型的變量都只是保存變量的引用,而且在進行賦值,參數傳遞,函數返回等操做時不會進行任何性質的拷貝。
庫函數 type()
返回變量的類型描述信息。
Lua 提供數字與字符串間的自動轉換。
可使用 format 函數控制數字向字符串的轉換。
變量有三種類型:全局變量、局部變量、表中的域。
函數外的變量默認爲全局變量,除非用 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
方便標記,-->
表明前面表達式的結果。
對 table 的索引使用方括號 []
。Lua使用語法糖提供 .
操做。
t[i] t.i -- 當索引爲字符串類型時的一種簡化寫法 gettable_event(t,i) -- 採用索引訪問本質上是一個相似這樣的函數調用
全部全局變量放在一個環境表裏,該表的變量名爲 _env
。對某個全局變量 a
的訪問即 _env.a
(_env_
只是爲了方便說明)。
每一個函數做爲變量持有一個環境表的引用,裏面包含該函數可調用的全部變量。
子函數會從父函數繼承環境表。
能夠經過函數 getfenv / setfenv
來讀寫環境表。
支持賦值,控制結構,函數調用,還有變量聲明。
不容許空的語句段,所以 ;;
是非法的。
chunck ::= {stat[';']}
([';']
應該是表示語句組後面 ;
是可選項。)
block ::= chunck stat ::= do block end
能夠將一個語句塊顯式地寫成語句組,能夠用於控制局部變量的做用範圍。
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 的值
+++
對全局變量以及表的域的賦值操做含義能夠在元表中更改。
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
+++
break
和 return
break
和 return
只能寫在語句塊的最後一句,若是實在須要寫在語句塊中間,那麼就在兩個關鍵詞外面包圍 do end
語句塊。
do break end
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。
%
操做符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 相同。
比較操做的結果是 boolean
型的,非 true
即 false
。
支持的操做符有:
< <= ~= == > >=
不支持 !
操做符。
+++
對於 ==
操做,運算時先比較兩個操做數的類型,若是不一致則結果爲 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 型
方便標記,-->
表明前面表達式的結果。
+++
userdata
與 table
的比較方式能夠經過元方法 eq
進行改變。
大小比較中,數字和字符串的比較與 C 語言一致。若是是其餘類型的值,Lua會嘗試調用元方法 lt
和 le
。
and,or,not
僅認爲 false
與 nil
爲假。
not
取反操做 not
的結果爲 boolean
類型。(and
和 or
的結果則不必定爲 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
+++
and
與 or
遵循短路原則,第二個操做數僅在須要的時候會進行求值操做。
例子
a = 5 x = a or jjjj() -- 雖而後面的函數並無定義,可是因爲不會執行,所以不會報錯。 print(a) -->5 print(x) -->5
經過上面這個例子,咱們應當對於邏輯操做有所警覺,由於這可能會引入一些未能及時預料到的錯誤。
..
鏈接兩個字符串(或者數字)成爲新的字符串。對於其餘類型,調用元方法 concat
。
#
對於字符串,長度爲字符串的字符個數。
對於表,經過尋找知足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
由低到高:
or and < > <= >= ~= == .. + - * / % not # - (unary) ^
冪運算>單目運算>四則運算>鏈接符>比較操做符>and>or
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
注意:
上面這兩條的存在使得上面的例子中 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
函數是一個表達式,其值爲 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
參數。這種形式是對方法
的模擬
Lua 中的函數調用的BNF語法以下:
functioncall ::= prefixexp args
若是 prefixexp 的值的類型是 function, 那麼這個函數就被用給出的參數調用。 不然 prefixexp 的元方法 "call" 就被調用, call 的第一個參數就是 prefixexp 的值,接下來的是 args 參數列表(參見 2.8 元表 | Metatable)。
函數調用根據是否傳入 self
參數分爲 .
調用和 :
調用。
函數調用根據傳入參數的類型,能夠分爲參數列表調用、表調用、字符串調用。
[待完善]
若是一個函數訪問了它的外部變量,那麼它就是一個閉包。
因爲函數內部的變量均爲局部變量,外界沒法對其進行訪問。這時若是外界想要改變局部變量的值,那麼就可使用閉包來實現這一目的。
具體的實現過程大體是這樣,函數內部有可以改變局部變量的子函數,函數將這個子函數返回,那麼外界就能夠經過使用這個子函數來操做局部變量了。
例子:利用閉包來實現對局部變量進行改變
-- 實現一個迭代器 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.3 變量 部分。
[待補充]
咱們可使用操做符對 Lua 的值進行運算,例如對數值類型的值進行加減乘除的運算操做以及對字符串的鏈接、取長操做等(在 2.5 表達式 這一節中介紹了許多相似的運算)。元表正是定義這些操做行爲的地方。
元表本質上是一個普通 Lua 表。元表中的鍵用來指定操做,稱爲「事件名」;元表中鍵所關聯的值稱爲「元方法」,定義操做的行爲。
僅表(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
等。
下面進行詳細介紹。
每一個值均可以擁有一個元表。對 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
事先提醒 Lua 使用 raw
前綴的函數來操做元方法,避免元方法的循環調用。
例如 Lua 獲取對象 obj 中元方法的過程以下:
rawget(getmetatable(obj)or{}, "__"..event_name)
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 用於賦值操做 -- 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 事件用於函數調用 -- 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
類型 thread
、function
和 userdata
的對象除了能與元表創建關聯外,還能關聯一個環境表。
關聯在線程上的環境表稱爲全局環境。
全局環境做爲子線程及子函數的默認環境。
全局環境可以直接被 C 調用。
關聯在 Lua 函數上的環境表接管函數對全局變量的全部訪問。而且做爲子函數的默認環境。
關聯在 C 函數上的環境能直接被 C 調用。
關聯在 userdata
上的環境沒有實際的用途,只是爲了方便程序員把一個表關聯到 userdata
上。
[待補充]
弱表是包含弱引用的表。
弱表的弱引用方式有三種。鍵弱引用,值弱引用,鍵和值均弱引用。
能夠經過元表中的 __mode
域來設置一個表是否有弱引用,以及弱引用的方式。
a = {} b = { __mode = "k"} -- 引號中添加 k 表示 key 弱引用,v 表示 value 弱引用, kv 表示均弱引用。 setmetable(a,b) -- b 是 a 的元表,綁定後就不能在更改 __mode 的值。
垃圾回收機制會把弱引用的部分回收。可是不管是哪一種弱引用,回收機制都會把整個鍵值對從弱表中移除。
這部分描述 Lua 的 C API,即用來與 Lua 進行通訊的 C 函數,全部的函數和常量都定義在 lua.h
頭文件裏面。
有一部分 C 函數是用宏來實現的。~~爲何?:因爲全部的宏只會使用他們的參數一次(除了第一個參數,即 Lua 狀態機),因此沒必要擔憂宏展開帶來的反作用。~~
默認狀況下 Lua 在進行函數調用時不會檢查函數的有效性和堅固性,若是想要進行檢查,則使用 luaconf.h
中的 luai_apicheck()
函數開啓。
Lua 調用 C API 時使用一個虛擬棧來傳遞參數,棧中的全部元素都是 Lua 的類型(例如 boolean
,table
,nil
等)。
Lua 調用 C 函數的時候都會新建一個虛擬棧,而不是使用舊棧或者其餘的棧。同時在 C 函數中,對 Lua API 調用時,只能使用當前調用所對應棧中的元素,其餘棧的元素是沒法訪問的。
虛擬棧中包含 C 函數所需的全部參數,函數的返回值也都放在該棧中。
這裏所謂的棧概念並非嚴格意義上的棧,能夠經過下標對棧中的元素進行訪問。1表示棧底,-1表示棧頂,又例如 3 表示從棧底開始的第三個元素。
因爲 Lua 的 C API 默認不作有效性和堅固性(魯棒性)檢測,所以開發人員有責任保證堅固性。特別要注意的是,不能讓堆棧溢出。Lua 只保證棧大小會大於 LUA_MINSTACK
(通常是 20)。開發人員可使用 lua_checkstack
函數來手動設置棧的大小。
除了用索引訪問函數堆棧的 Lua 元素,C 代碼還可使用僞索引來訪問堆棧之外的 Lua 元素,例如線程的環境、註冊表、函數的環境 以及 C函數的 upvalue
(上值)。能夠經過特別聲明來禁用僞索引。
線程的環境放在僞索引 LUA_GLOBALSINDEX
處,函數的環境放在僞索引 LUA_ENVIRONINDEX
處。
訪問環境的方式跟訪問表的方式是一致的,例如要訪問全局變量的值,可使用:
lua_getfield(L,LUA_GLOBALSINDEX,varname)
當咱們把建立出來的函數和一些值關聯在一塊兒,就獲得了一個閉包。那些關聯起來的值稱爲 upvalue
(上值)。
函數的上值都放在特定的僞索引處,能夠經過 lua_upvalueindex
獲取上值的僞索引。例如 lua_upvalueindex(3)
表示獲取第三個關聯值(按照關聯順序排列)對應的僞索引。
Lua 提供了一個註冊表,C 代碼能夠用來存放想要存放的 Lua 值。註冊表用僞索引 LUA_REGISTRYINDEX
定位。
爲了不命名衝突,通常採用包含庫名的字符串做爲鍵名。~~什麼東西?:或者能夠取你本身 C 代碼 中的一個地址,以 light userdata 的形式作鍵。~~
註冊表中的整數鍵有特定用途(用於實現補充庫的引用系統),不建議用於其餘用途。
[待補充]
本節介紹 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使用(介紹了該環境的設置在實際中的一個應用)