Lua語法


為什么要學Lua呢,因為大學時玩了一款游戲叫飢荒,最近發現做腳本也要用到這門語言,於是就簡單的學習一下,畢竟廣大程序員的樂趣就是會敲各種語言的Hello World。

此篇參考鏈接:

http://www.runoob.com/lua/lua-tutorial.html

Lua官網

設計目的

其設計目的是為了嵌入應用程序中,從而為應用程序提供靈活的擴展和定制功能。這句話言外之意就是“我就是個做腳本的,目的就是為了嵌入到應用程序中輔助擴展”。

Lua 特性

  • 輕量級: 它用標准C語言編寫並以源代碼形式開放,編譯后僅僅一百余K,可以很方便的嵌入別的程序里。
  • 可擴展: Lua提供了非常易於使用的擴展接口和機制:由宿主語言(通常是C或C++)提供這些功能,Lua可以使用它們,就像是本來就內置的功能一樣。
  • 其它特性:
    • 支持面向過程(procedure-oriented)編程和函數式編程(functional programming);
    • 自動內存管理;只提供了一種通用類型的表(table),用它可以實現數組,哈希表,集合,對象
    • 語言內置模式匹配;閉包(closure);函數也可以看做一個值提供多線程(協同進程,並非操作系統所支持的線程)支持;
    • 通過閉包和table可以很方便地支持面向對象編程所需要的一些關鍵機制,比如數據抽象,虛函數,繼承和重載等。

Lua 應用場景

  • 游戲開發  http://love2d.org/
  • 獨立應用腳本(這個就是我的目的,做游戲腳本)
  • Web 應用腳本
  • 擴展和數據庫插件如:MySQL Proxy 和 MySQL WorkBench
  • 安全系統,如入侵檢測系統

Lua 環境安裝

Windows環境安裝

google code下載,這個需要翻牆,也可以從其他地方下載。

安裝完后是個黑框框,cmd命令模式執行,跟java環境配置調用命令大同小異。這個是用來運行最終文件的。

跟java一樣要有IDE,類似於IDEA和myeclipse,這個在安裝Lua的過程中會有一個叫SciTE的軟件,這個就是Lua的編輯器。

Linux安裝環境

如果你沒有Linux運行環境,僅僅是用Lua這么小的東西,不想安裝虛擬機又不想買服務器的話。可以嘗試騰訊雲的Cloud Studio,在線編輯、運行。

Cloud Studio是基於瀏覽器的集成式開發環境,支持絕大部分編程語言,包括 HTML5、PHP、Python、Java、Ruby、C/C++、.NET 等等,無需下載安裝程序,一鍵切換開發環境。Cloud Studio提供了完整的 Linux 環境,並且支持自定義域名指向,動態計算資源調整,可以完成各種應用的開發編譯與部署。

新建界面時這個樣子的,發現上面沒有Lua,要去下載Code Runner插件。創建一個無來源的Blank,進入選擇運行環境(這里我選擇Java),然后安裝Lua環境(效果和Windows安裝的一樣)

在java的linux上安裝lua環境

sudo apt-get install libreadline-dev   #先安裝lua環境需要的lib,不行的話換個環境試試
curl -R -O http://www.lua.org/ftp/lua-5.3.0.tar.gz
tar zxf lua-5.3.0.tar.gz
cd lua-5.3.0
make linux  
sudo make install  #一定要sudo,不然可能會出現權限不允許的情況,因為你是coding,文件夾是root

寫個Hello World,沒問題

如果你既不想裝windows的環境也不想裝linux的環境,僅僅是為了學習或者看Lua基礎代碼的運行結果,可以用lua官網自帶的demo測試代碼頁面

Lua 基本語法

交互式編程

輸入lua,進入控制台交互界面

腳本式編程

直接在cmd中輸入lua main.lua,java是需要編譯后再用java命令調用class文件,而lua不需要編譯可以直接調用

注釋

單行注釋

兩個減號是單行注釋:

--

多行注釋

--[[
 多行注釋
 多行注釋
 --]]

標示符

Lua 標示符用於定義一個變量,函數獲取其他用戶定義的項。標示符以一個字母 A 到 Z 或 a 到 z 或下划線 _ 開頭上0個或多個字母,下划線,數字(0到9)。

最好不要使用下划線加大寫字母的標示符,因為Lua的保留字也是這樣的。Lua 不允許使用特殊字符如 @, $, 和 % 來定義標示符。 Lua 是一個區分大小寫的編程語言。

關鍵詞

變量

全局變量:

