Lua基礎知識記錄

Lua 基本語法

腳本式編程

我們可以將 Lua 程序代碼保持到一個以 lua 結尾的文件,並執行,該模式稱爲腳本式編程,如我們將如下代碼存儲在名爲 hello.lua 的腳本文件中:

print("Hello World!")
print("www.runoob.com")

使用 lua 名執行以上腳本,輸出結果爲:

$ lua hello.lua
Hello World!
www.runoob.com

我們也可以將代碼修改爲如下形式來執行腳本(在開頭添加:#!/usr/local/bin/lua):

實例

#!/usr/local/bin/lua

print("Hello World")
print("www.runoob.com")

以上代碼中,我們指定了 Lua 的解釋器 /usr/local/bin directory。加上 # 號標記解釋器會忽略它。接下來我們爲腳本添加可執行權限,並執行:

./hello.lua 
Hello World!
www.runoob.com

註釋

單行註釋

兩個減號是單行註釋:

--

多行註釋

--[[
 多行註釋
 多行註釋
 --]]

標示符

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

最好不要使用下劃線加大寫字母的標示符,因爲Lua的保留字也是這樣的。

Lua 不允許使用特殊字符如 @, $, % 來定義標示符。 Lua 是一個區分大小寫的編程語言。因此在 Lua Runoob runoob 是兩個不同的標示符。以下列出了一些正確的標示符:

mohd         zara      abc     move_name    a_123
myname50     _temp     j       a23b9        retVal

關鍵詞

以下列出了 Lua 的保留關鍵字。保留關鍵字不能作爲常量或變量或其他用戶自定義標示符:

and

break

do

else

elseif

end

false

for

function

if

in

local

nil

not

or

repeat

return

then

true

until

while

goto

   

一般約定,以下劃線開頭連接一串大寫字母的名字(比如 _VERSION)被保留用於 Lua 內部全局變量。


全局變量

在默認情況下,變量總是認爲是全局的。

全局變量不需要聲明,給一個變量賦值後即創建了這個全局變量,訪問一個沒有初始化的全局變量也不會出錯,只不過得到的結果是:nil

> print(b)
nil
> b=10
print(b)
10

如果你想刪除一個全局變量,只需要將變量賦值爲nil

b = nil
print(b)      --> nil

這樣變量b就好像從沒被使用過一樣。換句話說, 當且僅當一個變量不等於nil時,這個變量即存在。

Lua 數據類型

Lua 是動態類型語言,變量不要類型定義,只需要爲變量賦值。 值可以存儲在變量中,作爲參數傳遞或結果返回。

Lua 中有 8 個基本類型分別爲:nilbooleannumberstringuserdatafunctionthread table

數據類型

描述

nil

這個最簡單,只有值nil屬於該類,表示一個無效值(在條件表達式中相當於false)。

boolean

包含兩個值:falsetrue

number

表示雙精度類型的實浮點數

string

字符串由一對雙引號或單引號來表示

function

C Lua 編寫的函數

userdata

表示任意存儲在變量中的C數據結構

thread

表示執行的獨立線路,用於執行協同程序

table

Lua 中的表(table)其實是一個"關聯數組"associative arrays),數組的索引可以是數字、字符串或表類型。在 Lua 裏,table 的創建是通過"構造表達式"來完成,最簡單構造表達式是{},用來創建一個空表。

我們可以使用 type 函數測試給定變量或者值的類型:

實例

print(type("Hello world"))      --> string
print(type(10.4*3))             --> number
print(type(print))              --> function
print(type(type))               --> function
print(type(true))               --> boolean
print(type(nil))                --> nil
print(type(type(X)))            --> string


nil(空)

nil 類型表示一種沒有任何有效值,它只有一個值 -- nil,例如打印一個沒有賦值的變量,便會輸出一個 nil 值:

> print(type(a))
nil
>

對於全局變量和 tablenil 還有一個"刪除"作用,給全局變量或者 table 表裏的變量賦一個 nil 值,等同於把它們刪掉,執行下面代碼就知:

tab1 = { key1 = "val1", key2 = "val2", "val3" }
for k, v in pairs(tab1) do
    print(k .. " - " .. v)
end
 
tab1.key1 = 
nil
for k, v in pairs(tab1) do
    print(k .. " - " .. v)
end

nil 作比較時應該加上雙引號 "

> type(X)
nil
type(X)==nil
false
type(X)=="nil"
true
>

type(X)==nil 結果爲 false 的原因是因爲 type(type(X))==string


boolean(布爾)

boolean 類型只有兩個可選值:true(真) false(假),Lua false nil 看作是 false,其他的都爲 true,數字 0 也是 true:

實例

print(type(true))
print(type(false))
print(type(nil))
 
if false or nil then
    print("至少有一個是 true")
else
    print("false nil 都爲 false")
end

if 0 then
    print("數字 0 true")
else
    print("數字 0 false")
end

以上代碼執行結果如下:

$ lua test.lua 
boolean
boolean
nil
false 和 nil 都爲 false
數字 0 是 true

number(數字)

Lua 默認只有一種 number 類型 -- double(雙精度)類型(默認類型可以修改 luaconf.h 裏的定義),以下幾種寫法都被看作是 number 類型:

實例

print(type(2))
print(type(2.2))
print(type(0.2))
print(type(2e+1))
print(type(0.2e-1))
print(type(7.8263692594256e-06))


運行實例 »

以上代碼執行結果:

number
number
number
number
number
number

string(字符串)

字符串由一對雙引號或單引號來表示。

string1 = "this is string1"
string2 = 'this is string2'

也可以用 2 個方括號 "[[]]" 來表示"一塊"字符串。

實例

html = [[
<html>
<head></head>
<body>
    <a href="http://www.runoob.com/">
菜鳥教程</a>
</body>
</html>
]]

print(html)

以下代碼執行結果爲:

<html>
<head></head>
<body>
    <a href="http://www.runoob.com/">菜鳥教程</a>
</body>
</html>

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

> print("2" + 6)
8.0
print("2" + "6")
8.0
print("2 + 6")
2 + 6
print("-2e2" * "6")
-1200.0
print("error" + 1)
stdin:1: attempt to perform arithmetic on a string value
stack traceback:
        stdin:
1: in main chunk
        
[C]: in ?

以上代碼中"error" + 1執行報錯了,字符串連接使用的是 .. ,如:

> print("a" .. 'b')
ab
> print(157 .. 428)
157428
> 

使用 # 來計算字符串的長度,放在字符串前面,如下實例:

實例

