1. 程式人生 > >lua裡的一些特殊方法和變數

lua裡的一些特殊方法和變數

1.pcall (f, arg1, ···):pcall在保護模式(protected mode)下執行函式內容,同時捕獲所有的異常和錯誤。若一切正常,pcall返回true以及“被執行函式”的返回值;否則返回false和錯誤資訊(打印出來即可)。Lua 程式碼可以顯式的呼叫 error 函式來產生一條錯誤。

Calls function f with the given arguments in protected mode. This means that any error inside f is not propagated; instead, pcall

 catches the error and returns a status code. Its first result is the status code (a boolean), which is true if the call succeeds without errors. In such case, pcall also returns all results from the call, after this first result. In case of any error, pcall returns false
 plus the error message.

2.xpcall 接受兩個引數:呼叫函式、錯誤處理函式。xpcall (f, err)

his function is similar to pcall, except that you can set a new error handler.

xpcall calls function f in protected mode, using err as the error handler. Any error inside f is not propagated; instead, xpcall

 catches the error, calls the err function with the original error object, and returns a status code. Its first result is the status code (a boolean), which is true if the call succeeds without errors. In this case, xpcall also returns all results from the call, after this first result. In case of any error, xpcall returns false plus the result from err.

當錯誤發生時,Lua會在棧釋放以前呼叫錯誤處理函式,因此可以使用debug庫收集錯誤相關資訊。常用的debug處理函式:debug.debug()和debug.traceback(),前者給出Lua的提示符,你可以自己動手察看錯誤發生時的情況;後者通過traceback建立更多的錯誤資訊,也是控制檯直譯器用來構建錯誤資訊的函式。你可以在任何時候呼叫debug.traceback獲取當前執行的traceback資訊。
3._G 儲存全域性變數的table

在Lua中,要宣告全域性變數很簡單,那就是定義變數的時候,前面不要加上local。這個神祕的全域性變數,其實本質上也是一個table,它把我們建立的全域性變數都儲存到一個table裡了。而這個table的名字是:_G

 

A global variable (not a function) that holds the global environment (that is, _G._G = _G). Lua itself does not use this variable; changing its value does not affect any environment, nor vice-versa. (Use setfenv to change environments.)

 -- 定義一個全域性變數

 number = "1";

 -- 用三種方式輸出變數的值

 print(number );

print(_G["number "]);

print(_G.number );

輸出結果如下:

1
1
1

4.改變函式的全域性變數環境——setfenv函式

setfenv函式就是用來改變某個函式範圍裡的全域性環境的,通俗地說,就是把某個函式範圍內的_G給弄沒了。

setfenv函式兩個引數分別代表:

1). 第一個引數,可以是即將要改變環境的函式,也可以是一個數字。數字1代表當前函式,數字2代表呼叫當前函式的函式,後面以此類推。

2).第二個引數,新的全域性環境table

  -- 定義一個全域性變數

  number= "123";

 -- 將當前全域性環境重新設定為新的table

 setfenv(1, {});

  -- 輸出值

 print(number);

如果現在執行程式碼,輸出結果將會是這樣的:

attempt to call global ‘print’ (a nil value)

為什麼?很出乎意料的臉print函式都無法找到了?

這是因為我們已經把當前函式範圍內的全域性變數環境改變了,全域性變數預設是儲存在_G中的,而現在的全域性變數是在一個新的table裡。目前這個table是空的,所以不存在任何全域性變數。

setfenv函式就是用來改變某個函式範圍裡的全域性環境的,通俗地說,就是把某個函式範圍內的_G給弄沒了。

setfenv函式兩個引數分別代表:

1). 第一個引數,可以是即將要改變環境的函式,也可以是一個數字。數字1代表當前函式,數字2代表呼叫當前函式的函式,後面以此類推。

2).第二個引數,新的全域性環境table。

5.保留原來的_G

現在連print函式都無法使用了,對於測試很不方便,我們可以做個小動作,把原來的_G保留起來。

如下程式碼:

 -- 定義一個全域性變數

 number= "123";   

-- 將當前全域性環境重新設定為新的table

 setfenv(1, {g = _G})

-- 輸出值

 g.print(number);

   -- 再次定義一個全域性變數

  gName = "456";

   -- 再次輸出值

 g.print(number);

  -- 輸出原來的值

  g.print(g.number);

只要在定義新的環境時,把_G作為一個欄位放到新的table裡,就可以呼叫原來的全域性變量了。

那麼,輸出結果如下:

nil
456
123

三次呼叫g.print函式的輸出結果都是不一樣的:

a.第一次,此時剛剛重新設定了全域性環境,這時候當前函式的全域性變數只有一個,那就是g,所以number的值是nil。

b.第二次,我們再一次對number進行賦值,此時,已經在新的環境中了,所以接下來輸出的number值是存在的。