在默認情況下,變量總是認為是全局的。全局變量不需要聲明,給一個變量賦值后即創建了這個全局變量,訪問一個沒有初始化的全局變量也不會出錯,只不過得到的結果是:nil。如果你想刪除一個全局變量,只需要將變量賦值為nil。

一句話總結:變量默認全局,為初始化為nil,刪除全局變量賦值為nil(和C語言置為空指針類似)

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

Lua 中的變量全是全局變量,那怕是語句塊或是函數里,除非用 local 顯式聲明為局部變量。局部變量的作用域為從聲明位置開始到所在語句塊結束。變量的默認值均為 nil。

Lua可以對多個變量同時賦值,變量列表和值列表的各個元素用逗號分開,賦值語句右邊的值會依次賦給左邊的變量。

遇到賦值語句Lua會先計算右邊所有的值然后再執行賦值操作,所以我們可以這樣進行交換變量的值。

x, y = y, x                     -- swap 'x' for 'y'
a[i], a[j] = a[j], a[i]         -- swap 'a[i]' for 'a[j]'

當變量個數和值的個數不一致時,Lua會一直以變量個數為基礎采取以下策略:

變量個數 > 值的個數 按變量個數補足nil 變量個數 < 值的個數 多余的值會被忽略

多值賦值經常用來交換變量,或將函數調用返回給變量(函數可以返回多個變量)

f()返回兩個值,第一個賦給a,第二個賦給b。應該盡可能的使用局部變量,有兩個好處:

  • 局部變量可以避免命名沖突。
  • 訪問局部變量的速度比全局變量更快。

對 table 的索引使用方括號 []。Lua 也提供了 . 操作

--[[
全局變量與局部變量
]]--
local a ='' --局部變量作用於當前塊(這里相當於腳本文件)
do
    local b ='喵喵喵'  --局部變量作用於當前塊(do end塊)
end

local f = function(param)
    print(a,param)
    c = function()   --全局變量作用於全局
    end
end

f('')
print(a,b,c,param)

Lua 數據類型

Lua是動態類型語言,變量不要類型定義,只需要為變量賦值。 值可以存儲在變量中,作為參數傳遞或結果返回。Lua中有8個基本類型分別為:nil、boolean、number、string、userdata、function、thread和table。

例:

nil

nil 類型表示一種沒有任何有效值,它只有一個值 -- nil,例如打印一個沒有賦值的變量,便會輸出一個 nil 值,對於全局變量和 table,nil 還有一個"刪除"作用,給全局變量或者 table 表里的變量賦一個 nil 值,等同於把它們刪掉(類似於C的空指針)

nil 作比較時應該加上雙引號 ",因為type函數返回的是一個字符串

boolean

Lua把false和nil 看作是"假",其他的都為"真"

number

Lua默認只有一種 number 類型 -- double(雙精度)類型(默認類型可以修改 luaconf.h 里的定義)

string

一對單引號雙引號方括號 "[[]]" 來表示"一塊"字符串

 在對一個數字字符串上進行算術操作時,Lua 會嘗試將這個數字字符串轉成一個數字

字符串連接使用的是兩個點 ..

使用 #計算字符串的長度,放在字符串前面

table

table 的創建是通過"構造表達式"來完成,最簡單構造表達式是{},用來創建一個空表。也可以在表里添加一些數據(相當於數組),直接初始化表

Lua 中的表(table)其實是一個"關聯數組"(associative arrays),數組的索引可以是數字或者是字符串(相當於map,叫做字典)。

a = {} -- 新建一個全局空表(這里用法可以理解為一個map)
a["key1"] = "value1"  -- key和value
key = 10  -- 定義key變量
a[key] = 22  -- 將變量key作為鍵,22作為值
a[key] = a[key] + 11  -- 根據鍵修改值
--[[遍歷table]]--
for k, v in pairs(a) do  
    print(k .. " : " .. v)
end

結果如下:

不同於其他語言的數組把 0 作為數組的初始索引,在 Lua 里表的默認初始索引一般以 1 開始

local tbl = {"apple", "pear", "orange", "grape"}   -- 這個table看起來像數組
for key, val in pairs(tbl) do  -- 遍歷它,key為table的下標
    print("Key", key)
end

table 不會固定長度大小,有新數據添加時 table 長度會自動增長,沒初始的 table項都是 nil。

function

在 Lua 中,函數是被看作是"第一類值(First-Class Value)",函數可以存在變量里

function factorial1(n)  -- 階乘算法函數
    if n == 0 then
        return 1
    else
        return n * factorial1(n - 1)
    end
end
print(factorial1(5))  --打印factorial1函數返回的值
factorial2 = factorial1  -- 把函數factorial1作為參數賦給全局變量factorial2
print(factorial2(5))   -- 調用函數factorial2

