【Lua 】基礎知識梳理

Lua 的目的是爲了嵌入應用程序中,從而爲應用程序提供靈活的擴展和定製功能。

特點是輕量級、可擴展、面向過程編程和函數式編程、自動內存管理、語言內置模式匹配、閉包(closure)、函數也可以看做一個值、提供多線程(協同進程,並非操作系統所支持的線程)支持、通過閉包和 table 可以支持面向對象編程:數據抽象,虛函數,繼承和重載。

print("Hello World!")

兩個減號是單行註釋: –
多行註釋:

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

Lua 是一個區分大小寫的編程語言。

Lua 的保留關鍵字有:

and break   do  else
elseif  end false   for
function    if  in  local
nil not or  repeat
return  then    true    until
while

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

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

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

b = nil
print(b)      --> nil

當且僅當一個變量不等於 nil 時,這個變量即存在。

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

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

nil、boolean、number、string、
userdata (表示任意存儲在變量中的 C 數據結構)、
function (由 C 或 Lua 編寫的函數)、
thread (表示執行的獨立線路,用於執行協同程序) 、
table (是一個 "關聯數組" associative arrays,數組的索引可以是數字或者是字符串)。

Lua 默認只有一種 number 類型 即 double(雙精度)類型。

字符串由一對雙引號或單引號來表示。
也可以用 2 個方括號 “[[]]” 來表示”一塊”字符串。

html = [[
<html>
<head></head>
<body>

</body>
</html>
]]

print(html)

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

> print("2" + 6)
8.0
> print("2" + "6")
8.0
> print("2 + 6")
2 + 6

字符串連接使用的是 ..

> print("a" .. 'b')
ab

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