> len = "www.runoob.com"
print(#len)
14
print(#"www.runoob.com")
14


table(表)

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

實例

-- 創建一個空的 table
local tbl1 = {}
 
-- 直接初始表
local tbl2 = {"apple", "pear", "orange", "grape"}

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

實例

-- table_test.lua 腳本文件
a = {}
a["key"] = "value"
key = 10
a[key] = 22
a[key] = a[key] + 11
for k, v in pairs(a) do
    print(k .. " : " .. v)
end

腳本執行結果爲:

$ lua table_test.lua 
key : value
10 : 33

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

實例

-- table_test2.lua 腳本文件
local tbl = {"apple", "pear", "orange", "grape"}
for key, val in pairs(tbl) do
    print("Key", key)
end

腳本執行結果爲:

$ lua table_test2.lua 
Key    1
Key    2
Key    3
Key    4

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

實例

-- table_test3.lua 腳本文件
a3 = {}
for i = 1, 10 do
    a3[i] = i
end
a3["key"] = "val"
print(a3["key"])
print(a3["none"])

腳本執行結果爲:

$ lua table_test3.lua 
val
nil

function(函數)

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

實例

-- function_test.lua 腳本文件
function factorial1(n)
    if n == 0 then
        return 1
    else
        return n * factorial1(n - 1)
    end
end
print(factorial1(5))
factorial2 = factorial1
print(factorial2(5))

腳本執行結果爲:

$ lua function_test.lua 
120
120

function 可以以匿名函數(anonymous function)的方式通過參數傳遞:

實例

-- function_test2.lua 腳本文件
function testFun(tab,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
);

腳本執行結果爲:

$ lua function_test2.lua 
key1 = val1
key2 = val2

thread(線程)

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

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


userdata(自定義類型)

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

Lua 變量

變量在使用前,必須在代碼中進行聲明,即創建該變量。

編譯程序執行代碼之前編譯器需要知道如何給語句變量開闢存儲區,用於存儲變量的值。

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

Lua 中的變量全是全局變量,那怕是語句塊或是函數裏,除非用 local 顯式聲明爲局部變量。

局部變量的作用域爲從聲明位置開始到所在語句塊結束。

變量的默認值均爲 nil

實例

-- test.lua 文件腳本
a = 5               -- 全局變量
local b = 5         -- 局部變量

function joke()
    c = 5           -- 全局變量
    local d = 6     -- 局部變量
end

joke()
print(c,d)          --> 5 nil

do 
    
local a = 6     -- 局部變量
    b = 6           -- 對局部變量重新賦值
    print(a,b);     --> 6 6
end

print(a,b)      --> 5 6

執行以上實例輸出結果爲:

$ lua test.lua 
5    nil
6    6
5    6

賦值語句

賦值是改變一個變量的值和改變表域的最基本的方法。

a = "hello" .. "world"
t.n = t.n + 1

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

a, b = 10, 2*x       <-->       a=10; b=2*x

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

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

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

a. 變量個數 > 值的個數             按變量個數補足nil
b. 變量個數 < 值的個數             多餘的值會被忽略

實例

a, b, c = 0, 1
print(a,b,c)             --> 0   1   nil
 
a, b = a+
1, b+1, b+2     -- value of b+2 is ignored
print(a,b)               --> 1   2
 
a, b, c = 
0
print(a,b,c)             --> 0   nil   nil

上面最後一個例子是一個常見的錯誤情況,注意:如果要對多個變量賦值必須依次對每個變量賦值。

a, b, c = 0, 0, 0
print(a,b,c)             --> 0   0   0

多值賦值經常用來交換變量,或將函數調用返回給變量:

a, b = f()

f()返回兩個值,第一個賦給a,第二個賦給b

應該儘可能的使用局部變量,有兩個好處:

  • 1. 避免命名衝突。
  • 2. 訪問局部變量的速度比全局變量更快。

索引

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

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

實例

> site = {}
> site["key"] = "www.runoob.com"
print(site["key"])
www.runoob.com
print(site.key)
www.runoob.com

Lua 語言提供了以下幾種循環處理方式:

循環類型

描述

while 循環

在條件爲 true 時,讓程序重複地執行某些語句。執行語句前會先檢查條件是否爲 true

for 循環

重複執行指定語句,重複次數可在 for 語句中控制。

repeat...until

重複執行循環,直到 指定的條件爲真時爲止

循環嵌套

可以在循環內嵌套一個或多個循環語句(while do ... end;for ... do ... end;repeat ... until;


循環控制語句

循環控制語句用於控制程序的流程, 以實現程序的各種結構方式。

Lua 支持以下循環控制語句:

控制語句

描述

break 語句

退出當前循環或語句,並開始腳本執行緊接着的語句。

goto 語句

將程序的控制點轉移到一個標籤處。

Lua 提供了以下控制結構語句:

語句

描述

if 語句

if 語句 由一個布爾表達式作爲條件判斷,其後緊跟其他語句組成。

if...else 語句

if 語句 可以與 else 語句搭配使用, if 條件表達式爲 false 時執行 else 語句代碼。

if 嵌套語句

你可以在if  else if中使用一個或多個 if  else if 語句

Lua 函數

Lua中,函數是對語句和表達式進行抽象的主要方法。既可以用來處理一些特殊的工作,也可以用來計算一些值。

Lua 提供了許多的內建函數,你可以很方便的在程序中調用它們,如print()函數可以將傳入的參數打印在控制檯上。

Lua 函數主要有兩種用途:

  • 1.完成指定的任務,這種情況下函數作爲調用語句使用;
  • 2.計算並返回值,這種情況下函數作爲賦值語句的表達式使用。

函數定義

Lua 編程語言函數定義格式如下:

optional_function_scope function function_name( argument1, argument2, argument3..., argumentn)
    function_body
    return result_params_comma_separated
end

解析:

  • optional_function_scope: 該參數是可選的制定函數是全局函數還是局部函數,未設置該參數默認爲全局函數,如果你需要設置函數爲局部函數需要使用關鍵字 local
  • function_name: 指定函數名稱。
  • argument1, argument2, argument3..., argumentn: 函數參數,多個參數以逗號隔開,函數也可以不帶參數。
  • function_body: 函數體,函數中需要執行的代碼語句塊。
  • result_params_comma_separated: 函數返回值,Lua語言函數可以返回多個值,每個值以逗號隔開。

實例

以下實例定義了函數 max(),參數爲 num1, num2,用於比較兩值的大小,並返回最大值:

實例

--[[ 函數返回兩個值的最大值 --]]
function max(num1, num2)

   if (num1 > num2) then
      result = num1;
   
else
      result = num2;
   
end

   return result; 
end
-- 調用函數
print("兩值比較最大值爲 ",max(10,4))
print("兩值比較最大值爲 ",max(5,6))

以上代碼執行結果爲:

兩值比較最大值爲     10
兩值比較最大值爲     6

Lua 中我們可以將函數作爲參數傳遞給函數,如下實例:

實例

myprint = function(param)
   print("這是打印函數 -   ##",param,"##")
end

function add(num1,num2,functionPrint)
   result = num1 + num2
   
-- 調用傳遞的函數參數
   functionPrint(result)
end
myprint(10)
-- myprint 函數作爲參數傳遞
add(2,5,myprint)

以上代碼執行結果爲:

這是打印函數 -   ##    10    ##
這是打印函數 -   ##    7    ##

多返回值

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

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

Lua函數中,在return後列出要返回的值的列表即可返回多值,如:

實例

function maximum (a)
    local mi = 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}))

以上代碼執行結果爲:

23    3

可變參數

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

function add(...)  
local s = 0  
  for i, v in ipairs{...} do   --> {...} 表示一個由所有變長參數構成的數組  
    s = s + v  
  end  
  return s  
end  
print(add(3,4,5,6,7))  --->25

我們可以將可變參數賦值給一個變量。

例如,我們計算幾個數的平均值:

實例

function average(...)
   result = 0
   local arg={...}    --> arg 爲一個表,局部變量
   for i,v in ipairs(arg) do
      result = result + v
   