c.第三次,這次輸出的是g.number的值,通過g呼叫的number值是原先的全域性環境裡的值,所以number的值仍然是最初的“123”。

6.最後使用__index元方法保留原來的_G

這裡還有一個小技巧分享一下,剛剛舉例保留_G,但是呼叫print等函式時還需要形如g.print的方式,有點礙事。

我們可以利用__index來解決這個問題,如下程式碼:

  -- 定義一個全域性變數

 number = "123";

 -- 一個table,即將成為新的環境 

 local newG = {};

  setmetatable(newG, {__index = _G});

 -- 將當前全域性環境重新設定為新的table

  setfenv(1, newG);   

  number = "456!"; 

 -- 輸出值

  print(number );

  print(_G.number );

我們給新的table設定一個元表,這個元表的__index元方法就是_G。

於是,當新的環境裡找不到print欄位時,就會去_G裡尋找。

輸出結果如下

456
123

第一次輸出的是新環境裡的number 值,第二次輸出的是原來環境裡的number 值,互不影響。

7.lua的require函式有兩個特性:1.require函式會搜尋目錄載入檔案.2.require會判斷是否檔案已經載入避免重複載入同一檔案。所以,這個函式只能載入一次檔案,當我們載入的lua檔案動態改變後,需要重複載入時,只需要在記載前呼叫下package.loaded[“檔名.lua”] = nil就ok了,然後再required(‘檔名.lua’)。

8.package.path和 package.cpath

package.path用於搜尋自己寫的庫檔案或者第三方的庫檔案。package.path    = "./script/?.lua;" .. package.path

package.cpath用於搜尋自己寫的so庫檔案或者第三方的so庫檔案或者dll檔案。 package.cpath    = "./?.dll;./script/?.dll;" .. package.cpath

對於自定義的模組,模組檔案不是放在哪個檔案目錄都行,函式 require 有它自己的檔案路徑載入策略,它會嘗試從 Lua 檔案或 C 程式庫中載入模組。

require 用於搜尋 Lua 檔案的路徑是存放在全域性變數 package.path 中,當 Lua 啟動後,會以環境變數 LUA_PATH 的值來初始這個環境變數。如果沒有找到該環境變數,則使用一個編譯時定義的預設路徑來初始化。

當然,如果沒有 LUA_PATH 這個環境變數,也可以自定義設定,在當前使用者根目錄下開啟 .profile 檔案(沒有則建立,開啟 .bashrc 檔案也可以)。

當我們呼叫 require("module") 時就會嘗試開啟檔案目錄去搜索目標。如果找到目標檔案,則會呼叫 package.loadfile 來載入模組。否則,就會去找 C 程式庫。搜尋的檔案路徑是從全域性變數 package.cpath 獲取,而這個變數則是通過環境變數 LUA_CPATH 來初始。搜尋的策略跟上面的一樣,只不過現在換成搜尋的是 so 或 dll 型別的檔案。如果找得到,那麼 require 就會通過 package.loadlib 來載入它。

用法:

1 只加載想要的目錄
package.path = "../myLuaTest/myLuaCode/?.lua;" 

2 增加目錄
package.path = "../myLuaTest/myLuaCode/?.lua;"..package.path  在原有的目錄上加上自己的。 

還有一種方法(並沒有試過)

例如把 "~/lua/" 路徑加入 LUA_PATH 環境變數裡:

#LUA_PATH
export LUA_PATH="~/lua/?.lua;;"

檔案路徑以 ";" 號分隔,最後的 2 個 ";;" 表示新加的路徑後面加上原來的預設路徑。

接著,更新環境變數引數,使之立即生效。

source ~/.profile

 8.module模組:module用來來定義模組

module(...) 操作就相當於一下操作。

local modname = ... 

local M = {}  

_G[modname] = M  

package.loaded[modname] = M     這步是為了消除末尾的return語句,將模組table直接賦值給package.loaded

--[[       和普通Lua程式塊一樣宣告外部函式。       --]]  

setfenv(1,M)  

有一點容易被忽略掉,module 指令執行完後,整個環境被壓棧,所以前面全域性的東西再看不見了。比如定義了一個 test 模組,使用

module("test")

後,下面不再看的見前面的全域性環境。如果在這個模組裡想呼叫 print 輸出除錯資訊怎麼辦呢?一個簡單的方法是

local print=print
module("test")

這樣 print 是一個 local 變數,下面也是可見的。或者可以用

local _G=_G
module("test")

那麼 _G.print 也是可以用的。

