《Programming in Lua》的第二部分,主要介紹Lua中的閉包,模式匹配,日期時間表示,數據結構實現,編譯執行和錯誤方式,以及模塊(Modules)和包(Packages)的概念。
Lua的函數function屬於first-class的值, 也就是function同Lua中的其他類型number、string、table等所處的地位一樣,function也可作為一個變量存儲在table中。
那麼如何理解function是為first-class呢?以下通過兩種函數申明方式來進行解釋
這兩種聲明方式都是申明一個foo的函數,實現2 * x的功能。
第2種申明可如此理解:右側函數定義作為一條申明語句,創建一個類型為function的值,並把它賦予foo這個變量。甚至,可以理解在Lua中所有的函數都是匿名的,我們平常用到的函數比如print,只不過是把print作為一個變量保存了對應的函數而已。
使用table.sort來舉例說明匿名函數的便利性,根據如下network中name這種欄位的字母逆序進行排序
函數聲明方式
對於局部函數,需要注意遞歸的情況,如下申明會出現函數未定義情況。
閉包
先看如下例子:
以上就是一個閉包,newCounter返回一個匿名函數,而這個匿名函數用到了newCounter中定義的local變量count,這個count就是所謂的上值(up-value),每次調用這個返回的匿名函數比如c1(),這個count貌似已經不在作用域內了,因為newCounter已經返回了,於是,Lua中採用閉包來處理和解決這種情況,每一次的調用,這個count的值會加一併保存。當創建一個新的匿名函數比如c2,c2的count跟c1擁有的count是完全分開獨立,互不幹擾的,也就是說c2產生了一個新的閉包。因此c1和c2是基於同一個匿名函數的不同閉包。
閉包讓Lua函數的功能變得更強大,以下是使用閉包的例子:
Lua模式匹配的實現代碼不到600行,沒有實現所有POSIX正則表達式的全部功能,但是Lua模式匹配功能強大,提供了很多feature是在標準的POSIX中很難做到的。
模式匹配相關函數
string.gsub, 這個接口之前已有介紹,不再贅述。
string.find
string.find (s, pattern [, init [, plain]])
尋找字符串s中pattern匹配的位置,匹配成功則返回兩個索引值(pattern的起始和終止位置),否則返回nil。
第三個可選參數init表明從哪裡開始搜索,其默認值是1
第四個可選參數plain表明是否為普通的字符串子串的搜索,不考慮模式匹配,如下:
string.match
string.match (s, pattern [, init])
跟string.find類似,但是其不是返回成功匹配的位置,而是返回成功匹配的內容,第三個默認參數init同樣為指定搜索起點。例子如下:
string.gmatch
string.gmatch(s, pattern)
返回一個迭代器函數,遍歷字符串中所有出現的pattern。
Patterns模式
Lua中使用%來作為轉義字符,常用字符含義如下
所有轉義字符的大寫,表示其補集,例如%A代表所有的非字母字符(%a為所有字符),如:
在Lua的模式中有一些字符具有特殊含義,被稱為Magic Characters, 如下:
Lua提供了4種修改器如下:
Exercise
請編寫一個函數split,該函數接受兩個參數,第一個參數是字符串,第二個參數是分隔符模式:
Lua中日期和時間有兩種表示方式:一種是整數形式,一種是table形式(日期table有以下這些域:year, month, day, hour, min, sec, way, yay, and isdst)。
os.time
os.date
其功能跟os.time相反,是將數字表示的時間和日期轉換成date table或者字符串等更加易讀的形式。
例如:
Date-Time操作
計算從此刻起40天以後那天的日期
Lua可以用table高效的實現數組,列表,隊列,集合等數據結構。
Arrays數組
Linked List列表
Queue隊列
Sets集合
將元素作為key放入table中,value置為true即可
String buffer字符串緩存
假定我們需要從文件中一行行讀取數據,可以選用以下方法:
這種方式效率極低,前面我們也提高過..進行字符串拼接是很低效的(一次拼接就是一個內存分配和字符移動)。因此可以選用更為高效的方式table.concat
Compilation
loadfile從文件中加載Lua代碼,只編譯代碼並將編譯結果作為函數返回,dofile是基於loadfile,會執行函數,可定義如下:
dofile更加完整,但是loadfile更加靈活,可只編譯一次,運行多次,而dofile每次運行都需要編譯,開銷大。
load跟loadfile功能類似,但是load不是從文件中讀取Lua代碼,而是從字符串或者函數中讀取,如:
在加載定義了函數的代碼塊時,有一個坑需要注意,lua中定義是需要通過賦值的:
當使用loadfile加載上述兩個文件時,加載foo1.lua後foo函數是沒有被定義,需要執行之後才可使用,而foo2.lua被加載後foo可直接使用,如下:
Errors
處理錯誤的方式:
可以使用pcall在lua代碼內部處理錯誤,不過使用pcall是看不到traceback的,因為其在返回錯誤代碼時就已經銷毀了調用棧。
模塊:是能夠通過函數require加載,然後返回一個table,任何模塊導出的函數和常量,都定義在table中,這個table可以看作是一個命名空間。
Packages:由許多模塊組成。
模塊在lua中不是first-class,其不能作為參數傳遞給函數。
require加載模塊時,會從package.path路徑中去搜索,如果找到了對應文件,就通過loadfile進行加載。如果package.path中不能找對應的lua文件,就會從c庫中去搜索,即package.cpath,如果在c庫中找到了,就通過package.loadlib去找luaopen_modname的函數,其中modname為模塊名稱,luaopen_modname表示一個lua函數。
路徑搜索
require搜索路徑由一系列模版組成,模版之間用;隔開,如下;
如果調用require sql,直接用sql替換上述路徑中的?,那麼其搜索順序如下:
以下為實現一個類似package.searchpath的例子
首先要把modname中的.換成/,然後將modname替換path中的?。
Lua中實現模塊的方法
定義一個table,將所有函數放入table中,並返回table即可:
主要介紹Lua中閉包概念以及其用途,Lua的模式匹配,日期時間的兩種表現形式以及相關轉換接口,如何使用table實現其他數據結構如數組,列表,隊列,集合等,實現,編譯執行和錯誤方式,最後介紹了模塊(Modules)和包(Packages)的概念。
參考:
http://www.lua.org/pil/
http://www.lua.org/manual/5.3/manual.html#lua_call
陌生的人,請給我一支蘭州