function可以以匿名函數(anonymous function)的方式作為參數傳遞

function testFun(tab,fun)    -- 遍歷tab,並對每個key-val做fun函數處理
    for k ,v in pairs(tab) do
        print(fun(k,v));
    end
end

tab={key1="val1",key2="val2"};
-- 匿名函數作為參數
testFun(tab, --第一個參數為表
function(key,val)  --匿名函數作為第二個參數
    return key.."="..val;  --匿名參數實現內容
end
);

function調用方式

. 方式賦值函數human更像作為表的一個屬性,這個屬性是function,參數是需要自己傳的。

: 方式聲明的函數可以通過self.的方式調用表對象的其他參數。

-- 函數調用方式
human = {};
human.money = 1000;
-- .方式
human.isRich1 = function(h)
    return h.money>500;
end
-- :方式,self就代表函數所屬的table
function human:isRich2()
    return self.money>500;
end

print(human.isRich1(human));
print(human:isRich2());

thread

在 Lua 里,最主要的線程是協同程序(coroutine)。它跟線程(thread)差不多,擁有自己獨立的棧、局部變量和指令指針,可以跟其他協同程序共享全局變量和其他大部分東西。

線程跟協程的區別:線程可以同時多個運行,而協程任意時刻只能運行一個,並且處於運行狀態的協程只有被掛起(suspend)時才會暫停。

userdata

userdata 是一種用戶自定義數據,用於表示一種由應用程序或 C/C++ 語言庫所創建的類型,可以將任意 C/C++ 的任意數據類型的數據(通常是 struct 和 指針)存儲到 Lua 變量中調用。

Lua 流程控制

布爾表達式,除了nil和false為假其余全部為真。取反用not

if(布爾表達式 1)
then
   --[ 在布爾表達式 1 為 true 時執行該語句塊 --]
elseif( 布爾表達式 2)
then
   --[ 在布爾表達式 2 為 true 時執行該語句塊 --]
else 
   --[ 如果以上布爾表達式都不為 true 則執行該語句塊 --]
end

Lua 循環

while

statements(循環體語句) 可以是一條或多條語句,condition(條件) 可以是任意表達式,在 condition(條件) 為 true 時執行循環體語句。

while(condition)
do
   statements
end

repeat...until

在條件進行判斷前循環體都會執行一次。如果條件判斷語句(condition)為 false,循環會重新開始執行,直到條件判斷語句(condition)為 true 才會停止執行。

repeat
   statements
until( condition )

for

var 從 exp1 變化到 exp2,每次變化以 exp3 為步長遞增 var,並執行一次 "執行體"。exp3 是可選的,如果不指定,默認為1。

for var=exp1,exp2,exp3 do  
    <執行體>  
end  

泛型for

遍歷數組,i是數組索引值,v是對應索引的數組元素值。ipairs是Lua提供的一個迭代器函數,用來迭代數組。

--打印數組a的所有值  
a = {"one", "two", "three"}
for i, v in ipairs(a) do
    print(i, v)
end 

遍歷字典,k是key,v是value。

-- 遍歷字典t的所有值,key一定為string所以不用加引號,value不一定為string所以要加
t = {name="Young",age = 23,sex = "girl"}
for k, v in pairs(t) do
    print(k, v, t[k])
end 

循環實例

--[[循環]]--
local a = 0
-- while死循環,每0.5秒執行一次,不影響性能
while wait(0.5) do 
    print(a)
    a =a+1
end
print('print loop end')

-- repeat循環
repeat 
    print(a)
    a = a+1
until a>10 

-- for循環三個參數分別為起止值,步長
for i=1,10,1 do
    print(i)
end

--遍歷數組
local t = {"Monday", "Tuesday", "Wednesday","Thursday","Friday","Saturday","Sunday"}
t[1]="啦啦啦"  --更改數組第一位的值
print(t[1])
for i, v in ipairs(t) do
    print(i, v,t[i])
end 

-- 遍歷字典(key=value)
local t2 = {Monday=1, Tuesday=2, Wednesday=3,Thursday=4,Friday=5,Saturday=6,Sunday=7}
t2["Monday"]="啦啦啦~"  -- 修改key為Monday的鍵值對的值為啦啦啦~
print(t2['Monday'])  -- 打印鍵為Monday的鍵值對的值
for i, v in pairs(t2) do
    print(i, v,t2[i])
end 

Lua 函數

Lua 函數主要有兩種用途:

  • 1.完成指定的任務,這種情況下函數作為調用語句使用;
  • 2.計算並返回值,這種情況下函數作為賦值語句的表達式使用。