注意:關於module( ... , package.seeall)。 一般在一個Lua檔案內以module函式開始定義一個包。module同時定義了一個新的包的函式環境,以使在此包中定義的全域性變數都在這個環境中,而非使用包的函式的環境中。理解這一點非常關鍵。 “module(..., package.seeall)”的意思是定義一個包,包的名字與定義包的檔案的名字相同,並且在包的函式環境裡可以訪問使用包的函式環境。使用方式:一般用require函式來匯入一個包,要匯入的包必須被置於包路徑(packagepath)上。包路徑可以通過package.path或者環境變數來設定。一般來說,當前工作路徑總是在包路徑中。

再呼叫module函式時,多傳入一個package.seeall的引數,相當於 setmetatable(M, {__index = _G}) 這樣全域性的_G就不會消失。

9. collectgarbage:用來控制自動記憶體管理,當設定了setstepmul和setpause,Lua便會開啟自動垃圾回收。

Lua 實現了一個增量標記-掃描收集器。 它使用這兩個數字來控制垃圾收集迴圈: 垃圾收集器間歇率和垃圾收集器步進倍率。 這兩個數字都使用百分數為單位 (例如:值 100 在內部表示 1 )。

垃圾收集器間歇率控制著收集器需要在開啟新的迴圈前要等待多久。 增大這個值會減少收集器的積極性。 當這個值比 100 小的時候,收集器在開啟新的迴圈前不會有等待。 設定這個值為 200 就會讓收集器等到總記憶體使用量達到 之前的兩倍時才開始新的迴圈。

垃圾收集器步進倍率控制著收集器運作速度相對於記憶體分配速度的倍率。 增大這個值不僅會讓收集器更加積極,還會增加每個增量步驟的長度。 不要把這個值設得小於 100 , 那樣的話收集器就工作的太慢了以至於永遠都幹不完一個迴圈。 預設值是 200 ,這表示收集器以記憶體分配的"兩倍"速工作。

如果你把步進倍率設為一個非常大的數字 (比你的程式可能用到的位元組數還大 10% ), 收集器的行為就像一個 stop-the-world 收集器。 接著你若把間歇率設為 200 , 收集器的行為就和過去的 Lua 版本一樣了: 每次 Lua 使用的記憶體翻倍時,就做一次完整的收集。

  • collectgarbage("collect"): 做一次完整的垃圾收集迴圈。通過引數 opt 它提供了一組不同的功能:

  • collectgarbage("count"): 以 K 位元組數為單位返回 Lua 使用的總記憶體數。 這個值有小數部分,所以只需要乘上 1024 就能得到 Lua 使用的準確位元組數(除非溢位)。

  • collectgarbage("restart"): 重啟垃圾收集器的自動執行。

  • collectgarbage("setpause"): 將 arg 設為收集器的 間歇率 。 返回 間歇率 的前一個值。

  • collectgarbage("setstepmul"): 返回 步進倍率 的前一個值。

  • collectgarbage("step"): 單步執行垃圾收集器。 步長"大小"由 arg 控制。 傳入 0 時,收集器步進(不可分割的)一步。 傳入非 0 值, 收集器收集相當於 Lua 分配這些多(K 位元組)記憶體的工作。 如果收集器結束一個迴圈將返回 true 。

  • collectgarbage("stop"): 停止垃圾收集器的執行。 在呼叫重啟前,收集器只會因顯式的呼叫執行。

比如下面:

collectgarbage("collect");
collectgarbage("setpause", 100);
collectgarbage("setstepmul", 500);

https://blog.csdn.net/ChinarCSDN/article/details/78667262  sublime3和lua的安裝

10.math.random和math.randomseed

1.math.randomseed接收一個整數 n 作為隨機序列種子。對於相同的隨機種子, 生成的隨即序列一定是相同的。所以程式每次執行, 賦予不同的種子很重要。很自然想到使用系統時間作為隨機種子,可以看到前兩次執行的隨機數都是一樣的。究其原因,就是 os.time() 返回的時間是秒級的, 不夠精確, 而 random() 還有個毛病就是如果 seed 很小或者seed 變化很小,產生的隨機序列仍然很相似,math.randomseed(tostring(os.time()):reverse():sub(1, 6)),就是把 time返回的數值字串倒過來(低位變高位), 再取高位6位。 這樣, 即使 time變化很小, 但是因為低位變了高位, 種子數值變化卻很大,就可以使偽隨機序列生成的更好一些。https://blog.csdn.net/zhangxaochen/article/details/8095007 
這裡需要注意的是,如果隨機時給的隨機範圍不同,序列時不一樣的。並且在給定了隨機種子的前提下,我們即使不給範圍隨機48次,當第49和50次都給隨機範圍隨機的結果跟我們50次都給範圍隨機的結果時一樣的。

 math.random([n [, m]]) 有三種用法: 無參呼叫, 產生 [0,1) 之間的浮點隨機數; 只有引數 n, 產生 [1,n] 之間的整數; 有兩個引數 [n,m], 產生 [n,m]之間的隨機整數.

種子數只是隨機演算法的起源數字,和生成的隨機數的區間沒有任何關係。