> len = "www.w3cschool.cc"
> print(#len)
16

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

-- 創建一個空的 table
local tbl1 = {}

-- 直接初始表
local tbl2 = {"apple", "pear", "orange", "grape"}

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

a["key"] = "value"
a[10] = 100

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

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


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


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


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


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

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

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

遇到賦值語句 Lua 會先計算右邊所有的值然後再執行賦值操作,所以我們可以這樣進行交換變量的值:x, y = y, x -- swap 'x' for 'y'
變量個數 > 值的個數 按變量個數補足 nil;
變量個數 < 值的個數 多餘的值會被忽略;


循環

while( true )
do
   print("循環將永遠執行下去")
end
for var = exp1,exp2,exp3 do  
    <執行體>  
end

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

repeat
   statements
until( condition )

Lua 認爲 false 和 nil 爲假,true 和非 nil 爲真
Lua 中 0 爲 true

if(0) then
    print("0 爲 true")
end

邏輯運算符

and 邏輯與操作符。 若 Afalse,則返回 A,否則返回 B。
or  邏輯或操作符。 若 Atrue,則返回 A,否則返回 B。
not 邏輯非操作符。與邏輯運算結果相反,如果條件爲 true,邏輯非爲 false

.. 連接兩個字符串。
# 一元運算符,返回字符串或表的長度


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

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

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

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

string.gsub(mainString,findString,replaceString,num)
在字符串中替換, mainString 爲要替換的字符串, findString 爲被替換的字符,replaceString 要替換的字符,num 替換次數(可以忽略,則全部替換)

string.strfind (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 個拷貝

—- 並不華麗的分割線 —-

數組,就是相同數據類型的元素按一定順序排列的集合,可以是一維數組和多維數組。
在 Lua 索引值是以 1 爲起始,但你也可以指定 0 開始。
除此外我們還可以以負數爲數組索引值。

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

迭代器是用於遍歷集合或容器中元素的一種結構。在 Lua 語言中,集合往往指的是可以用來創建各種數據結構的表。比如,數組就是用表來創建的。
通用迭代器可以訪問集合中的鍵值對,Lua 提供默認迭代器函數 ipairs。

無狀態迭代器,這一類的迭代器函數中不會保存任何中間狀態

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

function squares(iteratorMaxCount)
   return square, iteratorMaxCount, 0
end  

for i,n in squares(3)
do
    print(i,n)
end

有狀態迭代器,在 Lua 中可以使用閉包來存儲當前元素的狀態。閉包通過函數調用得到變量的值。爲了創建一個新的閉包,我們需創建兩個函數,包括閉包函數本身和一個工廠函數,其中工廠函數用於創建閉包。


在 Lua 語言中,表是唯一可以用來創建不同數據類型的數據結構,比如常見的數組和字典都是用表來創建的。表沒有固定的大小,當數據量增加時表會自動增大。表被稱之爲對象,它既不是值也不是變量。Lua 用構造表達式 {} 創建一個空表。需要注意的是,在存儲表的變量和表本身之間沒有什麼固定的對應關係(注:一個表可以被不同的變量引用,一個變量也可以隨時改變其所引用的表對象)。

--簡單的表初始化
mytable = {}

--簡單的表賦值
mytable[1]= "Lua"

--移除引用
mytable = nil
-- lua 的垃圾回收機制負責回收內存空間

當我們有一個擁有一系列元素的表時,如果我們將其賦值給 b。那麼 a 和 b 都會引用同一個表對象(a 先引用該表),指向相同的內存空間。而不會再單獨爲 b 分配內存空間。即使給變量 a 賦值 nil,我們仍然可以用變量 b 訪問表本身。如果已經沒有變量引用表時,Lua 語言垃圾回收機制負責回收不再使用的內存以被重複使用。

下面的示例代碼使用到了上面提到的表的特性。  

Lua 語言內置的表操作函數:

table.concat(table[, sep [, i[,j]]]): 根據指定的參數合併表中的字符串。

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

-- 返回表中字符串連接後的結果 bananaorangeapple
print("Concatenated string ",table.concat(fruits))

--用字符串連接 banana, orange, apple
print("Concatenated string ",table.concat(fruits,", "))

--基於索引連接 orange, apple
print("Concatenated string ",table.concat(fruits,", ", 2,3))

table.insert(table,[pos,]value):在表中指定位置插入一個值。
table.maxn(table):返回表中最大的數值索引。
table.remove(table[,pos]):從表中移出指定的值。
table.sort(table[,comp]):根據指定的(可選)比較方法對錶進行排序操作。

sort 函數默認使用字母表對錶中的元素進行排序(可以通過提供比較函數改變排序策略)。


Lua 中的模塊與庫的概念相似,每個模塊都有一個全局唯一名字,並且每個模塊都包含一個表。使用一個模塊時,可以使用 require 加載模塊。模塊中可以包括函數和變量,所有這些函數和變量被表存儲於模塊的表中。模塊中的表的功能類似於命名空間,用於隔離不同模塊中的相同的變量名。在使用模塊的時候,我們應該遵守模塊定義的規範,在 require 加載模塊時返回模塊中的包含函數和變量的表對象。

模塊中表的使用使得我們可在絕大多數情況下可以像操作其它表一樣操作模塊。由於 Lua 語言允許對模塊本身進行操作,所以 Lua 也就具備了許多其它語言需要特殊機制才能實現的特殊性質。例如,這種自由的表操作機制使得編程人員可以用多種方法調用模塊中的函數。下面的例子演示了其中的一些方法:

-- 方法 1
require "printFormatter"
printFormatter.simpleFormat("test")

-- 方法 2
local formatter = require "printFormatter"
formatter.simpleFormat("test")

-- 方法 3
require "printFormatter"
local formatterFunction = printFormatter.simpleFormat
formatterFunction("test")

Lua 提供了一個高層次抽象的函數 require,使用這個函數可以加載所有需要的模塊。

local mymath =  {}
function mymath.add(a,b)
   print(a+b)
end

function mymath.sub(a,b)
   print(a-b)
end

function mymath.mul(a,b)
   print(a*b)
end

function mymath.div(a,b)
   print(a/b)
end

return mymath

在另一個文件 moduletutorial.lua 文件中訪問這個模塊。

mymathmodule = require("mymath")
mymathmodule.add(10,20)
mymathmodule.sub(30,20)
mymathmodule.mul(10,20)
mymathmodule.div(30,20)

注意的幾點:
把模塊和待運行的文件放在相同的目錄下;
模塊的名稱與文件名稱相同;
爲 require 函數返回模塊(在模塊中使用 return 命令返回存儲了函數和變量的表)。儘管有其它的模塊實現的方式,但是建議您使用上面的實現方法;

 

可以通過設置元表的鍵和相關方法來改變表的行爲。元方法的功能十分強大,使用元方法可以實現很多的功能:修改表的操作符功能或爲操作符添加新功能(實現操作的重載)、使用元表中的 __index 方法,我們可以實現在表中查找鍵不存在時轉而在元表中查找鍵值的功能;

Lua 提供了兩個十分重要的用來處理元表的方法:setmetatable(table,metatable):此方法用於爲一個表設置元表; getmetatable(table):此方法用於獲取表的元表對象。

-- 將一個表設置爲另一個表的元表
mytable = {}
mymetatable = {}
setmetatable(mytable, mytatable)

簡寫成一行代碼:mytable = setmetatable({}, {})


協程具有協同的性質,它允許兩個或多個方法以某種可控的方式協同工作。在任何一個時刻,都只有一個協程在運行,只有當正在運行的協程主動掛起時它的執行纔會被掛起(暫停)。當我們使用 resume 函數調用一個協程時,協程纔開始執行。當在協程調用 yield 函數時,協程掛起執行。再次調用 resume 函數時,協程再從上次掛起的地方繼續執行。這個過程一直持續到協程執行結束爲止。

協程與線程的區別在於協程不能併發,任意時刻只會有一個協程執行,而線程允許併發的存在。

Lua 爲支持協程提供的函數和功能:

coroutine.create(f): 用函數 f 創建一個協程,返回 thread 類型對象。
coroutine.resume(co[,val1,…]): 傳入參數(可選),重新執行協程 co。此函數返回執行狀態,也可以返回其它值。
coroutine.running(): 返回正在運行的協程,如果在主線程中調用此函數則返回 nil。
coroutine.status(co): 返回指定協程的狀態,狀態值允許爲:正在運行(running),正常(normal),掛起(suspended),結束(dead)。
coroutine.wrap(f): 與前面 coroutine.create 一樣,coroutine.wrap 函數也創建一個協程,與前者返回協程本身不同,後者返回一個函數。當調用該函數時,重新執行協程。
coroutine.yield(…): 掛起正在執行的協程。爲此函數傳入的參數值作爲執行協程函數 resume 的額外返回值(默認會返回協程執行狀態)。


Lua 的文件 IO 庫用於讀取或操作文件。Lua IO 庫提供兩類文件操作,它們分別是隱式文件描述符(implict file descriptors)和顯式文件描述符 (explicit file descriptors)。

簡單的打開文件操作:file = io.open (filename [, mode])

隱式文件描述符使用標準輸入輸出模式或者使用單個輸入文件和輸出文件。使用隱匿文件描述符的示例代碼如下:

-- 只讀模式打開文件
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)

-- 將內容追加到文件最後一行
io.write("-- End of the test.lua file")

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

我們也會經常用到顯示文件描述符,因爲它允許我們同時操作多個文件。這些函數與隱式文件描述符非常相似,只不過我們在這兒使用 file:function_name 而不是使用 io.function_name 而已。

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

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

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

-- 以追加模式打開文件
file = io.open("test.lua", "a")

-- 添加內容到文件的尾行
file:write("--test")

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

爲什麼需要錯誤處理機制,在真實的系統中程序往往非常複雜,它們經常涉及到文件操作、數據庫事務操作或網絡服務調用等,這個時候錯誤處理就顯得非常重要。不關注錯誤處理可能在處理諸如涉密或金融交易這些業務時造成重大的損失。
無論什麼時候,程序開發都要求小心地做好錯誤處理工作。在 Lua 中錯誤可以被分爲兩類:語法錯誤、運行時錯誤;

對於運行時錯誤,雖然程序也能成功運行,但是程序運行過程中可能因爲錯誤的輸入或者錯誤的使用函數而導致運行過程中產生錯誤。

我們經常用到 assert 和 error 兩個函數處理錯誤。下面是一個簡單的例子。

local function add(a,b)
   assert(type(a) == "number", "a is not a number")
   assert(type(b) == "number", "b is not a number")
   return a+b
end
add(10)

使用 pcall(f,arg1,…) 函數可以使用保護模式調用一個函數。如果函數 f 中發生了錯誤, 它並不會拋出一個錯誤,而是返回錯誤的狀態。

function myfunction ()
   n = n/nil
end

if pcall(myfunction) then
   print("Success")
else
    print("Failure")
end

xpcall(f,err) 函數調用函數 f 同時爲其設置了錯誤處理方法 err,並返回調用函數的狀態。任何發生在函數 f 中的錯誤都不會傳播,而是由 xpcall 函數捕獲錯誤並調用錯誤處理函數 err,傳入的參數即是錯誤對象本身。xpcall 的使用示例如下:

function myfunction ()
   n = n / nil
end

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

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

Lua 調試庫提供了創建自己的調試器所需要的所有原語函數,Lua 沒有內置調試器,但是有不少開源調試器。http://wiki.jikexueyuan.com/project/lua/debugging.html


Lua 通過特定算法的垃圾回收機制實現自動內存管理。由於自動內存管理機制的存在,作爲程序開發人員:

不需要關心對象的內存分配問題。
不再使用對象時,除了將引用它的變量設爲 nil,不需要主動釋放對象。
Lua 的垃圾回收器會不斷運行去收集不再被 Lua 程序訪問的對象。

http://wiki.jikexueyuan.com/project/lua/garbage-collection.html


在遊戲開發領域,Lua 語言因其結構和語法的簡潔性而在各類遊戲引擎中被廣泛使用。遊戲對圖形畫面要求非常苛刻,這無疑需消耗大量的內存空間,而這些內存空間的管理是非常棘手的問題。Lua 語言有自動的垃圾回收機制,這種自動化的內存管理機制也使得 Lua 受到遊戲引擎開發者的青睞。


Lua 標準庫利用 C 語言 API 實現並提供了豐富的函數,它們內置於 Lua 語言中。該標準庫不僅可以提供 Lua 語言內服務,還能提供外部服務,比如文件或數據庫的操作。

這些標準庫使用標準的 C API 接口實現,它們作爲獨立的 C 語言模塊提供給使用者。主要包括以下的內容:

基本庫,包括協程子庫
模塊庫
字符串操作
表操作
數學計算庫
文件輸入與輸出
操作系統工具庫
調試工具庫

End.

發佈了104 篇原創文章 · 獲贊 63 · 訪問量 27萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章