function_scope function function_name( argument1, argument2, argument3..., argumentn)
    function_body
    return result_params_comma_separated
end

解析:

  • function_scope: 該參數是可選的制定函數是全局函數還是局部函數,未設置該參數默認為全局函數,如果你需要設置函數為局部函數需要使用關鍵字 local

  • function_name: 指定函數名稱。

  • argument1, argument2, argument3..., argumentn: 函數參數,多個參數以逗號隔開,函數也可以不帶參數。

  • function_body: 函數體,函數中需要執行的代碼語句塊。

  • result_params_comma_separated: 函數返回值,Lua語言函數可以返回多個值,每個值以逗號隔開。

多返回值

Lua函數可以返回多個結果值,比如string.find,其返回匹配串"開始和結束的下標"(如果不存在匹配串返回nil)。

s, e = string.find("www.runoob.com", "runoob") 
print(s, e)

方法內寫法

function maximum (a)  -- 返回table a的最大值和索引
    local mi = 1             -- 最大值索引,初始為1
    local m = a[mi]          -- 最大值
    for i,val in ipairs(a) do
       if val > m then
           mi = i
           m = val
       end
    end
    return m, mi  -- 返回多個值的寫法
end

print(maximum({8,10,23,12,5}))

可變參數

Lua 函數可以接受可變數目的參數,和 C 語言類似,在函數參數列表中使用三點 ... 表示函數有可變的參數。

