LUA教程(游戲UI制作)二


2.5.5 - 取長度操作符
取長度操作符寫作一元操作 #。字符串的長度是它的字節數(就是以一個字符一個字節計算的字符串長度)。
table t 的長度被定義成一個整數下標 n 。它滿足 t[n] 不是 nil 而 t[n+1] 為 nil;此外,如果 t[1] 為 nil ,n 就可能是零。對於常規的數組,里面從 1 到 n 放着一些非空的值的時候,它的長度就精確的為 n,即最后一個值的下標。如果數組有一個“空洞” (就是說,nil 值被夾在非空值之間),那么 #t 可能是任何一個是 nil 值的位置的下標(就是說,任何一個 nil 值都有可能被當成數組的結束)。
2.5.6 - 優先級
Lua 中操作符的優先級寫在下表中,從低到高優先級排序:
     or
     and
     <     >     <=    >=    ~=    ==
     ..
     +     -
     *     /     %
     not   #     - (unary)
     ^
通常,你可以用括號來改變運算次序。連接操作符 ('..') 和冪操作 ('^') 是從右至左的。其它所有的操作都是從左至右。
2.5.7 - Table 構造
table 構造子是一個構造 table 的表達式。每次構造子被執行,都會構造出一個新的 table 。構造子可以被用來構造一個空的 table,也可以用來構造一個 table 並初始化其中的一些域。一般的構造子的語法如下
        tableconstructor ::= `{′ [fieldlist] `}′
        fieldlist ::= field {fieldsep field} [fieldsep]
        field ::= `[′ exp `]′ `=′ exp | Name `=′ exp | exp
        fieldsep ::= `,′ | `;′
每個形如 [exp1] = exp2 的域向 table 中增加新的一項,其鍵值為 exp1 而值為 exp2。形如 name = exp 的域等價於 ["name"] = exp。最后,形如 exp 的域等價於 = exp , 這里的 i 是一個從 1 開始不斷增長的數字。這這個格式中的其它域不會破壞其記數。舉個例子:
     a = { [f(1)] = g; "x", "y"; x = 1, f(x), [30] = 23; 45 }
等價於
     do
       local t = {}
       t[f(1)] = g
       t[1] = "x"         -- 1st exp
       t[2] = "y"         -- 2nd exp
       t.x = 1            -- t["x"] = 1
       t[3] = f(x)        -- 3rd exp
       t[30] = 23
       t[4] = 45          -- 4th exp
       a = t
     end
如果表單中最后一個域的形式是 exp ,而且其表達式是一個函數調用或者是一個可變參數,那么這個表達式所有的返回值將連續的進入列表(參見 §2.5.8)。為了避免這一點,你可以用括號把函數調用(或是可變參數)括起來(參見 §2.5)。
初始化域表可以在最后多一個分割符,這樣設計可以方便由機器生成代碼。
2.5.8 - 函數調用
Lua 中的函數調用的語法如下:
        functioncall ::= prefixexp args
函數調用時,第一步,prefixexp 和 args 先被求值。如果 prefixexp 的值的類型是 function,那么這個函數就被用給出的參數調用。否則 prefixexp 的元方法 "call" 就被調用,第一個參數就是 prefixexp 的值,跟下來的是原來的調用參數(參見 §2.8)。
這樣的形式
        functioncall ::= prefixexp `:′ Name args
可以用來調用 "方法"。這是 Lua 支持的一種語法糖。像 v:name(args) 這個樣子,被解釋成 v.name(v,args),這里 v 只會被求值一次。
參數的語法如下:
        args ::= `(′ [explist1] `)′
        args ::= tableconstructor
        args ::= String
所有參數的表達式求值都在函數調用之前。這樣的調用形式 f{fields} 是一種語法糖用於表示 f({fields});這里指參數列表是一個單一的新創建出來的列表。而這樣的形式 f'string' (或是 f"string" 亦或是 f[[string]])也是一種語法糖,用於表示 f('string');這里指參數列表是一個單獨的字符串。
因為表達式語法在 Lua 中比較自由,所以你不能在函數調用的 '(' 前換行。這個限制可以避免語言中的一些歧義。比如你這樣寫
     a = f
     (g).x(a)
Lua 將把它當作一個單一語句段, a = f(g).x(a) 。因此,如果你真的想作為成兩個語句段,你必須在它們之間寫上一個分號。如果你真的想調用 f,你必須從 (g) 前移去換行。
這樣一種調用形式:return functioncall 將觸發一個尾調用。 Lua 實現了適當的尾部調用(或是適當的尾遞歸):在尾調用中,被調用的函數重用調用它的函數的堆棧項。因此,對於程序執行的嵌套尾調用的層數是沒有限制的。然而,尾調用將刪除調用它的函數的任何調試信息。注意,尾調用只發生在特定的語法下,這時, return 只有單一函數調用作為參數;這種語法使得調用函數的結果可以精確返回。因此,下面這些例子都不是尾調用:
     return (f(x))        -- 返回值被調整為一個
     return 2 * f(x)
     return x, f(x)       -- 最加若干返回值
     f(x); return         -- 無返回值
     return x or f(x)     -- 返回值被調整為一個
2.5.9 - 函數定義
函數定義的語法如下:
        function ::= function funcbody
        funcbody ::= `(′ [parlist1] `)′ block end
另外定義了一些語法糖簡化函數定義的寫法:
        stat ::= function funcname funcbody
        stat ::= local function Name funcbody
        funcname ::= Name {`.′ Name} [`:′ Name]
這樣的寫法:
     function f () body end
被轉換成
     f = function () body end
這樣的寫法:
     function t.a.b.c.f () body end
被轉換成
     t.a.b.c.f = function () body end
這樣的寫法:
     local function f () body end
被轉換成
     local f; f = function () body end
注意,並不是轉換成
     local f = function () body end
(這個差別只在函數體內需要引用 f 時才有。)
一個函數定義是一個可執行的表達式,執行結果是一個類型為 function 的值。當 Lua 預編譯一個 chunk 的時候, chunk 作為一個函數,整個函數體也就被預編譯了。那么,無論何時 Lua 執行了函數定義,這個函數本身就被實例化了(或者說是關閉了)。這個函數的實例(或者說是 closure(閉包))是表達式的最終值。相同函數的不同實例有可能引用不同的外部局部變量,也可能擁有不同的環境表。
形參(函數定義需要的參數)是一些由實參(實際傳入參數)的值初始化的局部變量:
        parlist1 ::= namelist [`,′ `...′] | `...′
當一個函數被調用,如果函數沒有被定義為接收不定長參數,即在形參列表的末尾注明三個點 ('...'),那么實參列表就會被調整到形參列表的長度,變長參數函數不會調整實參列表;取而代之的是,它將把所有額外的參數放在一起通過變長參數表達式傳遞給函數,其寫法依舊是三個點。這個表達式的值是一串實參值的列表,看起來就跟一個可以返回多個結果的函數一樣。如果一個變長參數表達式放在另一個表達式中使用,或是放在另一串表達式的中間,那么它的返回值就會被調整為單個值。若這個表達式放在了一系列表達式的最后一個,就不會做調整了(除非用括號給括了起來)。
我們先做如下定義,然后再來看一個例子:
     function f(a, b) end
     function g(a, b, ...) end
     function r() return 1,2,3 end
下面看看實參到形參數以及可變長參數的映射關系:
     CALL            PARAMETERS
    
     f(3)             a=3, b=nil
     f(3, 4)          a=3, b=4
     f(3, 4, 5)       a=3, b=4
     f(r(), 10)       a=1, b=10
     f(r())           a=1, b=2
    
     g(3)             a=3, b=nil, ... -->  (nothing)
     g(3, 4)          a=3, b=4,   ... -->  (nothing)
     g(3, 4, 5, 8)    a=3, b=4,   ... -->  5  8
     g(5, r())        a=5, b=1,   ... -->  2  3
結果由 return 來返回(參見 §2.4.4)。如果執行到函數末尾依舊沒有遇到任何 return 語句,函數就不會返回任何結果。
冒號語法可以用來定義方法,就是說,函數可以有一個隱式的形參 self。因此,如下寫法:
     function t.a.b.c:f (params) body end
是這樣一種寫法的語法糖:
     t.a.b.c.f = function (self, params) body end
2.6 - 可視規則
Lua 是一個有詞法作用范圍的語言。變量的作用范圍開始於聲明它們之后的第一個語句段,結束於包含這個聲明的最內層語句塊的結束點。看下面這些例子:
     x = 10                -- 全局變量
     do                    -- 新的語句塊
       local x = x         -- 新的一個 'x', 它的值現在是 10
       print(x)            --> 10
       x = x+1
       do                  -- 另一個語句塊
         local x = x+1     -- 又一個 'x'
         print(x)          --> 12
       end
       print(x)            --> 11
     end
     print(x)              --> 10  (取到的是全局的那一個)
注意這里,類似 local x = x 這樣的聲明,新的 x 正在被聲明,但是還沒有進入它的作用范圍,所以第二個 x 指向的是外面一層的變量。
因為有這樣一個詞法作用范圍的規則,所以可以在函數內部自由的定義局部變量並使用它們。當一個局部變量被更內層的函數中使用的時候,它被內層函數稱作 upvalue(上值),或是 外部局部變量。
注意,每次執行到一個 local 語句都會定義出一個新的局部變量。看看這樣一個例子:
     a = {}
     local x = 20
     for i=1,10 do
       local y = 0
       a = function () y=y+1; return x+y end
     end
這個循環創建了十個 closure(這指十個匿名函數的實例)。這些 closure 中的每一個都使用了不同的 y 變量,而它們又共享了同一份 x。
2.7 - 錯誤處理
因為 Lua 是一個嵌入式的擴展語言,所有的 Lua 動作都是從宿主程序的 C 代碼調用 Lua 庫(參見 lua_pcall)中的一個函數開始的。在 Lua 編譯或運行的任何時候發生了錯誤,控制權都會交還給 C ,而 C 可以來做一些恰當的措施(比如打印出一條錯誤信息)。
Lua 代碼可以顯式的調用 error 函數來產生一條錯誤。如果你需要在 Lua 中捕獲發生的錯誤,你可以使用 pcall 函數。
2.8 - Metatable(元表)
Lua 中的每個值都可以用一個 metatable。這個 metatable 就是一個原始的 Lua table ,它用來定義原始值在特定操作下的行為。你可以通過在 metatable 中的特定域設一些值來改變擁有這個 metatable 的值的指定操作之行為。舉例來說,當一個非數字的值作加法操作的時候, Lua 會檢查它的 metatable 中 "__add" 域中的是否有一個函數。如果有這么一個函數的話,Lua 調用這個函數來執行一次加法。
我們叫 metatable 中的鍵名為 事件 (event) ,把其中的值叫作 元方法 (metamethod)。在上個例子中,事件是 "add" 而元方法就是那個執行加法操作的函數。
你可以通過 getmetatable 函數來查詢到任何一個值的 metatable。
你可以通過 setmetatable 函數來替換掉 table 的 metatable 。你不能從 Lua 中改變其它任何類型的值的 metatable (使用 debug 庫例外);要這樣做的話必須使用 C API 。
每個 table 和 userdata 擁有獨立的 metatable (當然多個 table 和 userdata 可以共享一個相同的表作它們的 metatable);其它所有類型的值,每種類型都分別共享唯一的一個 metatable。因此,所有的數字一起只有一個 metatable ,所有的字符串也是,等等。
一個 metatable 可以控制一個對象做數學運算操作、比較操作、連接操作、取長度操作、取下標操作時的行為, metatable 中還可以定義一個函數,讓 userdata 作垃圾收集時調用它。對於這些操作,Lua 都將其關聯上一個被稱作事件的指定健。當 Lua 需要對一個值發起這些操作中的一個時,它會去檢查值中 metatable 中是否有對應事件。如果有的話,鍵名對應的值(元方法)將控制 Lua 怎樣做這個操作。
metatable 可以控制的操作已在下面列出來。每個操作都用相應的名字區分。每個操作的鍵名都是用操作名字加上兩個下划線 '__' 前綴的字符串;舉例來說,"add" 操作的鍵名就是字符串 "__add"。這些操作的語義用一個 Lua 函數來描述解釋器如何執行更為恰當。
這里展示的用 Lua 寫的代碼僅作解說用;實際的行為已經硬編碼在解釋器中,其執行效率要遠高於這些模擬代碼。這些用於描述的的代碼中用到的函數( rawget , tonumber ,等等。)都可以在 §5.1 中找到。特別注意,我們使用這樣一個表達式來從給定對象中提取元方法
     metatable(obj)[event]
這個應該被解讀作
     rawget(getmetatable(obj) or {}, event)
這就是說,訪問一個元方法不再會觸發任何的元方法,而且訪問一個沒有 metatable 的對象也不會失敗(而只是簡單返回 nil)。
?        "add": + 操作。
下面這個 getbinhandler 函數定義了 Lua 怎樣選擇一個處理器來作二元操作。首先,Lua 嘗試第一個操作數。如果這個東西的類型沒有定義這個操作的處理器,然后 Lua 會嘗試第二個操作數。
     function getbinhandler (op1, op2, event)
       return metatable(op1)[event] or metatable(op2)[event]
     end
通過這個函數, op1 + op2 的行為就是
     function add_event (op1, op2)
       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
?        "sub": - 操作。 其行為類似於 "add" 操作。
?        "mul": * 操作。 其行為類似於 "add" 操作。
?        "div": / 操作。 其行為類似於 "add" 操作。
?        "mod": % 操作。 其行為類似於 "add" 操作,它的原生操作是這樣的 o1 - floor(o1/o2)*o2
?        "pow": ^ (冪)操作。 其行為類似於 "add" 操作,它的原生操作是調用 pow 函數(通過 C math 庫)。
?        "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
?        "concat": .. (連接)操作,
?             function concat_event (op1, op2)
?               if (type(op1) == "string" or type(op1) == "number") and
?                  (type(op2) == "string" or type(op2) == "number") then
?                 return op1 .. op2  -- 原生字符串連接
?               else
?                 local h = getbinhandler(op1, op2, "__concat")
?                 if h then
?                   return h(op1, op2)
?                 else
?                   error(???)
?                 end
?               end
?             end
?        "len": # 操作。
?             function len_event (op)
?               if type(op) == "string" then
?                 return strlen(op)         -- 原生的取字符串長度
?               elseif type(op) == "table" then
?                 return #op                -- 原生的取 table 長度
?               else
?                 local h = metatable(op).__len
?                 if h then
?                   -- 調用操作數的處理器
?                   return h(op)
?                 else  -- 沒有處理器:缺省行為
?                   error(???)
?                 end
?               end
?             end
關於 table 的長度參見 §2.5.5 。
?        "eq": == 操作。 函數 getcomphandler 定義了 Lua 怎樣選擇一個處理器來作比較操作。元方法僅僅在參於比較的兩個對象類型相同且有對應操作相同的元方法時才起效。
?             function getcomphandler (op1, op2, event)
?               if type(op1) ~= type(op2) then return nil end
?               local mm1 = metatable(op1)[event]
?               local mm2 = metatable(op2)[event]
?               if mm1 == mm2 then return mm1 else return nil end
?             end
"eq" 事件按如下方式定義:
     function eq_event (op1, op2)
       if type(op1) ~= type(op2) then  -- 不同的類型?
         return false   -- 不同的對象
       end
       if op1 == op2 then   -- 原生的相等比較結果?
         return true   -- 對象相等
       end
       -- 嘗試使用元方法
       local h = getcomphandler(op1, op2, "__eq")
       if h then
         return h(op1, op2)
       else
         return false
       end
     end
a ~= b 等價於 not (a == b) 。
?        "lt": < 操作。
?             function lt_event (op1, op2)
?               if type(op1) == "number" and type(op2) == "number" then
?                 return op1 < op2   -- 數字比較
?               elseif type(op1) == "string" and type(op2) == "string" then
?                 return op1 < op2   -- 字符串按逐字符比較
?               else
?                 local h = getcomphandler(op1, op2, "__lt")
?                 if h then
?                   return h(op1, op2)
?                 else
?                   error(???);
?                 end
?               end
?             end
a > b 等價於 b < a.
?        "le": <= 操作。
?             function le_event (op1, op2)
?               if type(op1) == "number" and type(op2) == "number" then
?                 return op1 <= op2   -- 數字比較
?               elseif type(op1) == "string" and type(op2) == "string" then
?                 return op1 <= op2   -- 字符串按逐字符比較
?               else
?                 local h = getcomphandler(op1, op2, "__le")
?                 if h then
?                   return h(op1, op2)
?                 else
?                   h = getcomphandler(op1, op2, "__lt")
?                   if h then
?                     return not h(op2, op1)
?                   else
?                     error(???);
?                   end
?                 end
?               end
?             end
a >= b 等價於 b <= a 。注意,如果元方法 "le" 沒有提供,Lua 就嘗試 "lt" ,它假定 a <= b 等價於 not (b < a) 。
?        "index": 取下標操作用於訪問 table[key] 。
?             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
?        "newindex": 賦值給指定下標 table[key] = value 。
?             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
?        "call": 當 Lua 調用一個值時調用。
?             function function_event (func, ...)
?               if type(func) == "function" then
?                 return func(...)   -- 原生的調用
?               else
?                 local h = metatable(func).__call
?                 if h then
?                   return h(func, ...)
?                 else
?                   error(???)
?                 end
?               end
?             end
2.9 - 環境
類型為 thread ,function ,以及 userdata 的對象,除了 metatable 外還可以用另外一個與之關聯的被稱作它們的環境的一個表,像 metatable 一樣,環境也是一個常規的 table ,多個對象可以共享同一個環境。
userdata 的環境在 Lua 中沒有意義。這個東西只是為了在程序員想把一個表關聯到一個 userdata 上時提供便利。
關聯在線程上的環境被稱作全局環境。全局環境被用作它其中的線程以及線程創建的非嵌套函數(通過 loadfile , loadstring 或是 load )的缺省環境。而且它可以被 C 代碼直接訪問(參見 §3.3)。
關聯在 C 函數上的環境可以直接被 C 代碼訪問(參見 §3.3)。它們會作為這個 C 函數中創建的其它函數的缺省環境。
關聯在 Lua 函數上的環境用來接管在函數內對全局變量(參見 §2.3)的所有訪問。它們也會作為這個函數內創建的其它函數的缺省環境。
你可以通過調用 setfenv 來改變一個 Lua 函數或是正在運行中的線程的環境。而想操控其它對象(userdata、C 函數、其它線程)的環境的話,就必須使用 C API 。
2.10 - 垃圾收集
Lua 提供了一個自動的內存管理。這就是說你不需要關心創建新對象的分配內存操作,也不需要在這些對象不再需要時的主動釋放內存。 Lua 通過運行一個垃圾收集器來自動管理內存,以此一遍又一遍的回收死掉的對象(這是指 Lua 中不再訪問的到的對象)占用的內存。 Lua 中所有對象都被自動管理,包括: table, userdata、 函數、線程、和字符串。
Lua 實現了一個增量標記清除的收集器。它用兩個數字來控制垃圾收集周期: garbage-collector pause 和 garbage-collector step multiplier 。
garbage-collector pause 控制了收集器在開始一個新的收集周期之前要等待多久。隨着數字的增大就導致收集器工作工作的不那么主動。小於 1 的值意味着收集器在新的周期開始時不再等待。當值為 2 的時候意味着在總使用內存數量達到原來的兩倍時再開啟新的周期。
step multiplier 控制了收集器相對內存分配的速度。更大的數字將導致收集器工作的更主動的同時,也使每步收集的尺寸增加。小於 1 的值會使收集器工作的非常慢,可能導致收集器永遠都結束不了當前周期。缺省值為 2 ,這意味着收集器將以內存分配器的兩倍速運行。
你可以通過在 C 中調用 lua_gc 或是在 Lua 中調用 collectgarbage 來改變這些數字。兩者都接受百分比數值(因此傳入參數 100 意味着實際值 1 )。通過這些函數,你也可以直接控制收集器(例如,停止或是重啟)。
2.10.1 - 垃圾收集的元方法
使用 C API ,你可以給 userdata (參見 §2.8)設置一個垃圾收集的元方法。這個元方法也被稱為結束子。結束子允許你用額外的資源管理器和 Lua 的內存管理器協同工作(比如關閉文件、網絡連接、或是數據庫連接,也可以說釋放你自己的內存)。
一個 userdata 可被回收,若它的 metatable 中有 __gc 這個域 ,垃圾收集器就不立即收回它。取而代之的是,Lua 把它們放到一個列表中。最收集結束后,Lua 針對列表中的每個 userdata 執行了下面這個函數的等價操作:
     function gc_event (udata)
       local h = metatable(udata).__gc
       if h then
         h(udata)
       end
     end
在每個垃圾收集周期的結尾,每個在當前周期被收集起來的 userdata 的結束子會以它們構造時的逆序依次調用。也就是說,收集列表中,最后一個在程序中被創建的 userdata 的結束子會被第一個調用。
2.10.2 - Weak Table(弱表)
weak table 是一個這樣的 table,它其中的元素都被弱引用。弱引用將被垃圾收集器忽略掉,換句話說,如果對一個對象的引用只有弱引用,垃圾收集器將回收這個對象。
weak table 的鍵和值都可以是 weak 的。如果一個 table 只有鍵是 weak 的,那么將運行收集器回收它們的鍵,但是會阻止回收器回收對應的值。而一個 table 的鍵和值都是 weak 時,就即允許收集器回收鍵又允許收回值。任何情況下,如果鍵和值中任一個被回收了,整個鍵值對就會從 table 中拿掉。 table 的 weak 特性可以通過在它的 metatable 中設置 __mode 域來改變。如果 __mode 域中是一個包含有字符 'k' 的字符串時, table 的鍵就是 weak 的。如果 __mode 域中是一個包含有字符 'v' 的字符串時, table 的值就是 weak 的。
在你把一個 table 當作一個 metatable 使用之后,就不能再修改 __mode 域的值。否則,受這個 metatable 控制的 table 的 weak 行為就成了未定義的。
2.11 - Coroutine (協同例程)
Lua 支持 coroutine ,這個東西也被稱為協同式多線程 (collaborative multithreading) 。 Lua 為每個 coroutine 提供一個獨立的運行線路。然而和多線程系統中的線程不同,coroutine 只在顯式的調用了 yield 函數時才會掛起。
創建一個 coroutine 需要調用一次 coroutine.create 。它只接收單個參數,這個參數是 coroutine 的主函數。 create 函數僅僅創建一個新的 coroutine 然后返回它的控制器(一個類型為 thread 的對象);它並不會啟動 coroutine 的運行。
當你第一次調用 coroutine.resume 時,所需傳入的第一個參數就是 coroutine.create 的返回值。這時,coroutine 從主函數的第一行開始運行。接下來傳入 coroutine.resume 的參數將被傳進 coroutine 的主函數。在 coroutine 開始運行后,它講運行到自身終止或是遇到一個 yields 。
coroutine 可以通過兩種方式來終止運行:一種是正常退出,指它的主函數返回(最后一條指令被運行后,無論有沒有顯式的返回指令); 另一種是非正常退出,它發生在未保護的錯誤發生的時候。第一種情況中, coroutine.resume 返回 true ,接下來會跟着 coroutine 主函數的一系列返回值。第二種發生錯誤的情況下, coroutine.resume 返回 false ,緊接着是一條錯誤信息。
coroutine 中切換出去,可以調用 coroutine.yield。當 coroutine 切出,與之配合的 coroutine.resume 就立即返回,甚至在 yield 發生在內層的函數調用中也可以(就是說,這不限於發生在主函數中,也可以是主函數直接或間接調用的某個函數里)。在 yield 的情況下,coroutine.resume 也是返回 true,緊跟着那些被傳入 coroutine.yield 的參數。等到下次你在繼續同樣的 coroutine ,將從調用 yield 的斷點處運行下去。斷點處 yield 的返回值將是 coroutine.resume 傳入的參數。
類似 coroutine.create , coroutine.wrap 這個函數也將創建一個 coroutine ,但是它並不返回 coroutine 本身,而是返回一個函數取而代之。一旦你調用這個返回函數,就會切入 coroutine 運行。所有傳入這個函數的參數等同於傳入 coroutine.resume 的參數。 coroutine.wrap 會返回所有應該由除第一個(錯誤代碼的那個布爾量)之外的由 coroutine.resume 返回的值。和 coroutine.resume 不同, coroutine.wrap 不捕獲任何錯誤;所有的錯誤都應該由調用者自己傳遞。
看下面這段代碼展示的一個例子:
     function foo (a)
       print("foo", a)
       return coroutine.yield(2*a)
     end
    
     co = coroutine.create(function (a,b)
           print("co-body", a, b)
           local r = foo(a+1)
           print("co-body", r)
           local r, s = coroutine.yield(a+b, a-b)
           print("co-body", r, s)
           return b, "end"
     end)
           
     print("main", coroutine.resume(co, 1, 10))
     print("main", coroutine.resume(co, "r"))
     print("main", coroutine.resume(co, "x", "y"))
     print("main", coroutine.resume(co, "x", "y"))
當你運行它,將得到如下輸出結果:
     co-body 1       10
     foo     2
    
     main    true    4
     co-body r
     main    true    11      -9
     co-body x       y
     main    true    10      end
     main    false   cannot resume dead coroutine
3 - 程序接口(API)
這個部分描述了 Lua 的 C API ,也就是宿主程序跟 Lua 通訊用的一組 C 函數。所有的 API 函數按相關的類型以及常量都聲明在頭文件 lua.h 中。
雖然我們說的是“函數”,但一部分簡單的 API 是以宏的形式提供的。所有的這些宏都只使用它們的參數一次(除了第一個參數,也就是 lua 狀態機),因此你不需擔心這些宏的展開會引起一些副作用。
在所有的 C 庫中,Lua API 函數都不去檢查參數的有效性和堅固性。然而,你可以在編譯 Lua 時加上打開一個宏開關來開啟 luaconf.h 文件中的宏 luai_apicheck 以改變這個行為。
3.1 - 堆棧
Lua 使用一個虛擬棧來和 C 傳遞值。棧上的的每個元素都是一個 Lua 值(nil,數字,字符串,等等)。
無論何時 Lua 調用 C,被調用的函數都得到一個新的棧,這個棧獨立於 C 函數本身的堆棧,也獨立於以前的棧。(譯注:在 C 函數里,用 Lua API 不能訪問到 Lua 狀態機中本次調用之外的堆棧中的數據)它里面包含了 Lua 傳遞給 C 函數的所有參數,而 C 函數則把要返回的結果也放入堆棧以返回給調用者(參見 lua_CFunction)。
方便起見,所有針對棧的 API 查詢操作都不嚴格遵循棧的操作規則。而是可以用一個索引來指向棧上的任何元素:正的索引指的是棧上的絕對位置(從一開始);負的索引則指從棧頂開始的偏移量。更詳細的說明一下,如果堆棧有 n 個元素,那么索引 1 表示第一個元素(也就是最先被壓入堆棧的元素)而索引 n 則指最后一個元素;索引 -1 也是指最后一個元素(即棧頂的元素),索引 -n 是指第一個元素。如果索引在 1 到棧頂之間(也就是,1 ≤ abs(index) ≤ top)我們就說這是個有效的索引。
3.2 - 堆棧尺寸
當你使用 Lua API 時,就有責任保證其堅固性。特別需要注意的是,你有責任控制不要堆棧溢出。你可以使用 lua_checkstack 這個函數來擴大可用堆棧的尺寸。
無論何時 Lua 調用 C ,它都只保證 LUA_MINSTACK 這么多的堆棧空間可以使用。 LUA_MINSTACK 一般被定義為 20 ,因此,只要你不是不斷的把數據壓棧,通常你不用關心堆棧大小。
所有的查詢函數都可以接收一個索引,只要這個索引是任何棧提供的空間中的值。棧能提供的最大空間是通過 lua_checkstack 來設置的。這些索引被稱作可接受的索引,通常我們把它定義為:
     (index < 0 && abs(index) <= top) ||
     (index > 0 && index <= stackspace)
注意,0 永遠都不是一個可接受的索引。(譯注:下文中凡提到的索引,沒有特別注明的話,都指可接受的索引。)
3.3 - 偽索引
除了特別聲明外,任何一個函數都可以接受另一種有效的索引,它們被稱作“偽索引”。這個可以幫助 C 代碼訪問一些並不在棧上的 Lua 值。偽索引被用來訪問線程的環境,函數的環境,注冊表,還有 C 函數的 upvalue (參見 §3.4)。
線程的環境(也就是全局變量放的地方)通常在偽索引 LUA_GLOBALSINDEX 處。正在運行的 C 函數的環境則放在偽索引 LUA_ENVIRONINDEX 之處。
你可以用常規的 table 操作來訪問和改變全局變量的值,只需要指定環境表的位置。舉例而言,要訪問全局變量的值,這樣做:
     lua_getfield(L, LUA_GLOBALSINDEX, varname);
3.4 - C Closure
當 C 函數被創建出來,我們有可能會把一些值關聯在一起,也就是創建一個 C closure ;這些被關聯起來的值被叫做 upvalue ,它們可以在函數被調用的時候訪問的到。(參見 lua_pushcclosure)。
無論何時去調用 C 函數,函數的 upvalue 都被放在指定的偽索引處。我們可以用 lua_upvalueindex 這個宏來生成這些偽索引。第一個關聯到函數的值放在 lua_upvalueindex(1) 位置處,依次類推。任何情況下都可以用 lua_upvalueindex(n) 產生一個 upvalue 的索引,即使 n 大於實際的 upvalue 數量也可以。它都可以產生一個可接受但不一定有效的索引。
3.5 - 注冊表
Lua 提供了一個注冊表,這是一個預定義出來的表,可以用來保存任何 C 代碼想保存的 Lua 值。這個表可以用偽索引 LUA_REGISTRYINDEX 來定位。任何 C 庫都可以在這張表里保存數據,為了防止沖突,你需要特別小心的選擇鍵名。一般的用法是,你可以用一個包含你的庫名的字符串做為鍵名,或者可以取你自己 C 代碼中的一個地址,以 light userdata 的形式做鍵。
注冊表里的整數健被用於補充庫中實現的引用系統的工作,一般說來不要把它們用於別的用途。
3.6 - C 中的錯誤處理
在內部實現中,Lua 使用了 C 的 longjmp 機制來處理錯誤。(如果你使用 C++ 的話,也可以選擇換用異常;參見 luaconf.h 文件。)當 Lua 碰到任何錯誤(比如內存分配錯誤、類型錯誤、語法錯誤、還有一些運行時錯誤)它都會產生一個錯誤出去;也就是調用一個 long jump 。在保護環境下,Lua 使用 setjmp 來設置一個恢復點;任何發生的錯誤都會激活最近的一個恢復點。
幾乎所有的 API 函數都可能產生錯誤,例如內存分配錯誤。但下面的一些函數運行在保護環境中(也就是說它們創建了一個保護環境再在其中運行),因此它們不會產生錯誤出來: lua_newstate, lua_close, lua_load, lua_pcall, and lua_cpcall。
在 C 函數里,你也可以通過調用 lua_error 產生一個錯誤。
3.7 - 函數和類型
在這里我們按字母次序列出了所有 C API 中的函數和類型。
________________________________________
lua_Alloc
typedef void * (*lua_Alloc) (void *ud,
                             void *ptr,
                             size_t osize,
                             size_t nsize);
Lua 狀態機中使用的內存分配器函數的類型。內存分配函數必須提供一個功能類似於 realloc 但又不完全相同的函數。它的參數有 ud ,一個由 lua_newstate 傳給它的指針; ptr ,一個指向已分配出來或是將被重新分配或是要釋放的內存塊指針; osize ,內存塊原來的尺寸; nsize ,新內存塊的尺寸。如果且只有 osize 是零時,ptr 為 NULL 。當 nsize 是零,分配器必須返回 NULL;如果 osize 不是零,分配器應當釋放掉 ptr 指向的內存塊。當 nsize 不是零,若分配器不能滿足請求時,分配器返回 NULL 。當 nsize 不是零而 osize 是零時,分配器應該和 malloc 有相同的行為。當 nsize 和 osize 都不是零時,分配器則應和 realloc 保持一樣的行為。 Lua 假設分配器在 osize >= nsize 時永遠不會失敗。
這里有一個簡單的分配器函數的實現。這個實現被放在補充庫中,由 luaL_newstate 提供。
     static void *l_alloc (void *ud, void *ptr, size_t osize,
                                                size_t nsize) {
       (void)ud;  (void)osize;  /* not used */
       if (nsize == 0) {
         free(ptr);
         return NULL;
       }
       else
         return realloc(ptr, nsize);
     }
這段代碼假設 free(NULL) 啥也不影響,而且 realloc(NULL, size) 等價於 malloc(size)。這兩點是 ANSI C 保證的行為。
________________________________________
lua_atpanic
lua_CFunction lua_atpanic (lua_State *L, lua_CFunction panicf);
設置一個新的 panic (恐慌) 函數,並返回前一個。
如果在保護環境之外發生了任何錯誤, Lua 就會調用一個 panic 函數,接着調用 exit(EXIT_FAILURE),這樣就開始退出宿主程序。你的 panic 函數可以永遠不返回(例如作一次長跳轉)來避免程序退出。
panic 函數可以從棧頂取到出錯信息。


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com