end
   print("總共傳入 " .. #arg .. " 個數")
   return result/#arg
end

print("平均值爲",average(10,5,3,4,5,6))

以上代碼執行結果爲:

總共傳入 6 個數
平均值爲    5.5

我們也可以通過 select("#",...) 來獲取可變參數的數量:

實例

function average(...)
   result = 0
   local arg={...}
   for i,v in ipairs(arg) do
      result = result + v
   
end
   print("總共傳入 " .. select("#",...) .. " 個數")
   return result/select("#",...)
end

print("平均值爲",average(10,5,3,4,5,6))

以上代碼執行結果爲:

總共傳入 6 個數
平均值爲    5.5

有時候我們可能需要幾個固定參數加上可變參數,固定參數必須放在變長參數之前:

實例

function fwrite(fmt, ...)  ---> 固定的參數fmt
    return io.write(string.format(fmt, ...))     
end

fwrite("runoob\n")       --->fmt = "runoob", 沒有變長參數。  
fwrite("%d%d\n", 1, 2)   --->fmt = "%d%d", 變長參數爲 1 2

輸出結果爲:

runoob
12

通常在遍歷變長參數的時候只需要使用 {…},然而變長參數可能會包含一些 nil,那麼就可以用 select 函數來訪問變長參數了:select('#', …)或者 select(n, …)

    • select('#', …) 返回可變參數的長度
    • select(n, …) 用於返回 n  select('#',…) 的參數

調用select時,必須傳入一個固定實參selector(選擇開關)和一系列變長參數。如果selector爲數字n,那麼select返回它的第n個可變實參,否則只能爲字符串"#",這樣select會返回變長參數的總數。例子代碼:

實例

do  
    
function foo(...)  
        
for i = 1, select('#', ...) do  -->獲取參數總數
            local arg = select(i, ...); -->讀取參數
            print("arg", arg);  
        
end  
    
end  
  
    foo
(1, 2, 3, 4);  
end

輸出結果爲:

arg    1
arg    2
arg    3
arg    4

Lua 運算符

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

  • 算術運算符
  • 關係運算符
  • 邏輯運算符
  • 其他運算符

算術運算符

下表列出了 Lua 語言中的常用算術運算符,設定 A 的值爲10B 的值爲 20

操作符

描述

實例

+

加法

A + B 輸出結果 30

-

減法

A - B 輸出結果 -10

*

乘法

A * B 輸出結果 200

/

除法

B / A w輸出結果 2

%

取餘

B % A 輸出結果 0

^

乘冪

A^2 輸出結果 100

-

負號

-A 輸出結果 -10

實例

我們可以通過以下實例來更加透徹的理解算術運算符的應用:

實例

a = 21
b = 10
c = a + b
print("Line 1 - c 的值爲 ", c )
c = a - b
print("Line 2 - c 的值爲 ", c )
c = a * b
print("Line 3 - c 的值爲 ", c )
c = a / b
print("Line 4 - c 的值爲 ", c )
c = a % b
print("Line 5 - c 的值爲 ", c )
c = a^2
print("Line 6 - c 的值爲 ", c )
c = -a
print("Line 7 - c 的值爲 ", c )

以上程序執行結果爲:

Line 1 - c 的值爲     31
Line 2 - c 的值爲     11
Line 3 - c 的值爲     210
Line 4 - c 的值爲     2.1
Line 5 - c 的值爲     1
Line 6 - c 的值爲     441
Line 7 - c 的值爲     -21

關係運算符

下表列出了 Lua 語言中的常用關係運算符,設定 A 的值爲10B 的值爲 20

操作符

描述

實例

==

等於,檢測兩個值是否相等,相等返回 true,否則返回 false

(A == B) false

~=

不等於,檢測兩個值是否相等,相等返回 false,否則返回 true

(A ~= B) true

>

大於,如果左邊的值大於右邊的值,返回 true,否則返回 false

(A > B) false

<

小於,如果左邊的值大於右邊的值,返回 false,否則返回 true

(A < B) true

>=

大於等於,如果左邊的值大於等於右邊的值,返回 true,否則返回 false

(A >= B) 返回 false

<=

小於等於, 如果左邊的值小於等於右邊的值,返回 true,否則返回 false

(A <= B) 返回 true

實例

我們可以通過以下實例來更加透徹的理解關係運算符的應用:

實例

a = 21
b = 10

if( a == b )
then
   print("Line 1 - a 等於 b" )
else
   print("Line 1 - a 不等於 b" )
end

if( a ~= b )
then
   print("Line 2 - a 不等於 b" )
else
   print("Line 2 - a 等於 b" )
end

if ( a < b )
then
   print("Line 3 - a 小於 b" )
else
   print("Line 3 - a 大於等於 b" )
end

if ( a > b ) 
then
   print("Line 4 - a 大於 b" )
else
   print("Line 5 - a 小於等於 b" )
end

-- 修改 a b 的值
a = 5
b = 20
if ( a <= b ) 
then
   print("Line 5 - a 小於等於  b" )
end

if ( b >= a ) 
then
   print("Line 6 - b 大於等於 a" )
end

以上程序執行結果爲:

Line 1 - a 不等於 b
Line 2 - a 不等於 b
Line 3 - a 大於等於 b
Line 4 - a 大於 b
Line 5 - a 小於等於  b
Line 6 - b 大於等於 a

邏輯運算符

下表列出了 Lua 語言中的常用邏輯運算符,設定 A 的值爲 trueB 的值爲 false

操作符

描述

實例

and

邏輯與操作符。 A false,則返回 A,否則返回 B

(A and B) false

or

邏輯或操作符。 A true,則返回 A,否則返回 B

(A or B) true

not

邏輯非操作符。與邏輯運算結果相反,如果條件爲 true,邏輯非爲 false

not(A and B) true

實例

我們可以通過以下實例來更加透徹的理解邏輯運算符的應用:

實例

a = true
b = true

if ( a and b )
then
   print("a and b - 條件爲 true" )
end

if ( a or b )
then
   print("a or b - 條件爲 true" )
end

print("---------分割線---------" )

-- 修改 a b 的值
a = false
b = true

if ( a and b )
then
   print("a and b - 條件爲 true" )
else
   print("a and b - 條件爲 false" )
end

if ( not( a and b) )
then
   print("not( a and b) - 條件爲 true" )
else
   print("not( a and b) - 條件爲 false" )
end

以上程序執行結果爲:

a and b - 條件爲 true
a or b - 條件爲 true
---------分割線---------
a and b - 條件爲 false
not( a and b) - 條件爲 true

其他運算符

下表列出了 Lua 語言中的連接運算符與計算表或字符串長度的運算符:

操作符

描述

實例

..

連接兩個字符串

a..b ,其中 a "Hello " b "World", 輸出結果爲 "Hello World"

#

一元運算符,返回字符串或表的長度。

#"Hello" 返回 5

實例

我們可以通過以下實例來更加透徹的理解連接運算符與計算表或字符串長度的運算符的應用:

實例

a = "Hello "
b = "World"

print("連接字符串 a b ", a..b )

print("b 字符串長度 ",#)

print("字符串 Test 長度 ",#"Test" )

print("菜鳥教程網址長度 ",#"www.runoob.com" )

以上程序執行結果爲:

連接字符串 a 和 b     Hello World
b 字符串長度     5
字符串 Test 長度     4
菜鳥教程網址長度     14

運算符優先級

從高到低的順序:

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

除了 ^  .. 外所有的二元運算符都是左連接的。

a+i < b/2+1          <-->       (a+i) < ((b/2)+1)
5+x^2*8              <-->       5+((x^2)*8)
a < y and y <= z     <-->       (a < y) and (y <= z)
-x^2                 <-->       -(x^2)
x^y^z                <-->       x^(y^z)

實例

我們可以通過以下實例來更加透徹的瞭解 Lua 語言運算符的優先級:

實例

a = 20
b = 10
c = 15
d = 5

e = (a + b) * c / d;-- ( 30 * 15 ) / 5
print("(a + b) * c / d 運算值爲  :",)

e = ((a + b) * c) / d; -- (30 * 15 ) / 5
print("((a + b) * c) / d 運算值爲 :",)

e = (a + b) * (c / d);-- (30) * (15/5)
print("(a + b) * (c / d) 運算值爲 :",)

e = a + (b * c) / d;  -- 20 + (150/5)
print("a + (b * c) / d 運算值爲   :",)

以上程序執行結果爲:

(a + b) * c / d 運算值爲  :    90.0
((a + b) * c) / d 運算值爲 :    90.0
(a + b) * (c / d) 運算值爲 :    90.0
a + (b * c) / d 運算值爲   :    50.0

Lua 字符串

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

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

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

以上三種方式的字符串實例如下:

實例

string1 = "Lua"
print("\"字符串 1 \"",string1)
string2 = 'runoob.com'
print("字符串 2 ",string2)

string3 = [["Lua 教程"]]
print("字符串 3 ",string3)

以上代碼執行輸出結果爲:

"字符串 1 是"    Lua
字符串 2 是    runoob.com
字符串 3 是    "Lua 教程"

轉義字符用於表示不能直接顯示的字符,比如後退鍵,回車鍵,等。如在字符串轉換雙引號可以使用 "\""

所有的轉義字符和所對應的意義:

轉義字符

意義

ASCII碼值(十進制)

\a

響鈴(BEL)

007

\b

退格(BS) ,將當前位置移到前一列

008

\f

換頁(FF),將當前位置移到下頁開頭

012

\n

換行(LF) ,將當前位置移到下一行開頭

010

\r

回車(CR) ,將當前位置移到本行開頭

013

\t

水平製表(HT) (跳到下一個TAB位置)

009

\v

垂直製表(VT)

011

\\

代表一個反斜線字符''\'

092

\'

代表一個單引號(撇號)字符

039

\"

代表一個雙引號字符

034

\0

空字符(NULL)

000

\ddd

13位八進制數所代表的任意字符

三位八進制

\xhh

12位十六進制所代表的任意字符

二位十六進制


字符串操作

Lua 提供了很多的方法來支持字符串的操作:

序號

方法 & 用途

1

string.upper(argument):
字符串全部轉爲大寫字母。

2

string.lower(argument):
字符串全部轉爲小寫字母。

3

string.gsub(mainString,findString,replaceString,num)

在字符串中替換。

mainString 爲要操作的字符串, findString 爲被替換的字符,replaceString 要替換的字符,num 替換次數(可以忽略,則全部替換),如:

> string.gsub("aaaa","a","z",3);
zzza    3

4

string.find (str, substr, [init, [end]])
在一個指定的目標字符串中搜索指定的內容(第三個參數爲索引),返回其具體位置。不存在則返回 nil

> string.find("Hello Lua user", "Lua", 1) 
7    9

5

string.reverse(arg)
字符串反轉

> string.reverse("Lua")
auL

6

string.format(...)
返回一個類似printf的格式化字符串

> string.format("the value is:%d",4)
the value is:4

7

string.char(arg) string.byte(arg[,int])
char 將整型數字轉成字符並連接, byte 轉換字符爲整數值(可以指定某個字符,默認第一個字符)

> string.char(97,98,99,100)
abcd
> string.byte("ABCD",4)
68
> string.byte("ABCD")
65
>

8

string.len(arg)
計算字符串長度。

string.len("abc")
3

9

string.rep(string, n)
返回字符串stringn個拷貝

> string.rep("abcd",2)
abcdabcd

10

..
鏈接兩個字符串

> print("www.runoob.".."com")
www.runoob.com

11

string.gmatch(str, pattern)
回一個迭代器函數,每一次調用這個函數,返回一個在字符串 str 找到的下一個符合 pattern 描述的子串。如果參數 pattern 描述的字符串沒有找到,迭代函數返回nil

> for word in string.gmatch("Hello Lua user", "%a+") do print(word) end
Hello
Lua
user

12

string.match(str, pattern, init)
string.match()只尋找源字串str中的第一個配對. 參數init可選, 指定搜尋過程的起點, 默認爲1 
在成功配對時, 函數將返回配對表達式中的所有捕獲結果; 如果沒有設置捕獲標記, 則返回整個配對字符串. 當沒有成功的配對時, 返回nil

> = string.match("I have 2 questions for you.", "%d+ %a+")
2 questions

 
> = string.format("%d, %q", string.match("I have 2 questions for you.", "(%d+) (%a+)"))
2, "questions"

字符串大小寫轉換

以下實例演示瞭如何對字符串大小寫進行轉換:

實例

string1 = "Lua";
print(string.upper(string1))
print(string.lower(string1))

以上代碼執行結果爲:

LUA
lua

字符串查找與反轉

以下實例演示瞭如何對字符串進行查找與反轉操作:

實例

string = "Lua Tutorial"
-- 查找字符串
print(string.find(string,"Tutorial"))
reversedString = string.reverse(string)
print("新字符串爲",reversedString)

以上代碼執行結果爲:

5    12
新字符串爲    lairotuT auL

字符串格式化

Lua 提供了 string.format() 函數來生成具有特定格式的字符串, 函數的第一個參數是格式 , 之後是對應格式中每個代號的各種數據。

由於格式字符串的存在, 使得產生的長字符串可讀性大大提高了。這個函數的格式很像 C 語言中的 printf()

以下實例演示瞭如何對字符串進行格式化操作:

格式字符串可能包含以下的轉義碼:

  • %c - 接受一個數字, 並將其轉化爲ASCII碼錶中對應的字符
  • %d, %i - 接受一個數字並將其轉化爲有符號的整數格式
  • %o - 接受一個數字並將其轉化爲八進制數格式
  • %u - 接受一個數字並將其轉化爲無符號整數格式
  • %x - 接受一個數字並將其轉化爲十六進制數格式, 使用小寫字母
  • %X - 接受一個數字並將其轉化爲十六進制數格式, 使用大寫字母
  • %e - 接受一個數字並將其轉化爲科學記數法格式, 使用小寫字母e
  • %E - 接受一個數字並將其轉化爲科學記數法格式, 使用大寫字母E
  • %f - 接受一個數字並將其轉化爲浮點數格式
  • %g(%G) - 接受一個數字並將其轉化爲%e(%E, 對應%G)%f中較短的一種格式
  • %q - 接受一個字符串並將其轉化爲可安全被Lua編譯器讀入的格式
  • %s - 接受一個字符串並按照給定的參數格式化該字符串

爲進一步細化格式, 可以在%號後添加參數. 參數將以如下的順序讀入:

  • (1) 符號: 一個+號表示其後的數字轉義符將讓正數顯示正號. 默認情況下只有負數顯示符號.
  • (2) 佔位符: 一個0, 在後面指定了字串寬度時佔位用. 不填時的默認佔位符是空格.
  • (3) 對齊標識: 在指定了字串寬度時, 默認爲右對齊, 增加-號可以改爲左對齊.
  • (4) 寬度數值
  • (5) 小數位數/字串裁切: 在寬度數值後增加的小數部分n, 若後接f(浮點數轉義符, %6.3f)則設定該浮點數的小數只保留n, 若後接s(字符串轉義符, %5.3s)則設定該字符串只顯示前n.

實例

string1 = "Lua"
string2 = "Tutorial"
number1 = 10
number2 = 20
-- 基本字符串格式化
print(string.format("基本格式化 %s %s",string1,string2))
-- 日期格式化
date = 2; month = 1; year = 2014
print(string.format("日期格式化 %02d/%02d/%03d", date, month, year))
-- 十進制格式化
print(string.format("%.4f",1/3))

以上代碼執行結果爲:

基本格式化 Lua Tutorial
日期格式化 02/01/2014
0.3333

其他例子:

實例

string.format("%c", 83)                 -- 輸出S
string.format("%+d", 17.0)              -- 輸出+17
string.format("%05d", 17)               -- 輸出00017
string.format("%o", 17)                 -- 輸出21
string.format("%u", 3.14)               -- 輸出3
string.format("%x", 13)                 -- 輸出d
string.format("%X", 13)                 -- 輸出D
string.format("%e", 1000)               -- 輸出1.000000e+03
string.format("%E", 1000)               -- 輸出1.000000E+03
string.format("%6.3f", 13)              -- 輸出13.000
string.format("%q", "One\nTwo")         -- 輸出"One\
                                        --   Two"
string.format("%s", "monkey")           -- 輸出monkey
string.format("%10s", "monkey")         -- 輸出    monkey
string.format("%5.3s", "monkey")        -- 輸出  mon

字符與整數相互轉換

以下實例演示了字符與整數相互轉換:

實例

-- 字符轉換
-- 轉換第一個字符
print(string.byte("Lua"))
-- 轉換第三個字符
print(string.byte("Lua",3))
-- 轉換末尾第一個字符
print(string.byte("Lua",-1))
-- 第二個字符
print(string.byte("Lua",2))
-- 轉換末尾第二個字符
print(string.byte("Lua",-2))

-- 整數 ASCII 碼轉換爲字符
print(string.char(97))

以上代碼執行結果爲:

76
97
97
117
117
a

其他常用函數

以下實例演示了其他字符串操作,如計算字符串長度,字符串連接,字符串複製等:

實例

string1 = "www."
string2 = "runoob"
string3 = ".com"
-- 使用 .. 進行字符串連接
print("連接字符串",string1..string2..string3)

-- 字符串長度
print("字符串長度 ",string.len(string2))

-- 字符串複製 2
repeatedString = string.rep(string2,2)
print(repeatedString)

以上代碼執行結果爲:

連接字符串    www.runoob.com
字符串長度     6
runoobrunoob

匹配模式

Lua 中的匹配模式直接用常規的字符串來描述。 它用於模式匹配函數 string.find, string.gmatch, string.gsub, string.match

你還可以在模式串中使用字符類。

字符類指可以匹配一個特定字符集合內任何字符的模式項。比如,字符類 %d 匹配任意數字。所以你可以使用模式串 %d%d/%d%d/%d%d%d%d 搜索 dd/mm/yyyy 格式的日期:

實例

s = "Deadline is 30/05/1999, firm"
date = "%d%d/%d%d/%d%d%d%d"
print(string.sub(s, string.find(s, date)))    --> 30/05/1999

下面的表列出了Lua支持的所有字符類:

單個字符( ^$()%.[]*+-? ): 與該字符自身配對

  • .(): 與任何字符配對
  • %a: 與任何字母配對
  • %c: 與任何控制符配對(例如\n)
  • %d: 與任何數字配對
  • %l: 與任何小寫字母配對
  • %p: 與任何標點(punctuation)配對
  • %s: 與空白字符配對
  • %u: 與任何大寫字母配對
  • %w: 與任何字母/數字配對
  • %x: 與任何十六進制數配對
  • %z: 與任何代表0的字符配對
  • %x(此處x是非字母非數字字符): 與字符x配對. 主要用來處理表達式中有功能的字符(^$()%.[]*+-?)的配對問題, 例如%%%配對
  • [數個字符類]: 與任何[]中包含的字符類配對. 例如[%w_]與任何字母/數字, 或下劃線符號(_)配對
  • [^數個字符類]: 與任何不包含在[]中的字符類配對. 例如[^%s]與任何非空白字符配對

當上述的字符類用大寫書寫時, 表示與非此字符類的任何字符配對. 例如, %S表示與任何非空白字符配對.例如,'%A'非字母的字符:

> print(string.gsub("hello, up-down!", "%A", "."))
hello..up.down.    4

數字4不是字符串結果的一部分,他是gsub返回的第二個結果,代表發生替換的次數。

在模式匹配中有一些特殊字符,他們有特殊的意義,Lua中的特殊字符如下:

( ) . % + - * ? [ ^ $

'%' 用作特殊字符的轉義字符,因此 '%.' 匹配點;'%%' 匹配字符 '%'。轉義字符 '%'不僅可以用來轉義特殊字符,還可以用於所有的非字母的字符。

模式條目可以是:

  • 單個字符類匹配該類別中任意單個字符;
  • 單個字符類跟一個 '*' 將匹配零或多個該類的字符。 這個條目總是匹配儘可能長的串;
  • 單個字符類跟一個 '+' 將匹配一或更多個該類的字符。 這個條目總是匹配儘可能長的串;
  • 單個字符類跟一個 '-' 將匹配零或更多個該類的字符。 '*' 不同, 這個條目總是匹配儘可能短的串;
  • 單個字符類跟一個 '?' 將匹配零或一個該類的字符。 只要有可能,它會匹配一個;
  • %n 這裏的 n 可以從 1 9 這個條目匹配一個等於 n 號捕獲物(後面有描述)的子串。
  • %bxy 這裏的 x  y 是兩個明確的字符; 這個條目匹配以 x 開始 y 結束, 且其中 x  y 保持 平衡 的字符串。 意思是,如果從左到右讀這個字符串,對每次讀到一個 x  +1 ,讀到一個 y  -1 最終結束處的那個 y 是第一個記數到 0  y 舉個例子,條目 %b() 可以匹配到括號平衡的表達式。
  • %f[set]  邊境模式 這個條目會匹配到一個位於 set 內某個字符之前的一個空串, 且這個位置的前一個字符不屬於 set  集合 set 的含義如前面所述。 匹配出的那個空串之開始和結束點的計算就看成該處有個字符 '\0' 一樣。

模式:

模式 指一個模式條目的序列。 在模式最前面加上符號 '^' 將錨定從字符串的開始處做匹配。 在模式最後面加上符號 '$' 將使匹配過程錨定到字符串的結尾。 如果 '^' '$' 出現在其它位置,它們均沒有特殊含義,只表示自身。

捕獲:

模式可以在內部用小括號括起一個子模式; 這些子模式被稱爲 捕獲物 當匹配成功時,由 捕獲物 匹配到的字符串中的子串被保存起來用於未來的用途。 捕獲物以它們左括號的次序來編號。 例如,對於模式 "(a*(.)%w(%s*))"  字符串中匹配到 "a*(.)%w(%s*)" 的部分保存在第一個捕獲物中 (因此是編號 1 ); "." 匹配到的字符是 2 號捕獲物, 匹配到 "%s*" 的那部分是 3 號。

作爲一個特例,空的捕獲 () 將捕獲到當前字符串的位置(它是一個數字)。 例如,如果將模式 "()aa()" 作用到字符串 "flaaap" 上,將產生兩個捕獲物: 3 5

Lua 數組

數組,就是相同數據類型的元素按一定順序排列的集合,可以是一維數組和多維數組。

Lua 數組的索引鍵值可以使用整數表示,數組的大小不是固定的。


一維數組

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

實例

array = {"Lua", "Tutorial"}

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

以上代碼執行輸出結果爲:

nil
Lua
Tutorial

正如你所看到的,我們可以使用整數索引來訪問數組元素,如果知道的索引沒有值則返回nil

Lua 索引值是以 1 爲起始,但你也可以指定 0 開始。

除此外我們還可以以負數爲數組索引值:

實例

array = {}

for i= -2, 2 do
   array[i] = i *2
end

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

以上代碼執行輸出結果爲:

-4
-2
0
2
4

多維數組

多維數組即數組中包含數組或一維數組的索引鍵對應一個數組。

以下是一個三行三列的陣列多維數組:

實例

-- 初始化數組
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

以上代碼執行輸出結果爲:

1
2
3
2
4
6
3
6
9

不同索引鍵的三行三列陣列多維數組:

實例

-- 初始化數組
array = {}
maxRows = 3
maxColumns = 3
for row=1,maxRows do
   for col=1,maxColumns do
      array[row*maxColumns +col] = row*col
   
end
end

-- 訪問數組
for row=1,maxRows do
   for col=1,maxColumns do
      print(array[row*maxColumns +col])
   end
end

以上代碼執行輸出結果爲:

1
2
3
2
4
6
3
6
9

正如你所看到的,以上的實例中,數組設定了指定的索引值,這樣可以避免出現 nil 值,有利於節省內存空間。

Lua 迭代器

迭代器(iterator)是一種對象,它能夠用來遍歷標準模板庫容器中的部分或全部元素,每個迭代器對象代表容器中的確定的地址。

Lua 中迭代器是一種支持指針類型的結構,它可以遍歷集合的每一個元素。


泛型 for 迭代器

泛型 for 在自己內部保存迭代函數,實際上它保存三個值:迭代函數、狀態常量、控制變量。

泛型 for 迭代器提供了集合的 key/value 對,語法格式如下:

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

上面代碼中,k, v爲變量列表;pairs(t)爲表達式列表。

查看以下實例:

實例

array = {"Google", "Runoob"}

for key,value in ipairs(array) 
do
   print(key, value)
end

以上代碼執行輸出結果爲:

1  Google
2  Runoob

以上實例中我們使用了 Lua 默認提供的迭代函數 ipairs

下面我們看看泛型 for 的執行過程:

  • 首先,初始化,計算 in 後面表達式的值,表達式應該返回泛型 for 需要的三個值:迭代函數、狀態常量、控制變量;與多值賦值一樣,如果表達式返回的結果個數不足三個會自動用 nil 補足,多出部分會被忽略。
  • 第二,將狀態常量和控制變量作爲參數調用迭代函數(注意:對於 for 結構來說,狀態常量沒有用處,僅僅在初始化時獲取他的值並傳遞給迭代函數)。
  • 第三,將迭代函數返回的值賦給變量列表。
  • 第四,如果返回的第一個值爲nil循環結束,否則執行循環體。
  • 第五,回到第二步再次調用迭代函數

Lua中我們常常使用函數來描述迭代器,每次調用該函數就返回集合的下一個元素。Lua 的迭代器包含以下兩種類型:

  • 無狀態的迭代器
  • 多狀態的迭代器

無狀態的迭代器

無狀態的迭代器是指不保留任何狀態的迭代器,因此在循環中我們可以利用無狀態迭代器避免創建閉包花費額外的代價。

每一次迭代,迭代函數都是用兩個變量(狀態常量和控制變量)的值作爲參數被調用,一個無狀態的迭代器只利用這兩個值可以獲取下一個元素。

這種無狀態迭代器的典型的簡單的例子是 ipairs,它遍歷數組的每一個元素。

以下實例我們使用了一個簡單的函數來實現迭代器,實現 數字 n 的平方:

實例

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

以上實例輸出結果爲:

1    1
2    4
3    9

迭代的狀態包括被遍歷的表(循環過程中不會改變的狀態常量)和當前的索引下標(控制變量),ipairs 和迭代函數都很簡單,我們在 Lua 中可以這樣實現:

實例

function iter (a, i)
    i = i + 1
    local v = a[i]
    if v then
       return i, v
    
end
end
 
function ipairs (a)
    return iter, a, 0
end

Lua 調用 ipairs(a) 開始循環時,他獲取三個值:迭代函數 iter、狀態常量 a、控制變量初始值 0;然後 Lua 調用 iter(a,0) 返回 1, a[1](除非 a[1]=nil);第二次迭代調用 iter(a,1) 返回 2, a[2]……直到第一個 nil 元素。


多狀態的迭代器

很多情況下,迭代器需要保存多個狀態信息而不是簡單的狀態常量和控制變量,最簡單的方法是使用閉包,還有一種方法就是將所有的狀態信息封裝到 table 內,將 table 作爲迭代器的狀態常量,因爲這種情況下可以將所有的信息存放在 table 內,所以迭代函數通常不需要第二個參數。

以下實例我們創建了自己的迭代器:

實例

array = {"Google", "Runoob"}

function elementIterator (collection)
   local index = 0
   local count = #collection
   
-- 閉包函數
   return function ()
      index = index + 1
      if index <= count
      
then
         --  返回迭代器的當前元素
         return collection[index]
      end
   end
end

for element in elementIterator(array)
do
   print(element)
end

以上實例輸出結果爲:

Google
Runoob

以上實例中我們可以看到,elementIterator 內使用了閉包函數,實現計算集合大小並輸出各個元素。

Lua table()

table Lua 的一種數據結構用來幫助我們創建不同的數據類型,如:數組、字典等。

Lua table 使用關聯型數組,你可以用任意類型的值來作數組的索引,但這個值不能是 nil

Lua table 是不固定大小的,你可以根據自己需要進行擴容。

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


table()的構造

構造器是創建和初始化表的表達式。表是Lua特有的功能強大的東西。最簡單的構造函數是{},用來創建一個空表。可以直接初始化數組:

-- 初始化表
mytable = {}

-- 指定值
mytable[1]= "Lua"

-- 移除引用
mytable = nil
-- lua 垃圾回收會釋放內存

當我們爲 table a 並設置元素,然後將 a 賦值給 b,則 a b 都指向同一個內存。如果 a 設置爲 nil ,則 b 同樣能訪問 table 的元素。如果沒有指定的變量指向aLua的垃圾回收機制會清理相對應的內存。

以下實例演示了以上的描述情況:

實例

-- 簡單的 table
mytable = {}
print("mytable 的類型是 ",type(mytable))

mytable[1]= "Lua"
mytable["wow"] = "修改前"
print("mytable 索引爲 1 的元素是 ", mytable[1])
print("mytable 索引爲 wow 的元素是 ", mytable["wow"])

-- alternatetablemytable的是指同一個 table
alternatetable = mytable

print("alternatetable 索引爲 1 的元素是 ", alternatetable[1])
print("mytable 索引爲 wow 的元素是 ", alternatetable["wow"])

alternatetable["wow"] = "修改後"

print("mytable 索引爲 wow 的元素是 ", mytable["wow"])

-- 釋放變量
alternatetable = nil
print("alternatetable ", alternatetable)

-- mytable 仍然可以訪問
print("mytable 索引爲 wow 的元素是 ", mytable["wow"])

mytable = nil
print("mytable ", mytable)

以上代碼執行結果爲:

mytable 的類型是     table
mytable 索引爲 1 的元素是     Lua
mytable 索引爲 wow 的元素是     修改前
alternatetable 索引爲 1 的元素是     Lua
mytable 索引爲 wow 的元素是     修改前
mytable 索引爲 wow 的元素是     修改後
alternatetable 是     nil
mytable 索引爲 wow 的元素是     修改後
mytable 是     nil

Table 操作

以下列出了 Table 操作常用的方法:

序號

方法 & 用途

1

table.concat (table [, sep [, start [, end]]]):

concatconcatenate(連鎖, 連接)的縮寫. table.concat()函數列出參數中指定table的數組部分從start位置到end位置的所有元素, 元素間以指定的分隔符(sep)隔開。

2

table.insert (table, [pos,] value):

table的數組部分指定位置(pos)插入值爲value的一個元素. pos參數可選, 默認爲數組部分末尾.

3

table.maxn (table)

指定table中所有正數key值中最大的key. 如果不存在key值爲正數的元素, 則返回0(Lua5.2之後該方法已經不存在了,本文使用了自定義函數實現)

4

table.remove (table [, pos])

返回table數組部分位於pos位置的元素. 其後的元素會被前移. pos參數可選, 默認爲table長度, 即從最後一個元素刪起。

5

table.sort (table [, comp])

對給定的table進行升序排序。

接下來我們來看下這幾個方法的實例。

Table 連接

我們可以使用 concat() 輸出一個列表中元素連接成的字符串:

實例

fruits = {"banana","orange","apple"}
-- 返回 table 連接後的字符串
print("連接後的字符串 ",table.concat(fruits))

-- 指定連接字符
print("連接後的字符串 ",table.concat(fruits,", "))

-- 指定索引來連接 table
print("連接後的字符串 ",table.concat(fruits,", ", 2,3))

執行以上代碼輸出結果爲:

連接後的字符串     bananaorangeapple
連接後的字符串     banana, orange, apple
連接後的字符串     orange, apple

插入和移除

以下實例演示了 table 的插入和移除操作:

實例

fruits = {"banana","orange","apple"}

-- 在末尾插入
table.insert(fruits,"mango")
print("索引爲 4 的元素爲 ",fruits[4])

-- 在索引爲 2 的鍵處插入
table.insert(fruits,2,"grapes")
print("索引爲 2 的元素爲 ",fruits[2])

print("最後一個元素爲 ",fruits[5])
table.remove(fruits)
print("移除後最後一個元素爲 ",fruits[5])

執行以上代碼輸出結果爲:

索引爲 4 的元素爲     mango
索引爲 2 的元素爲     grapes
最後一個元素爲     mango
移除後最後一個元素爲     nil

Table 排序

以下實例演示了 sort() 方法的使用,用於對 Table 進行排序:

實例

fruits = {"banana","orange","apple","grapes"}
print("排序前")
for k,v in ipairs(fruits) do
        print(k,v)
end

table.sort(fruits)
print("排序後")
for k,v in ipairs(fruits) do
        print(k,v)
end

執行以上代碼輸出結果爲:

排序前
1    banana
2    orange
3    apple
4    grapes
排序後
1    apple
2    banana
3    grapes
4    orange

Table 最大值

table.maxn Lua5.2 之後該方法已經不存在了,我們定義了 table_maxn 方法來實現。

以下實例演示瞭如何獲取 table 中的最大值:

實例

function table_maxn(t)
  local mn=nil;
  for k, v in pairs(t) do
    if(mn==nil) then
      mn=v
    
end
    if mn < v then
      mn = v
    
end
  end
  return mn
end
tbl = {[1] = 2, [2] = 6, [3] = 34, [26] =5}
print("tbl 最大值:", table_maxn(tbl))
print("tbl 長度 ", #tbl)

執行以上代碼輸出結果爲:

tbl 最大值:    34
tbl 長度     3

注意:

當我們獲取 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 模塊與包

模塊類似於一個封裝庫,從 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

由上可知,模塊的結構就是一個 table 的結構,因此可以像操作調用 table 裏的元素那樣來操作調用模塊裏的常量或函數。

上面的 func2 聲明爲程序塊的局部變量,即表示一個私有函數,因此是不能從外部訪問模塊裏的這個私有函數,必須通過模塊裏的公有函數來調用.


require 函數

Lua提供了一個名爲require的函數用來加載模塊。要加載一個模塊,只需要簡單地調用就可以了。例如:

require("<模塊名>")

或者

require "<模塊名>"

執行 require 後會返回一個由模塊常量或函數組成的 table,並且還會定義一個包含該 table 的全局變量。

test_module.lua 文件

-- test_module.lua 文件
-- module 模塊爲上文提到到 module.lua
require("module")
 
print(module.constant)
 
module.func3
()

以上代碼執行結果爲:

這是一個常量
這是一個私有函數!

或者給加載的模塊定義一個別名變量,方便調用:

test_module2.lua 文件

-- test_module2.lua 文件
-- module 模塊爲上文提到到 module.lua
-- 別名變量 m
local m = require("module")
 
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

LuaC是很容易結合的,使用 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 元表(Metatable)

Lua table 中我們可以訪問對應的key來得到value值,但是卻無法對兩個 table 進行操作。

因此 Lua 提供了元表(Metatable),允許我們改變table的行爲,每個行爲關聯了對應的元方法。

例如,使用元表我們可以定義Lua如何計算兩個table的相加操作a+b

Lua試圖對兩個表進行相加時,先檢查兩者之一是否有元表,之後檢查是否有一個叫"__add"的字段,若找到,則調用對應的值。"__add"等即時字段,其對應的值(往往是一個函數或是table)就是"元方法"

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

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

以下實例演示瞭如何對指定的表設置元表:

mytable = {}                          -- 普通表 
mymetatable = {}                      -- 元表
setmetatable(mytable,mymetatable)     -- mymetatable 設爲 mytable 的元表 

以上代碼也可以直接寫成一行:

mytable = setmetatable({},{})

以下爲返回對象元表:

getmetatable(mytable)                 -- 這回返回mymetatable

__index 元方法

這是 metatable 最常用的鍵。

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

我們可以在使用 lua 命令進入交互模式查看:

$ lua
Lua 5.3.0  Copyright 
(C) 1994-2015 Lua.org, PUC-Rio
> other = 
{ foo = 3 } 
> t = 
setmetatable({}, { __index = other }) 
> t.foo

3
> t.bar
nil

如果__index包含一個函數的話,Lua就會調用那個函數,table和鍵會作爲參數傳遞給函數。

__index 元方法查看錶中元素是否存在,如果不存在,返回結果爲 nil;如果存在則由 __index 返回結果。

實例

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

實例解析:

  • mytable 表賦值爲 {key1 = "value1"}
  • mytable 設置了元表,元方法爲 __index
  • mytable表中查找 key1,如果找到,返回該元素,找不到則繼續。
  • mytable表中查找 key2,如果找到,返回 metatablevalue,找不到則繼續。
  • 判斷元表有沒有__index方法,如果__index方法是一個函數,則調用該函數。
  • 元方法中查看是否傳入 "key2" 鍵的參數(mytable.key2已設置),如果傳入 "key2" 參數返回 "metatablevalue",否則返回 mytable 對應的鍵值。

我們可以將以上代碼簡單寫成:

mytable = setmetatable({key1 = "value1"}, { __index = { key2 = "metatablevalue" } })
print(mytable.key1,mytable.key2)

總結

Lua 查找一個表元素時的規則,其實就是如下 3 個步驟:

  • 1.在表中查找,如果找到,返回該元素,找不到則繼續
  • 2.判斷該表是否有元表,如果沒有元表,返回 nil,有元表則繼續。
  • 3.判斷元表有沒有 __index 方法,如果 __index 方法爲 nil,則返回 nil;如果 __index 方法是一個表,則重複 123;如果 __index 方法是一個函數,則返回該函數的返回值。

該部分內容來自作者寰子:https://blog.csdn.net/xocoder/article/details/9028347


__newindex 元方法

__newindex 元方法用來對錶更新,__index則用來對錶訪問

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

以下實例演示了 __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

以上實例中表設置了元方法 __newindex,在對新索引鍵(newkey)賦值時(mytable.newkey = "新值2"),會調用元方法,而不進行賦值。而如果對已存在的索引鍵(key1),則會進行賦值,而不調用元方法 __newindex

以下實例使用了 rawset 函數來更新表:

實例

mytable = setmetatable({key1 = "value1"}, {
  __newindex = function(mytable, key, value)
                rawset(mytable, key, "\""..value.."\"")

  end
})

mytable.key1 = "new value"
mytable.key2 = 4

print(mytable.key1,mytable.key2)

以上實例執行輸出結果爲:

new value    "4"

爲表添加操作符

以下實例演示了兩表相加操作:

實例

-- 計算表中最大值,table.maxnLua5.2以上版本中已無法使用
-- 自定義計算表中最大鍵值函數 table_maxn,即計算表的元素個數
function table_maxn(t)
    local mn = 0
    for k, v in pairs(t) do
        if mn < k then
            mn = k
        
end
    end
    return mn
end

-- 兩表相加操作
mytable = setmetatable({ 1, 2, 3 }, {
  __add = function(mytable, newtable)
    for i = 1, table_maxn(newtable) do
      table.insert(mytable, table_maxn(mytable)+1,newtable[i])
    end
    return mytable
  
end
})

secondtable = {4,5,6}

mytable = mytable + secondtable
        
for k,v in ipairs(mytable) do
print(k,v)
end

以上實例執行輸出結果爲:

1    1
2    2
3    3
4    4
5    5
6    6

__add 鍵包含在元表中,並進行相加操作。 表中對應的操作列表如下:(注意:__是兩個下劃線)

模式

描述

__add

對應的運算符 '+'.

__sub

對應的運算符 '-'.

__mul

對應的運算符 '*'.

__div

對應的運算符 '/'.

__mod

對應的運算符 '%'.

__unm

對應的運算符 '-'.

__concat

對應的運算符 '..'.

__eq

對應的運算符 '=='.

__lt

對應的運算符 '<'.

__le

對應的運算符 '<='.


__call 元方法

__call 元方法在 Lua 調用一個值時調用。以下實例演示了計算表中元素的和:

實例

-- 計算表中最大值,table.maxnLua5.2以上版本中已無法使用
-- 自定義計算表中最大鍵值函數 table_maxn,即計算表的元素個數
function table_maxn(t)
    local mn = 0
    for k, v in pairs(t) do
        if mn < k then
            mn = k
        
end
    end
    return mn
end

-- 定義元方法__call
mytable = setmetatable({10}, {
  __call = function(mytable, newtable)
        sum = 0
        for i = 1, table_maxn(mytable) do
                sum = sum + mytable[i]
        end
    for i = 1, table_maxn(newtable) do
                sum = sum + newtable[i]
        end
        return sum
  
end
})
newtable = {10,20,30}
print(mytable(newtable))

以上實例執行輸出結果爲:

70

__tostring 元方法

__tostring 元方法用於修改表的輸出行爲。以下實例我們自定義了表的輸出內容:

實例

mytable = setmetatable({ 10, 20, 30 }, {
  __tostring = function(mytable)
    sum = 0
    for k, v in pairs(mytable) do
                sum = sum + v
        
end
    return "表所有元素的和爲 " .. sum
  
end
})
print(mytable)

以上實例執行輸出結果爲:

表所有元素的和爲 60

從本文中我們可以知道元表可以很好的簡化我們的代碼功能,所以瞭解 Lua 的元表,可以讓我們寫出更加簡單優秀的 Lua 代碼。

Lua 協同程序(coroutine)


什麼是協同(coroutine)

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

協同是非常強大的功能,但是用起來也很複雜。

線程和協同程序區別

線程與協同程序的主要區別在於,一個具有多個線程的程序可以同時運行幾個線程,而協同程序卻需要彼此協作的運行。

在任一指定時刻只有一個協同程序在運行,並且這個正在運行的協同程序只有在明確的被要求掛起的時候纔會被掛起。

協同程序有點類似同步的多線程,在等待同一個線程鎖的幾個線程有點類似協同。

基本語法

方法

描述

coroutine.create()

創建 coroutine,返回 coroutine 參數是一個函數,當和 resume 配合使用的時候就喚醒函數調用

coroutine.resume()

重啓 coroutine,和 create 配合使用

coroutine.yield()

掛起 coroutine,將 coroutine 設置爲掛起狀態,這個和 resume 配合使用能有很多有用的效果

coroutine.status()

查看 coroutine 的狀態
注:coroutine 的狀態有三種:deadsuspendedrunning,具體什麼時候有這樣的狀態請參考下面的程序

coroutine.wrap()

創建 coroutine,返回一個函數,一旦你調用這個函數,就進入 coroutine,和 create 功能重複

coroutine.running()

返回正在跑的 coroutine,一個 coroutine 就是一個線程,當使用running的時候,就是返回一個 corouting 的線程號

以下實例演示了以上各個方法的用法:

coroutine_test.lua 文件

-- 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("----------")

以上實例執行輸出結果爲:

1
dead
----------
1
----------
1
2
3
running
thread: 0x7fb801c05868    false
suspended
thread: 0x7fb801c04c88    true
----------

coroutine.running就可以看出來,coroutine在底層實現就是一個線程。

create一個coroutine的時候就是在新線程中註冊了一個事件。

當使用resume觸發事件的時候,createcoroutine函數就被執行了,當遇到yield的時候就代表掛起當前線程,等候再次resume觸發事件。

接下來我們分析一個更詳細的實例:

實例

function foo (a)
    print("foo 函數輸出", a)
    return coroutine.yield(2 * a) -- 返回  2*a 的值
end
 
co = 
coroutine.create(function (a , b)
    print("第一次協同程序執行輸出", a, b) -- co-body 1 10
    local r = foo(a + 1)
     
    
print("第二次協同程序執行輸出", r)
    local r, s = coroutine.yield(a + b, a - b)  -- ab的值爲第一次調用協同程序時傳入
     
    
print("第三次協同程序執行輸出", r, s)
    return b, "結束協同程序"                   -- b的值爲第二次調用協同程序時傳入
end)
        
print("main", coroutine.resume(co, 1, 10)) -- true, 4
print("--分割線----")
print("main", coroutine.resume(co, "r")) -- true 11 -9
print("---分割線---")
print("main", coroutine.resume(co, "x", "y")) -- true 10 end
print("---分割線---")
print("main", coroutine.resume(co, "x", "y")) -- cannot resume dead coroutine
print("---分割線---")

以上實例執行輸出結果爲:

第一次協同程序執行輸出    1    10
foo 函數輸出    2
main    true    4
--分割線----
第二次協同程序執行輸出    r
main    true    11    -9
---分割線---
第三次協同程序執行輸出    x    y
main    true    10    結束協同程序
---分割線---
main    false    cannot resume dead coroutine
---分割線---

以上實例接下如下:

  • 調用resume,將協同程序喚醒,resume操作成功返回true,否則返回false
  • 協同程序運行;
  • 運行到yield語句;
  • yield掛起協同程序,第一次resume返回;(注意:此處yield返回,參數是resume的參數)
  • 第二次resume,再次喚醒協同程序;(注意:此處resume的參數中,除了第一個參數,剩下的參數將作爲yield的參數)
  • yield返回;
  • 協同程序繼續運行;
  • 如果使用的協同程序繼續運行完成後繼續調用 resume方法則輸出:cannot resume dead coroutine

resumeyield的配合強大之處在於,resume處於主程中,它將外部狀態(數據)傳入到協同程序內部;而yield則將內部的狀態(數據)返回到主程中。


生產者-消費者問題

現在我就使用Lua的協同程序來完成生產者-消費者這一經典問題。

實例

local newProductor

function productor()
     local i = 0
     while true do
          i = i + 1
          send(i)     -- 將生產的物品發送給消費者
     end
end

function consumer()
     while true do
          local i = receive()     -- 從生產者那裏得到物品
          print(i)
     end
end

function receive()
     local status, value = coroutine.resume(newProductor)
     return value
end

function send(x)
     coroutine.yield(x)     -- x表示需要發送的值,值返回以後,就掛起該協同程序
end

-- 啓動程序
newProductor = coroutine.create(productor)
consumer()

以上實例執行輸出結果爲:

1
2
3
4
5
6
7
8
9
10
11
12
13
……

Lua 文件 I/O

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

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

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

打開文件操作語句如下:

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

mode 的值有:

模式

描述

r

以只讀方式打開文件,該文件必須存在。

w

打開只寫文件,若文件存在則文件長度清爲0,即該文件內容會消失。若文件不存在則建立該文件。

a

以附加的方式打開只寫文件。若文件不存在,則會建立該文件,如果文件存在,寫入的數據會被加到文件尾,即文件原先的內容會被保留。(EOF符保留)

r+

以可讀寫方式打開文件,該文件必須存在。

w+

打開可讀寫文件,若文件存在則文件長度清爲零,即該文件內容會消失。若文件不存在則建立該文件。

a+

a類似,但此文件可讀可寫

b

二進制模式,如果文件是二進制文件,可以加上b

+

號表示對文件既可以讀也可以寫


簡單模式

簡單模式使用標準的 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)

執行以上代碼,你會發現,輸出了 test.lua 文件的第一行信息,並在該文件最後一行添加了 lua 的註釋。如我這邊輸出的是:

-- test.lua 文件

在以上實例中我們使用了 io."x" 方法,其中 io.read() 中我們沒有帶參數,參數可以是下表中的一個:

模式

描述

"*n"

讀取一個數字並返回它。例:file.read("*n")

"*a"

從當前位置讀取整個文件。例:file.read("*a")

"*l"(默認)

讀取下一行,在文件尾 (EOF) 處返回 nil。例:file.read("*l")

number

返回一個指定字符個數的字符串,或在 EOF 時返回 nil。例:file.read(5)

其他的 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()

執行以上代碼,你會發現,輸出了 test.ua 文件的第一行信息,並在該文件最後一行添加了 lua 的註釋。如我這邊輸出的是:

-- test.lua 文件

read 的參數與簡單模式一致。

其他方法:

  • 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(); 讀取默認輸入設備的內容,但結束時不關閉文件,如:
  • for line in io.lines("main.lua") do
  •  
  •   print(line)
  •  
  end

以下實例使用了 seek 方法,定位到文件倒數第 25 個位置並使用 read 方法的 *a 參數,即從當期位置(倒數第 25 個位置)讀取整個文件。

實例

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

file:seek("end",-25)
print(file:read("*a"))

-- 關閉打開的文件
file:close()
</pre>
<p>
我這邊輸出的結果是:</p>
<pre>
st.lua
文件末尾--test

Lua 錯誤處理

程序運行中錯誤處理是必要的,在我們進行文件操作,數據轉移及web service 調用過程中都會出現不可預期的錯誤。如果不注重錯誤信息的處理,就會造成信息泄露,程序無法運行等情況。

任何程序語言中,都需要錯誤處理。錯誤類型有:

  • 語法錯誤
  • 運行錯誤

語法錯誤

語法錯誤通常是由於對程序的組件(如運算符、表達式)使用不當引起的。一個簡單的實例如下:

-- test.lua 文件
a == 2

以上代碼執行結果爲:

lua: test.lua:2: syntax error near '=='

正如你所看到的,以上出現了語法錯誤,一個 "=" 號跟兩個 "=" 號是有區別的。一個 "=" 是賦值表達式兩個 "=" 是比較運算。

另外一個實例:

實例

for a= 1,10
   print(a)
end

執行以上程序會出現如下錯誤:

lua: test2.lua:2: 'do' expected near 'print'

語法錯誤比程序運行錯誤更簡單,運行錯誤無法定位具體錯誤,而語法錯誤我們可以很快的解決,如以上實例我們只要在for語句下添加 do 即可:

實例

for a= 1,10
do
   print(a)
end


運行錯誤

運行錯誤是程序可以正常執行,但是會輸出報錯信息。如下實例由於參數輸入錯誤,程序執行時報錯:

function add(a,b)
   return a+b
end

 
add(10)

當我們編譯運行以下代碼時,編譯是可以成功的,但在運行的時候會產生如下錯誤:

lua: test2.lua:2: attempt to perform arithmetic on local 'b' (a nil value)
stack traceback:
    test2.lua:2: in function 'add'
    test2.lua:5: in main chunk
    [C]: ?

以下報錯信息是由於程序缺少 b 參數引起的。


錯誤處理

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

實例

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

執行以上程序會出現如下錯誤:

lua: test.lua:3: b 不是一個數字
stack traceback:
    [C]: in function 'assert'
    test.lua:3: in local 'add'
    test.lua:6: in main chunk
    [C]: in ?

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

error函數

語法格式:

error (message [, level])

功能:終止正在執行的函數,並返回message的內容作爲錯誤信息(error函數永遠都不會返回)

通常情況下,error會附加一些錯誤位置的信息到message頭部。

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

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

pcall xpcalldebug

Lua中處理錯誤,可以使用函數pcallprotected call)來包裝需要執行的代碼。

pcall接收一個函數和要傳遞給後者的參數,並執行,執行結果:有錯誤、無錯誤;返回值true或者或false, errorinfo

語法格式如下

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

簡單實例:

實例

> =pcall(function(i) print(i) end, 33)
33
true
   
> =
pcall(function(i) print(i) error('error..') end, 33)
33
false        stdin:1: error..

<p這裏注意對返回值的邏輯判斷:< p="" style="color: rgb(51, 51, 51); font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans CJK SC", "WenQuanYi Micro Hei", Arial, sans-serif; font-size: 12px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;">

> function f() return false,2 end
> if f() then print '1' else print '0' end
0

pcall以一種"保護模式"來調用第一個參數,因此pcall可以捕獲函數執行中的任何錯誤。

通常在錯誤發生時,希望落得更多的調試信息,而不只是發生錯誤的位置。但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)
33
stack traceback:
stdin:1: in function <stdin:1>
[C]: in function 'error'
stdin:1: in function <stdin:1>
[C]: in function 'xpcall'
stdin:1: in main chunk
[C]: in ?
false        nil

xpcall 使用實例 2:

實例

function myfunction ()
   n = n/nil
end

function myerrorhandler( err )
   print( "ERROR:", err )
end

status = xpcall( myfunction, myerrorhandler )
print( status)

執行以上程序會出現如下錯誤:

ERROR:    test2.lua:2: attempt to perform arithmetic on global 'n' (a nil value)
false

</p這裏注意對返回值的邏輯判斷:<>

Lua 調試(Debug)

Lua 提供了 debug 庫用於提供創建我們自定義調試器的功能。Lua 本身並未有內置的調試器,但很多開發者共享了他們的 Lua 調試器代碼。

Lua debug 庫包含以下函數:

序號

方法 & 用途

1.

debug():

進入一個用戶交互模式,運行用戶輸入的每個字符串。 使用簡單的命令以及其它調試設置,用戶可以檢閱全局變量和局部變量, 改變變量的值,計算一些表達式,等等。 
輸入一行僅包含 cont 的字符串將結束這個函數, 這樣調用者就可以繼續向下運行。

2.

getfenv(object):

返回對象的環境變量。

3.

gethook(optional thread):

返回三個表示線程鉤子設置的值: 當前鉤子函數,當前鉤子掩碼,當前鉤子計數

4.

getinfo ([thread,] f [, what]):

返回關於一個函數信息的表。 你可以直接提供該函數, 也可以用一個數字 f 表示該函數。 數字 f 表示運行在指定線程的調用棧對應層次上的函數: 0 層表示當前函數(getinfo 自身); 1 層表示調用 getinfo 的函數 (除非是尾調用,這種情況不計入棧);等等。 如果 f 是一個比活動函數數量還大的數字, getinfo 返回 nil

5.

debug.getlocal ([thread,] f, local):

此函數返回在棧的 f 層處函數的索引爲 local 的局部變量 的名字和值。 這個函數不僅用於訪問顯式定義的局部變量,也包括形參、臨時變量等。

6.

getmetatable(value):

把給定索引指向的值的元表壓入堆棧。如果索引無效,或是這個值沒有元表,函數將返回 0 並且不會向棧上壓任何東西。

7.

getregistry():

返回註冊表表,這是一個預定義出來的表, 可以用來保存任何 C 代碼想保存的 Lua 值。

8.

getupvalue (f, up)

此函數返回函數 f 的第 up 個上值的名字和值。 如果該函數沒有那個上值,返回 nil  
'(' (開括號)打頭的變量名錶示沒有名字的變量 (去除了調試信息的代碼塊)。

10.

sethook ([thread,] hook, mask [, count]):

將一個函數作爲鉤子函數設入。 字符串 mask 以及數字 count 決定了鉤子將在何時調用。 掩碼是由下列字符組合成的字符串,每個字符有其含義:

  • 'c': 每當 Lua 調用一個函數時,調用鉤子;
  • 'r': 每當 Lua 從一個函數內返回時,調用鉤子;
  • 'l': 每當 Lua 進入新的一行時,調用鉤子。

11.

setlocal ([thread,] level, local, value):

這個函數將 value 賦給 棧上第 level 層函數的第 local 個局部變量。 如果沒有那個變量,函數返回 nil 如果 level 越界,拋出一個錯誤。

12.

setmetatable (value, table):

value 的元表設爲 table (可以是 nil)。 返回 value

13.

setupvalue (f, up, value):

這個函數將 value 設爲函數 f 的第 up 個上值。 如果函數沒有那個上值,返回 nil 否則,返回該上值的名字。

14.

traceback ([thread,] [message [, level]]):

如果 message 有,且不是字符串或 nil 函數不做任何處理直接返回 message 否則,它返回調用棧的棧回溯信息。 字符串可選項 message 被添加在棧回溯信息的開頭。 數字可選項 level 指明從棧的哪一層開始回溯 (默認爲 1 ,即調用 traceback 的那裏)。

上表列出了我們常用的調試函數,接下來我們可以看些簡單的例子:

實例

function myfunction ()
print(debug.traceback("Stack trace"))
print(debug.getinfo(1))
print("Stack trace end")
        return 10
end
myfunction ()
print(debug.getinfo(1))

執行以上代碼輸出結果爲:

Stack trace
stack traceback:
    test2.lua:2: in function 'myfunction'
    test2.lua:8: in main chunk
    [C]: ?
table: 0054C6C8
Stack trace end

在以實例中,我們使用到了 debug 庫的 traceback getinfo 函數, getinfo 函數用於返回函數信息的表。

另一個實例

我們經常需要調試函數的內的局部變量。我們可以使用 getupvalue 函數來設置這些局部變量。實例如下:

實例

function newCounter ()
  local n = 0
  local k = 0
  return function ()
    k = n
    n = n + 
1
    return n
    
end
end

counter = newCounter ()
print(counter())
print(counter())

local i = 1

repeat
  name, val = debug.getupvalue(counter, i)
  if name then
    print ("index", i, name, "=", val)
        if(name == "n") then
                debug.setupvalue (counter,2,10)
        end
    i = i + 1
  end -- if
until not name

print(counter())

執行以上代碼輸出結果爲:

1
2
index    1    k    =    1
index    2    n    =    2
11

在以上實例中,計數器在每次調用時都會自增1。實例中我們使用了 getupvalue 函數查看局部變量的當前狀態。我們可以設置局部變量爲新值。實例中,在設置前 n 的值爲 2,使用 setupvalue 函數將其設置爲 10。現在我們調用函數,執行後輸出爲 11 而不是 3


調試類型

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

命令行調試器有:RemDebugclidebuggerctracexdbLuaLuaInterface - DebuggerRldbModDebug

圖形界調試器有:SciTEDecodaZeroBrane Studioakdebuggerluaedit

Lua 垃圾回收

Lua 採用了自動內存管理。 這意味着你不用操心新創建的對象需要的內存如何分配出來, 也不用考慮在對象不再被使用後怎樣釋放它們所佔用的內存。

Lua 運行了一個垃圾收集器來收集所有死對象 (即在 Lua 中不可能再訪問到的對象)來完成自動內存管理的工作。 Lua 中所有用到的內存,如:字符串、表、用戶數據、函數、線程、 內部結構等,都服從自動管理。

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

垃圾收集器間歇率控制着收集器需要在開啓新的循環前要等待多久。 增大這個值會減少收集器的積極性。 當這個值比 100 小的時候,收集器在開啓新的循環前不會有等待。 設置這個值爲 200 就會讓收集器等到總內存使用量達到 之前的兩倍時纔開始新的循環。

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

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


垃圾回收器函數

Lua 提供了以下函數collectgarbage ([opt [, arg]])用來控制自動內存管理:

  • 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"): 停止垃圾收集器的運行。 在調用重啓前,收集器只會因顯式的調用運行。

以下演示了一個簡單的垃圾回收實例:

實例

mytable = {"apple", "orange", "banana"}

print(collectgarbage("count"))

mytable = nil

print(collectgarbage("count"))

print(collectgarbage("collect"))

print(collectgarbage("count"))

執行以上程序,輸出結果如下(注意內存使用的變化)

20.9560546875
20.9853515625
0
19.4111328125

Lua 面向對象

面向對象編程(Object Oriented ProgrammingOOP)是一種非常流行的計算機編程架構。

以下幾種編程語言都支持面向對象編程:

  • C++
  • Java
  • Objective-C
  • Smalltalk
  • C#
  • Ruby

面向對象特徵

  • 1 封裝:指能夠把一個實體的信息、功能、響應都裝入一個單獨的對象中的特性。
  • 2 繼承:繼承的方法允許在不改動原程序的基礎上對其進行擴充,這樣使得原功能得以保存,而新功能也得以擴展。這有利於減少重複編碼,提高軟件的開發效率。
  • 3 多態:同一操作作用於不同的對象,可以有不同的解釋,產生不同的執行結果。在運行時,可以通過指向基類的指針,來調用實現派生類中的方法。
  • 4)抽象:抽象(Abstraction)是簡化複雜的現實問題的途徑,它可以爲具體問題找到最恰當的類定義,並且可以在最恰當的繼承級別解釋問題。

Lua 中面向對象

我們知道,對象由屬性和方法組成。LUA中最基本的結構是table,所以需要用table來描述對象的屬性。

lua 中的 function 可以用來表示方法。那麼LUA中的類可以通過 table + function 模擬出來。

至於繼承,可以通過 metetable 模擬出來(不推薦用,只模擬最基本的對象大部分時間夠用了)。

Lua 中的表不僅在某種意義上是一種對象。像對象一樣,表也有狀態(成員變量);也有與對象的值獨立的本性,特別是擁有兩個不同值的對象(table)代表兩個不同的對象;一個對象在不同的時候也可以有不同的值,但他始終是一個對象;與對象類似,表的生命週期與其由什麼創建、在哪創建沒有關係。對象有他們的成員函數,表也有:

Account = {balance = 0}
function Account.withdraw (v)
    Account.balance = Account.balance - v
end

這個定義創建了一個新的函數,並且保存在Account對象的withdraw域內,下面我們可以這樣調用:

Account.withdraw(100.00)

一個簡單實例

以下簡單的類包含了三個屬性: area, length breadthprintArea方法用於打印計算結果:

實例

-- 元類
Rectangle = {area = 0, length = 0, breadth = 0}

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

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

創建對象

創建對象是爲類的實例分配內存的過程。每個類都有屬於自己的內存並共享公共數據。

r = Rectangle:new(nil,10,20)

訪問屬性

我們可以使用點號(.)來訪問類的屬性:

print(r.length)

訪問成員函數

我們可以使用冒號 : 來訪問類的成員函數:

r:printArea()

內存在對象初始化時分配。

完整實例

以下我們演示了 Lua 面向對象的完整實例:

實例

-- 元類
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()

執行以上程序,輸出結果爲:

面積爲     100

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

接下來的實例,Square 對象繼承了 Shape :

Square = Shape:new()
-- Derived class method new
function Square:new (o,side)
  o = o or Shape:new(o,side)
  setmetatable(o, self)
  self.__index = self
  
return o
end

完整實例

以下實例我們繼承了一個簡單的類,來擴展派生類的方法,派生類中保留了繼承類的成員變量和方法:

實例

-- 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()

執行以上代碼,輸出結果爲:

面積爲     100
正方形面積爲     100
矩形面積爲     200

函數重寫

Lua 中我們可以重寫基礎類的函數,在派生類中定義自己的實現方式:

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

Lua 數據庫訪問

本文主要爲大家介紹 Lua 數據庫的操作庫:LuaSQL。他是開源的,支持的數據庫有:ODBC, ADO, Oracle, MySQL, SQLite PostgreSQL

本文爲大家介紹MySQL的數據庫連接。

LuaSQL 可以使用 LuaRocks 來安裝可以根據需要安裝你需要的數據庫驅動。

LuaRocks 安裝方法:

wget http://luarocks.org/releases/luarocks-2.2.1.tar.gz
tar zxpf luarocks-2.2.1.tar.gz
cd luarocks-2.2.1
$ ./configure; 
sudo make bootstrap
sudo luarocks install luasocket
$ lua
Lua 5.3.0 Copyright 
(C) 1994-2015 Lua.org, PUC-Rio
> require 
"socket"

Window 下安裝 LuaRockshttps://github.com/keplerproject/luarocks/wiki/Installation-instructions-for-Windows

安裝不同數據庫驅動:

luarocks install luasql-sqlite3
luarocks 
install luasql-postgres
luarocks 
install luasql-mysql
luarocks 
install luasql-sqlite
luarocks 
install luasql-odbc

你也可以使用源碼安裝方式,Lua Github 源碼地址:https://github.com/keplerproject/luasql

Lua 連接MySql 數據庫:

實例

require "luasql.mysql"

--創建環境對象
env = luasql.mysql()

--連接數據庫
conn = env:connect("數據庫名","用戶名","密碼","IP地址",端口)

--設置數據庫的編碼格式
conn:execute"SET NAMES UTF8"

--執行數據庫操作
cur = conn:execute("select * from role")

row = cur:fetch({},"a")

--文件對象的創建
file = io.open("role.txt","w+");

while row do
    var = string.format("%d %s\n", row.id, row.name)

    print(var)

    file:write(var)

    row = cur:fetch(row,"a")
end


file:close()  --關閉文件對象
conn:close()  --關閉數據庫連接
env:close()   --關閉數據庫環境

5.2 版本之後,require 不再定義全局變量,需要保存其返回值。

require "luasql.mysql"

需要寫成:

luasql = require "luasql.mysql"

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章