function average(...)   -- 取平均值
   result = 0   -- 結果
   local arg_tbl ={...}    --> arg 為一個表,局部變量,將輸入的過個參數封裝為一個表
   for i,v in ipairs(arg_tbl) do  -- 遍歷表
      result = result + v    -- 將value相加
   end  
   print("總共傳入 " .. #arg .. " 個數")
   return result/#arg_tbl -- 將綜合除以總數量
end

print("平均值為",average(10,5,3,4,5,6))
  • select('#', …) 返回可變參數的長度
  • select(n, …) 用於訪問 n 到 select('#',…) 的參數

運算符

運算符是一個特殊的符號,用於告訴解釋器執行特定的數學或邏輯運算。Lua提供了以下幾種運算符類型:

  • 算術運算符

  • 關系運算符

  • 邏輯運算符

  • 其他運算符

運算符優先級

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

字符串

字符串或串(String)是由數字、字母、下划線組成的一串字符。

Lua 語言中字符串可以使用以下三種方式來表示:

  • 單引號間的一串字符。
  • 雙引號間的一串字符。
  • [[和]]間的一串字符。

轉義字符

字符串操作

string.upper(argument)  -- 字符串全部轉為大寫字母
string.lower(argument)  -- 字符串全部轉為小寫字母
string.gsub(mainString,findString,replaceString,num)   -- 在字符串中替換,mainString為要替換的字符串, findString 為被替換的字符,replaceString 要替換的字符,num 替換次數(可以忽略,則全部替換)
string.find (str, substr, [init, [end]])  --在一個指定的目標字符串中搜索指定的內容(第三個參數為索引),返回其具體位置。不存在則返回 nil。
string.reverse(arg)  -- 字符串反轉
string.format(...)  --  返回一個類似printf的格式化字符串
string.char(arg) 和 string.byte(arg[,int])  --  char 將整型數字轉成字符並連接, byte 轉換字符為整數值(可以指定某個字符,默認第一個字符)。
string.len(arg)  --  計算字符串長度。
string.rep(string, n)  -- 返回字符串string的n個拷貝
..  -- 鏈接兩個字符串
string.gmatch(str, pattern)  --  回一個迭代器函數,每一次調用這個函數,返回一個在字符串 str 找到的下一個符合 pattern 描述的子串。如果參數 pattern 描述的字符串沒有找到,迭代函數返回nil。
string.match(str, pattern, init)  --  string.match()只尋找源字串str中的第一個配對. 參數init可選, 指定搜尋過程的起點, 默認為1。 
在成功配對時, 函數將返回配對表達式中的所有捕獲結果; 如果沒有設置捕獲標記, 則返回整個配對字符串. 當沒有成功的配對時, 返回nil。

string.format字符串格式化

pattern匹配模式

數組

數組,就是相同數據類型的元素按一定順序排列的集合,可以是一維數組和多維數組。Lua 數組的索引鍵值可以使用整數表示,數組的大小不是固定的。

一維數組

一維數組是最簡單的數組,其邏輯結構是線性表。一維數組可以用for循環出數組中的元素

array = {"Lua", "Tutorial"}

for i= 0, 2 do
   print(array[i])
end

多維數組

多維數組即數組中包含數組或一維數組的索引鍵對應一個數組。以下是一個三行三列的陣列多維數組

-- 初始化數組
array = {}
for i=1,3 do
   array[i] = {}
      for j=1,3 do
         array[i][j] = i*j
      end
end

-- 訪問數組
for i=1,3 do
   for j=1,3 do
      print(array[i][j])
   end
end

Lua 迭代器

泛型 for 迭代器

泛型 for 在自己內部保存迭代函數,實際上它保存三個值:迭代函數、狀態常量、控制變量。泛型 for 迭代器提供了集合的 key/value 對,語法格式如下:

for k, v in pairs(t) 
do
    print(k, v)
end

無狀態的迭代器

無狀態的迭代器是指不保留任何狀態的迭代器,因此在循環中我們可以利用無狀態迭代器避免創建閉包花費額外的代價。每一次迭代,迭代函數都是用兩個變量(狀態常量和控制變量)的值作為參數被調用,一個無狀態的迭代器只利用這兩個值可以獲取下一個元素。這種無狀態迭代器的典型的簡單的例子是ipairs,它遍歷數組的每一個元素。

function square(iteratorMaxCount,currentNumber)
   if currentNumber<iteratorMaxCount
   then
      currentNumber = currentNumber+1
   return currentNumber, currentNumber*currentNumber
   end
end

for i,n in square,3,0
do
   print(i,n)
end

table表

Lua也是通過table來解決模塊(module)、包(package)和對象(Object)的。 例如string.format表示使用"format"來索引table string。

table操作

獲取table長度,當我們獲取 table 的長度的時候無論是使用 # 還是 table.getn 其都會在索引中斷的地方停止計數,而導致無法正確取得 table 的長度。

可以使用以下方法來代替:

function table_leng(t)
  local leng=0
  for k, v in pairs(t) do
    leng=leng+1
  end
  return leng;
end

模塊與包

模塊類似於一個封裝庫,從 Lua 5.1 開始,Lua 加入了標准的模塊管理機制,可以把一些公用的代碼放在一個文件里,以 API 接口的形式在其他地方調用,有利於代碼的重用和降低代碼耦合度。

Lua 的模塊是由變量、函數等已知元素組成的 table,因此創建一個模塊很簡單,就是創建一個 table,然后把需要導出的常量、函數放入其中,最后返回這個 table 就行。以下為創建自定義模塊 module.lua,文件代碼格式如下:

-- 文件名為 module.lua
-- 定義一個名為 module 的模塊(其實就是一個全局變量)
module = {} 
 
-- 定義一個常量
module.constant = "這是一個常量"
 
-- 定義一個函數
function module.func1()
    io.write("這是一個公有函數!\n")
end
 
local function func2()
    print("這是一個私有函數!")
end
 
function module.func3()
    func2()
end
 
return module -- 返回對象(把開始定義的那個全局變量在最末尾處返回)

require 函數

require("<模塊名>")  --寫法一
require "<模塊名>"  --寫法二

用法一,全局要用

-- main.lua 文件
-- module 模塊為上文提到到 module.lua
require("module")  -- 在最上面聲明,相當於java的import
 
print(module.constant)  -- module.lua文件中聲明的全局變量名module.變量名
 
module.func3()  --  module.lua文件中聲明的全局變量名module.函數名

用法二,局部要用

-- main2.lua 文件
-- module 模塊為上文提到到 module.lua
-- 別名變量 m
local m = require("module")  -- 把require函數返回module.lua文件中返回的全局變量module並賦值給本地變量m
 
print(m.constant)
 
m.func3()

加載機制

對於自定義的模塊,模塊文件不是放在哪個文件目錄都行,函數 require 有它自己的文件路徑加載策略,它會嘗試從 Lua 文件或 C 程序庫中加載模塊。

require 用於搜索 Lua 文件的路徑是存放在全局變量 package.path 中,當 Lua 啟動后,會以環境變量 LUA_PATH 的值來初始這個環境變量。如果沒有找到該環境變量,則使用一個編譯時定義的默認路徑來初始化。

當然,如果沒有 LUA_PATH 這個環境變量,也可以自定義設置,在當前用戶根目錄下打開 .profile 文件(沒有則創建,打開 .bashrc 文件也可以),例如把 "~/lua/" 路徑加入 LUA_PATH 環境變量里:

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

文件路徑以 ";" 號分隔,最后的 2 個 ";;" 表示新加的路徑后面加上原來的默認路徑。

接着,更新環境變量參數,使之立即生效。

source ~/.profile

這時假設 package.path 的值是:

/Users/dengjoe/lua/?.lua;./?.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/?/init.lua;/usr/local/lib/lua/5.1/?.lua;/usr/local/lib/lua/5.1/?/init.lua

那么調用 require("module") 時就會嘗試打開以下文件目錄去搜索目標。

/Users/dengjoe/lua/module.lua;
./module.lua
/usr/local/share/lua/5.1/module.lua
/usr/local/share/lua/5.1/module/init.lua
/usr/local/lib/lua/5.1/module.lua
/usr/local/lib/lua/5.1/module/init.lua

如果找過目標文件,則會調用 package.loadfile 來加載模塊。否則,就會去找 C 程序庫。

搜索的文件路徑是從全局變量 package.cpath 獲取,而這個變量則是通過環境變量 LUA_CPATH 來初始。

搜索的策略跟上面的一樣,只不過現在換成搜索的是 so 或 dll 類型的文件。如果找得到,那么 require 就會通過 package.loadlib 來加載它。

C包

Lua和C是很容易結合的,使用C為Lua寫包。

與Lua中寫包不同,C包在使用以前必須首先加載並連接,在大多數系統中最容易的實現方式是通過動態連接庫機制。

Lua在一個叫loadlib的函數內提供了所有的動態連接的功能。這個函數有兩個參數:庫的絕對路徑和初始化函數。所以典型的調用的例子如下:

local path = "/usr/local/lua/lib/libluasocket.so"
local f = loadlib(path, "luaopen_socket")

loadlib函數加載指定的庫並且連接到Lua,然而它並不打開庫(也就是說沒有調用初始化函數),反之他返回初始化函數作為Lua的一個函數,這樣我們就可以直接在Lua中調用他。

如果加載動態庫或者查找初始化函數時出錯,loadlib將返回nil和錯誤信息。我們可以修改前面一段代碼,使其檢測錯誤然后調用初始化函數:

local path = "/usr/local/lua/lib/libluasocket.so"
-- 或者 path = "C:\\windows\\luasocket.dll",這是 Window 平台下
local f = assert(loadlib(path, "luaopen_socket"))
f()  -- 真正打開庫

一般情況下我們期望二進制的發布庫包含一個與前面代碼段相似的stub文件,安裝二進制庫的時候可以隨便放在某個目錄,只需要修改stub文件對應二進制庫的實際路徑即可。

將stub文件所在的目錄加入到LUA_PATH,這樣設定后就可以使用require函數加載C庫了。

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

Lua 元表(Metatable)

table的元表提供了一種機制,支持類似操作符重載的行為。在 Lua table 中我們可以訪問對應的key來得到value值,但是卻無法對兩個 table 進行操作。因此 Lua 提供了元表(Metatable),允許我們改變table的行為,每個行為關聯了對應的元方法。

有兩個很重要的函數來處理元表:

  • setmetatable(table,metatable): 對指定 table 設置元表(metatable),如果元表(metatable)中存在 __metatable 鍵值,setmetatable 會失敗。
  • getmetatable(table): 返回對象的元表(metatable)。

_index元方法

這是 metatable 最常用的鍵。當你通過鍵來訪問 table 的時候,如果這個鍵沒有值,那么Lua就會尋找該table的metatable(假定有metatable)中的__index 鍵。如果__index包含一個表格,Lua會在表格中查找相應的鍵。

$ lua> other = { foo = 3 } 
> t = setmetatable({}, { __index = other }) 
> t.foo
3
> t.bar
nil

不是表

mytable = setmetatable({key1 = "value1"}, {
  __index = function(mytable, key)
    if key == "key2" then
      return "metatablevalue"
    else
      return nil
    end
  end
})

print(mytable.key1,mytable.key2)
value1    metatablevalue

__newindex 元方法

_newindex 元方法用來對表更新,__index則用來對表訪問 。當你給表的一個缺少的索引賦值,解釋器就會查找__newindex 元方法:如果存在則調用這個函數而不進行賦值操作。

mymetatable = {}
mytable = setmetatable({key1 = "value1"}, { __newindex = mymetatable })

print(mytable.key1)

mytable.newkey = "新值2"
print(mytable.newkey,mymetatable.newkey)

mytable.key1 = "新值1"
print(mytable.key1,mymetatable.key1)

value1
nil    新值2
新值1    nil

_add元方法

f1 = {a = 1, b = 2}  -- 表示一個分數 a/b. 
f2 = {a = 2, b = 3} 

metafraction = {} 
function metafraction.__add(f1, f2) 
  local sum = {} 
  sum.b = f1.b * f2.b 
  sum.a = f1.a * f2.b + f2.a * f1.b 
  return sum
end

setmetatable(f1, metafraction) 
setmetatable(f2, metafraction) 

s = f1 + f2  -- 調用在f1的元表上的__add(f1, f2) 方法 
__index、__add等的值,被稱為元方法。 
-- table元方法的清單: -- __add(a, b) for a + b -- __sub(a, b) for a - b -- __mul(a, b) for a * b -- __div(a, b) for a / b -- __mod(a, b) for a % b -- __pow(a, b) for a ^ b -- __unm(a) for -a -- __concat(a, b) for a .. b -- __len(a) for #a -- __eq(a, b) for a == b -- __lt(a, b) a < b -- __le(a, b) for a <= b -- __index(a, b) <fn or a table> for a.b -- __newindex(a, b, c) for a.b = c -- __call(a, ...) for a(...) 

Lua 協同程序(coroutine)

Lua 協同程序(coroutine)與線程比較類似:擁有獨立的堆棧,獨立的局部變量,獨立的指令指針,同時又與其它協同程序共享全局變量和其它大部分東西。

線程和協同程序區別

線程與協同程序的主要區別在於,一個具有多個線程的程序可以同時運行幾個線程,而協同程序卻需要彼此協作的運行。在任一指定時刻只有一個協同程序在運行,並且這個正在運行的協同程序只有在明確的被要求掛起的時候才會被掛起。協同程序有點類似同步的多線程,在等待同一個線程鎖的幾個線程有點類似協同。

-- coroutine_test.lua 文件
co = coroutine.create(
    function(i)
        print(i);
    end
)
 
coroutine.resume(co, 1)   -- 1
print(coroutine.status(co))  -- dead
 
print("----------")
 
co = coroutine.wrap(
    function(i)
        print(i);
    end
)
 
co(1)
 
print("----------")
 
co2 = coroutine.create(
    function()
        for i=1,10 do
            print(i)
            if i == 3 then
                print(coroutine.status(co2))  --running
                print(coroutine.running()) --thread:XXXXXX
            end
            coroutine.yield()
        end
    end
)
 
coroutine.resume(co2) --1
coroutine.resume(co2) --2
coroutine.resume(co2) --3
 
print(coroutine.status(co2))   -- suspended
print(coroutine.running())
 
print("----------")

Lua 文件 I/O

Lua I/O 庫用於讀取和處理文件。分為簡單模式(和C一樣)、完全模式。

  • 簡單模式(simple model)擁有一個當前輸入文件和一個當前輸出文件,並且提供針對這些文件相關的操作。
  • 完全模式(complete model) 使用外部的文件句柄來實現。它以一種面對對象的形式,將所有的文件操作定義為文件句柄的方法

簡單模式在做一些簡單的文件操作時較為合適。但是在進行一些高級的文件操作的時候,簡單模式就顯得力不從心。例如同時讀取多個文件這樣的操作,使用完全模式則較為合適。

打開文件語句

file = io.open (filename [, mode])

mod值

簡單模式

簡單模式使用標准的 I/O 或使用一個當前輸入文件和一個當前輸出文件。

以下為 file.lua 文件代碼,操作的文件為test.lua(如果沒有你需要創建該文件),代碼如下:

-- 以只讀方式打開文件
file = io.open("test.lua", "r")

-- 設置默認輸入文件為 test.lua
io.input(file)

-- 輸出文件第一行
print(io.read())

-- 關閉打開的文件
io.close(file)

-- 以附加的方式打開只寫文件
file = io.open("test.lua", "a")

-- 設置默認輸出文件為 test.lua
io.output(file)

-- 在文件最后一行添加 Lua 注釋
io.write("--  test.lua 文件末尾注釋")

-- 關閉打開的文件
io.close(file)

read參數

io 方法有:

  • io.tmpfile():返回一個臨時文件句柄,該文件以更新模式打開,程序結束時自動刪除

  • io.type(file): 檢測obj是否一個可用的文件句柄

  • io.flush(): 向文件寫入緩沖中的所有數據

  • io.lines(optional file name): 返回一個迭代函數,每次調用將獲得文件中的一行內容,當到文件尾時,將返回nil,但不關閉文件

完全模式

通常我們需要在同一時間處理多個文件。我們需要使用 file:function_name 來代替 io.function_name 方法。以下實例演示了如何同時處理同一個文件:

-- 以只讀方式打開文件
file = io.open("test.lua", "r")

-- 輸出文件第一行
print(file:read())

-- 關閉打開的文件
file:close()

-- 以附加的方式打開只寫文件
file = io.open("test.lua", "a")

-- 在文件最后一行添加 Lua 注釋
file:write("--test")

-- 關閉打開的文件
file:close()

其他方法:

  • file:seek(optional whence, optional offset): 設置和獲取當前文件位置,成功則返回最終的文件位置(按字節),失敗則返回nil加錯誤信息。參數 whence 值可以是:

    • "set": 從文件頭開始
    • "cur": 從當前位置開始[默認]
    • "end": 從文件尾開始
    • offset:默認為0
    不帶參數file:seek()則返回當前位置,file:seek("set")則定位到文件頭,file:seek("end")則定位到文件尾並返回文件大小
  • file:flush(): 向文件寫入緩沖中的所有數據

  • io.lines(optional file name): 打開指定的文件filename為讀模式並返回一個迭代函數,每次調用將獲得文件中的一行內容,當到文件尾時,將返回nil,並自動關閉文件。
    若不帶參數時io.lines() <=> io.input():lines(); 讀取默認輸入設備的內容,但結束時不關閉文件,如

錯誤處理

錯誤有語法錯誤(編輯器不夠強大不能像java一樣檢測語法)和運行時錯誤(運行時出錯,如空指針異常等),

我們可以使用兩個函數:assert 和 error 來處理錯誤。實例如下:

local function add(a,b)
   assert(type(a) == "number", "a 不是一個數字")
   assert(type(b) == "number", "b 不是一個數字")
   return a+b
end
add(10)

assert首先檢查第一個參數,若沒問題,assert不做任何事情;否則,assert以第二個參數作為錯誤信息拋出。

error函數

error (message [, level])

功能:終止正在執行的函數,並返回message的內容作為錯誤信息(error函數永遠都不會返回),通常情況下,error會附加一些錯誤位置的信息到message頭部。

Level參數指示獲得錯誤的位置:

  • Level=1[默認]:為調用error位置(文件+行號)
  • Level=2:指出哪個調用error的函數的函數
  • Level=0:不添加錯誤位置信息

pcall

pcall以一種"保護模式"來調用第一個參數,因此pcall可以捕獲函數執行中的任何錯誤。pcall接收一個函數和要傳遞給后者的參數,並執行,執行結果:有錯誤、無錯誤;返回值true或者或false, errorinfo。

if pcall(function_name, ….) then
-- 沒有錯誤
else
-- 一些錯誤
end

xpcall

通常在錯誤發生時,希望落得更多的調試信息,而不只是發生錯誤的位置。但pcall返回時,它已經銷毀了調用桟的部分內容。

Lua提供了xpcall函數,xpcall接收第二個參數——一個錯誤處理函數,當錯誤發生時,Lua會在調用桟展開(unwind)前調用錯誤處理函數,於是就可以在這個函數中使用debug庫來獲取關於錯誤的額外信息了。

debug庫提供了兩個通用的錯誤處理函數:

  • debug.debug:提供一個Lua提示符,讓用戶來檢查錯誤的原因
  • debug.traceback:根據調用桟來構建一個擴展的錯誤消息
xpcall(function(i) print(i) error('error..') end, function() print(debug.traceback()) end, 33)

Lua 調試(Debug)

調試類型

  • 命令行調試
  • 圖形界面調試

命令行調試器有:RemDebug、clidebugger、ctrace、xdbLua、LuaInterface - Debugger、Rldb、ModDebug。

圖形界調試器有:SciTE、Decoda、ZeroBrane Studio、akdebugger、luaedit。

Lua 面向對象

-- Meta class
Shape = {area = 0}
-- 基礎類方法 new
function Shape:new (o,side)
  o = o or {}
  setmetatable(o, self)
  self.__index = self
  side = side or 0
  self.area = side*side;
  return o
end
-- 基礎類方法 printArea
function Shape:printArea ()
  print("面積為 ",self.area)
end

-- 創建對象
myshape = Shape:new(nil,10)
myshape:printArea()

Square = Shape:new()
-- 派生類方法 new
function Square:new (o,side)
  o = o or Shape:new(o,side)
  setmetatable(o, self)
  self.__index = self
  return o
end

-- 派生類方法 printArea
function Square:printArea ()
  print("正方形面積為 ",self.area)
end

-- 創建對象
mysquare = Square:new(nil,10)
mysquare:printArea()

Rectangle = Shape:new()
-- 派生類方法 new
function Rectangle:new (o,length,breadth)
  o = o or Shape:new(o)
  setmetatable(o, self)
  self.__index = self
  self.area = length * breadth
  return o
end

-- 派生類方法 printArea
function Rectangle:printArea ()
  print("矩形面積為 ",self.area)
end

-- 創建對象
myrectangle = Rectangle:new(nil,10,20)
myrectangle:printArea()

https://learnxinyminutes.com/docs/zh-cn/lua-cn/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM