lua5.3程序設計精粹

1.在註釋代碼時我們可以使用如下方式:

--[[
代碼段
--]]

這樣當要還原註釋塊代碼時只需要在第一行前面加上一個-就可以將第一行變成單行註釋,而最後一行的–]]本身就是單行註釋。如下所示:

---[[
代碼段
--]]

2.lua中條件語句將除Boolean值false和nil外的所有其他值視爲真,否則視爲假。常見的邏輯運算中,and和or都遵循短路求值原則,即只在必要時纔對第二個操作數進行求值。如下所示:
and的運算結果爲:如果第一個操作數的結果爲條件false,則返回第一個操作數,否則返回第二個操作數。
or運算結果爲:如果第一個操作數的結果爲條件true,則返回第一個操作數,否則返回第二個操作數。
not運算結果爲:永遠返回Boolean類型值。

3.具有十進制的小數或者指數的數值會被當做浮點型值,否則會被當做整形值。常見要點如下:
1>.不論浮點型還是整形值,使用type函數獲取的類型都是number。
2>.浮點值使用math.type函數獲取的值爲float,整形值使用math.type獲取的值爲integer。
3>.浮點值得精度爲雙精度浮點值,整型值精度爲64位長度整形值。如果想要縮短number類型長度,可以使用LUA_32BITS宏來限定浮點值爲單精度浮點值,整形值爲32位長度整形值。
4>.使用string.format("%a", 十進制數)來獲取十六進制數。其中十六進制浮點數(小數部分和以p開頭的指數部分組成)可以保留所有的浮點數精度,並且比十進制轉換的速度更快。
5>.算數運算中,除了除法得到的結果永遠爲浮點值外,其他所有的運算操作均滿足:操作數都是整形時,得到的運算結果爲整形數值,否則得到的運算結果爲浮點數值。
6>.十進制的有限小數在用二進制表示時可能是無限小數,從而造成運算不對。如:12.7-20+7.3的結果就不爲0,原因就是12.7和7.3用二進制表示小數位是無限的。
7>.對整形數值進行越界處理時會發生迴環(最大值變最小值,最小值變最大值);對浮點值進行越界處理時會得到一個近似值。
8>.浮點數值轉換成整形數值時,必須滿足整形數值的形式和長度要求。通常有以下方式:
.使用異或0(|0)來對浮點數值進行轉換處理,轉換失敗會拋出異常。
.使用math.tointeger函數來對浮點數值進行轉換處理,轉換失敗會返回nil。

4.字符串具有以下特性:
1>.單引號括起來的字符串中出現雙引號可以不用轉義,雙引號括起來的字符串中出現單引號也可以不用轉義。否則就都需要進行轉義處理。
2>.可以使用[描述符[字符串內容]描述符]來表達長字符串或者多行字符串。其中描述符前後必須保持一致,且可以爲空串。
3>.\z可以將後面的所有空格字符給過濾掉,直到遇到不爲空格字符爲止。
4>.數值字符串進行算數運算時得到的結果爲浮點型數值;數值進行字符串操作時得到的結果爲字符串。
5>.字符串是不可修改的,對字符串進行的所有操作而得到的字符串都是新建的。

5.表具有以下特性:
1>.當被用作表索引時,任何能夠被轉換爲整形的浮點數都會被轉換成整形數。如a[2.0] = 10等價於a[2] = 10。
2>.表在使用pairs遍歷時是無序的,但是每個條目只可能被遍歷一次。且表中key不能爲nil,但是值可以爲nil。當值爲nil時表示不存在或者刪除這個條目,也不會計數到表的長度中。
3>.對錶成員連續訪問時,爲了降低訪問次數,可以使用安全訪問的方式進行操作。如下所示:

-- 在一次成功的訪問中對錶進行了6次訪問,也就是例子中的6個訪問符號"."
zip = company and company.director and company.director.address and company.director.address.zipcode
-- 基於類似安全訪問的形式,一次成功訪問對錶進行3次訪問,也就是例子中的3個訪問符號"."
E = {}
zip = (((company or E).director or E).address or E).zipcode

4>.table中的move函數可以用來對錶中指定區間數據移動到表中指定位置後面,也可以將表中指定區間數據移動到另一個表中指定位置後面。這些移動操作都是一個拷貝參數。移動的表數據長度並沒有發生改變,也就是說移動後不要的位置數據可以自己置空處理。

6.函數具有以下特性:
1>.函數參數只有一個且爲字符串或者表構造器時,可以不用寫()。
2>.當函數內部返回多個值時,只有當函數調用是一系列表達式的最後(或者唯一)一個表達式時才能返回多個結果,否則只能返回一個結果,其他結果會被丟棄掉。
3>.對返回多個結果的函數使用()包起來可以強制其只返回一個結果。
4>.table.pack函數用來將參數列表以及參數個數保存在一個表中進行返回,其中參數個數用變量n存儲;table.unpack函數用來交參數表按照從頭到尾輸出參數,直到遇到第一個nil或者遍歷完參數表爲止,也可以指定輸出參數表中指定區間範圍的參數。
5>.select函數用來將參數列表按照指定selector方式輸出。當selector爲“#”時,此時會輸出參數個數;當selector爲索引值時,此時會輸出索引值後(包含該索引位置)的所有參數。
6>.尾調用是指在函數的最後一行調用其他函數後立馬結束函數調用,也就是格式爲:"return func(args)"的形式。其中尾調用函數不會佔用棧空間大小。

7.輸入和輸出具有以下特性:
1>.io.input函數參數爲空時表示獲取當前輸入流,參數爲指定輸入流時表示獲取該指定輸入流。
2>.io.output函數參數爲空時表示獲取當前輸出流,參數爲指定輸出流時表示獲取該指定輸出流。
3>.io.read函數表示從當前輸入流中進行讀取數據。其中可以讀取指定字節大小的數據,也可以讀取所有內容或者一行內容等。
4>.io.write函數表示從當前輸出流中進行寫入數據。
5>.io.lines函數用來從當前輸入流中讀取所有行數據或者指定字節大小數據。
6>.io.open函數表示以只讀或者只寫或者二進制等形式來操作指定文件,並獲取該文件句柄。通過該文件句柄調用read來讀取文件內容,調用write來寫入文件內容,調用seek來返回當前新位置在流中相對於文件開頭的偏移,調用close來關閉該文件句柄。
7>.io.flush函數將所有緩衝區數據寫入文件。
8>.io.popen函數用來執行執行系統命令,並重定向命令的輸入輸出。
9>.setvbuf函數用來設置緩衝區模式。當爲no時表示無緩衝區;當爲full時表示緩衝區滿時才寫入數據;當爲line時表示當遇到換行時才寫入數據;
10>.os.rename函數用於文件的重命名。os.remove函數用來刪除文件。os.exit函數用於終止程序執行,參數可以爲0或者true表示執行成功,也可以爲非0或者false表示執行失敗。os.getenv函數用來獲取指定環境變量的配置信息。os.execute函數用來執行系統命令,且第一個返回值表示程序是否成功完成,第二個返回值表示程序完成狀態,第三個返回值表示信號代碼。

8.局部變量具有以下特性:
1>.局部變量只有在所在代碼塊中有效,且不能超過200個。由於交互模式(終端命令行,使用lua -i進入)中每一行語句就是一個單獨的代碼塊,所以不能直接在交互模式下訪問局部變量,此時可以使用"do 局部變量定義以及訪問等操作 end"的模式來使用,因爲解析器發現do後,直到出現end纔會認爲代碼塊結束。
2>.局部變量訪問速度比全局變量快。
3>.局部變量使用完畢後可以被垃圾回收器進行回收。
4>.局部變量可以避免由於不必要的命名而造成全局變量的混亂。也可以避免同一程序中不同代碼塊中命令衝突。
5>.局部變量在使用時才進行定義和初始化,這樣可以提高可讀性。
6>.使用repeat until循環語句時,repeat代碼塊中定義的局部變量在until中也可以使用。

9.跳轉命令具有以下特性:
1>.return只能是代碼塊的最後一句或者end,else和until之前的最後一句。否則會拋出系統異常。
2>.goto語句的格式爲"goto 標籤名字 “::標籤名字:: 其他代碼”",其中goto語句不能跳轉到代碼塊內;不能跳轉轉到函數外;不能跳轉到局部變量作用域處;可以使用goto來模擬continue和redo,狀態機等操作。

10.函數具有以下特性:
1>函數都是匿名的,所謂的函數名實際上只是包含匿名函數的變量名而已。如下所示:

-- 全局函數定義:
function foo()  end  <=>  foo = function () end
--局部函數定義
local function goo() end <=> local goo; goo = function () end

2>.局部間接遞歸函數在定義時,最後一個函數不能加上local關鍵字,否則就會出現最後局部變量函數的前向聲明變成未知狀態,從而在使用時就報錯。如下所示:

-- 最後一個局部函數的前向聲明
local f

-- 局部函數g的定義
local function g()
	-- 調用局部函數f
	f()
end

-- 局部函數f的定義,此處不能加上local,否則上面的local f就會變成未知狀態,函數g中訪問f就會出問題
function f()
	-- 調用局部函數g
	g()
end

3>.函數是第一類值,也就是說函數可以作爲其他函數的參數,也可以作爲其他函數的返回值,還可以用來存儲在表中或者全局和局部變量中。
4>.當函數B中調用函數A時,此時函數A可以訪問函數B中所有的局部變量。而函數B中的局部變量在函數A中既不是局部變量也不是全局變量而是上值upvalue。這個上值只有在函數A調用完畢後纔會回收掉。這種函數B調用函數A的情形就叫做閉包。

11.模式匹配具有以下特性:
1>.string.find函數具有四個參數。第一個參數表示原始字符串;第二個參數表示匹配模式;第三個參數表示起始查找位置;第四個參數表示是否使用簡單搜索(第二個參數在第一個參數中只進行單純的查找子字符串操作)。當查找成功時就會返回起始和終止位置,否則返回nil。
2>.string.match函數具有三個參數。第一個參數表示原始字符串;第二個參數表示匹配模式;第三個參數表示起始查找位置;當查找成功時就會返回查找到的字符串,否則返回nil。
3>.string.gsub函數具有四個參數。第一個參數表示原始字符串;第二個參數表示匹配模式;第三個參數表示替換字符串(也可以是表或者函數。其中表是以查找到的字符串作爲key來獲取替換字符串;而函數是將查找的字符串作爲參數並返回替換字符串。當獲取的替換字符串爲nil時就不改變這個查找的字符串);第四個參數表示替換次數。當替換成功時返回替換後的字符串以及替換次數,否則返回原始字符串。
4>.string.gmatch函數具有兩個參數。第一個參數表示原始字符串;第二個參數表示匹配模式。該函數會返回一個函數,然後返回的函數會遍歷原始字符串中所有出現匹配模式的字符串。
5>.字符( ) . % + - * ? [ ] ^ $表示魔法字符。用法如下:
():表示從目標字符串中捕獲滿足()中指定的模式匹配的內容用於後續用途。可以使用%n且n爲數字來表示獲取第n個捕獲結果的副本,當n爲0時表示整個匹配。如果()中沒有指定任何模式匹配的話,就表示獲取()後面字符在目標字符串中的位置,當字符在()前面時就表示字符在目標字符串中的位置處的下一個位置。
.:表示任意字符。
%:表示轉義字符。當%後面跟的是大寫形式類就表示是小寫形式類的補集。如:%a表示匹配所有字母字符,%A表示匹配任意非字母字符。同時這些魔法字符也可以使用%進行轉義。如:%%就是匹配一個%;%?就是匹配一個?
+:表示重複一次或者多次。
-:表示區間範圍。如:[0-7]表示字符0開始到字符7終止,可以表示匹配8進制數。也可以表示重複0次或者多次,且按照最短滿足字符進行匹配。
*:表示重複0次或者多次。按照最長滿足字符進行匹配。
?:表示可選(出現0次或一次)。
[ ] :表示自定義字符分類。通常將單個字符和字符分類組合起來使用。如:[%w_]表示匹配所有以_結尾的字母或者數字。
^:表示對字符集取補集。如:[^0-7]表示匹配所有八進制以外的字符。當放在模式的開頭時表示從頭開始匹配。
$:模式以$結尾表示匹配到結尾。
6>.模式%bxy表示匹配x作爲起始字符,y作爲結束字符的子串。
7>.URL編碼就是將鍵值對中的鍵和值分別進行編碼。核心就是對字符串中的& = + 轉換成十六進制數,將空格替換成+的過程。
8>.utf8的模式匹配通過設置utf8.charpattern來定義。
9>.模式匹配中的一些注意事項:
1>>.儘可能少的使用gsub函數,因爲性能不高。
2>>.儘可能使用精確的匹配模式,這樣得到的結果比較精確,否則結果在某些特殊情況下未知。
3>>.可以將可能出現歧義的內容編碼成別的內容(如:十六進制值),然後再進行模式匹配來獲取包含歧義內容的匹配字符串。最後將歧義字符串進行還原成源字符串形式即可。

12.日期和時間具有以下特性:
1>.日期表的格式爲:{year(年),month(月),day(日),hour(時),min(分),sec(秒),wday(周幾),yday(一年中的第幾天),isdst(是否夏時令)}。其中UTC日期表爲{year=1970, month=1, day=1, hour=0, min=0, sec=0, wday=5, yday=1, isdst=false}。
2>.os.time函數就是用指定參數(日期表,爲nil時就取本機日期表。日期表中的year,month,day是必須要指定的,而hour,min,sec是可選的,且hour默認值爲12,min和sec默認值爲0)減去UTC日期表再減去本機時區對應的秒值,從而得到一個時間戳。
3>.os.date函數特性如下:
1>>.第二個參數爲時間戳。當參數爲nil時就取本機當前時間戳,也就是os.time的值。
2>>.第一個參數表示格式化字符串。當參數爲nil時取%c值;當參數包含!時函數得到的日期表等於UTC日期表加上第二個時間戳參數對應的日期表,否則得到的日期表等於UTC日期表加上第二個時間戳參數對應的日期表再加上本機時區對應的秒值;當參數爲*t時函數返回日期表,否則就返回參數被日期表替換後的字符串結果;
3>.os.difftime函數用來返回第一個參數對應的時間戳減去第二個參數對應的時間戳的差值。

13.位和字節具有以下特性:
1>.位運算只能針對整形數值,且是整形類型的所有位(標準整形是64位,精簡整形是32位)進行操作。其中常見的位操作符如下:
&:按位與操作。
|:按位或操作。
~:按位異或或者一元取反操作。
>>:邏輯右移操作。
<<:邏輯左移操作。
2>.整形數除以2的冪得到的結果等價於整形數向右移動冪次位。如:1234 / (2^4) == 1234 >> 4。
3>.整形數向右移動n位的結果等價於整形數向左移動-n位。如:1234 >> 4 == 1234 << -4。
4>.整形數的位數小於移動位數時,結果等於0。如:1234 >> 80 == 0。
5>.64位整形數中只想操作32位整形數的話,可以對64位整形數向右移動32位來忽略高32位,從而達到操作32位整數的效果。
6>.整形數默認都是有符號整形數。如果想要得到無符號整形數的話,可以使用%u或者%x來轉換成字符串的形式進行顯示;如果想要以無符號整形數的方式進行比較話,可以使用math.ult函數來比較第一個參數是否小於等於第二個參數,當然也可以使用符號位的掩碼0x8000000000000000來對整數的符號位進行過濾後再進行比較。
7>.有符號數和無符號數的相互轉換過程如下:u表示無符號整數,f表示有符號整數。
1>>.無符號轉換成有符號:f=(u+0.0)%2^64。
2>>.有符號轉換成無符號:u=math.tointeger(((f+2^63)%2^64)-2^63)。
8>.string.pack函數的第一個參數是格式化字符串;第二個參數是打包數據;返回值是將打包數據按照指定格式化字符串處理後的二進制字符串。
9>.string.unpack函數的第一個參數是格式化字符串;第二個參數是二進制字符串;返回結果是將二進制字符串按照指定格式化字符串處理後的打包數據和該打包數據的後一位索引值。
10>.打包和解包二進制數據中常用的格式化字符串如下所示:
1>>.針對整數而言,小寫格式爲有符號整數,大寫形式表示無符號整數。如:i表示有符號整數,I表示無符號整數。後面都可以指定字節數(i7表示7個字節的有符號整數,I7表示7個字節的無符號整數)。
2>>.z表示以\0結尾的字符串。
3>>.cn表示定長字符串。其中n應該等於打包字符串的長度。
4>>.sn表示指定字節的字符串。當n爲nil時,也就是隻有s時就表示指定size_t(一般爲8個字節的無符號整形數)個字節的字符串。
5>>.f表示單精度浮點數;d表示雙精度浮點數;n表示lua的浮點數。
6>>.=表示機器原生的大小端模式;>表示大端模式;<表示小端模式。
7>>.!n表示當數據比n小的話,就對齊到自身大小上;否則就對齊到n上。當n爲nil時,也就是爲!時,則對齊方式爲機器默認的對齊方式。其中對齊大小隻能是2的整數次冪,否則就會拋出異常。

14.儘可能使用table.concat函數來做字符串的拼接操作,而不要用“…”來做。理由如下:
1>>.table.concat類似於java中的StringBuilder字符串緩衝區。它會將第一個參數表示的表中的每一個字符串元素進行拼接,字符串元素之間用第二個參數表示的分割符進行分割,從而得到一個新的字符串。
2>>."…“操作符會將左右兩邊的字符串進行拼接,從而得到一個新的字符串。
3>>.由於字符串是不可變類型,所以每一個拼接產生的字符串都是會額外分配內存的新字符串。當參與拼接的字符串比較多時,table.concat相對於”…“而言,由於產生的臨時字符串會更少,所以佔用的內存會更小,拼接時移動的內存也更小,速度也更快。根據我寫的測試用例來看:在執行100000次字符串拼接操作中,”…“的時間消耗是table.concat的66倍多;”…"的內存消耗是table.concat的1.7倍多。

15.lua通常適合用來作爲數據文件。因爲相較於其他編程語言以及xml,json,csv等,lua運行速度更快,佔用內存更低。使用時需要注意一下幾點:
1>.將數據文件以lua的表結構來進行表示。
2>.lua中的每一行數據之間可能同字段名存在相同的值。此時就需要將這些相同值的數據信息抽離出來作爲公共表數據。然後針對公共表數據按照每一行存在相同值的數據再次進行抽離,得到進一步的公共基礎表數據。然後使公共表數據中相同數據信息從公共基礎表中獲取,原始每一行數據中相同數據信息從公共表中獲取。這樣處理後,數據文件中進行了大量相同屬性值得去重操作,從而降低數據文件的存儲大小,同時也降低了數據文件運行時內存。
3>.將屬性字段名抽離出來用索引形式表示,從而得到一個屬性字段表。然後將每一行數據中用到的字段名使用索引值來表示。在取值時索引值會從屬性字段表中取出屬性名,進而通過屬性名獲取屬性值。這樣處理後,表的存儲key從string變成int類型,從而降低表的大小,進而降低數據文件的存儲大小,同時也降低了數據文件運行時內存。

16.lua在序列化數據時,通常具有以下特點:
1>.針對數值類型,布爾類型,空值類型,字符串類型,可以使用%q來對數據進行安全形式的序列化操作。其中數值類型會將整形值按照%d輸出,浮點值按照%a保留精度形勢輸出;布爾類型輸出true或者false;空值類型輸出nil;字符串類型會將裏面的特殊字符串以及轉義字符等進行轉義等處理。
2>.保存不帶循環的表時只需要按照表中key-value鍵值對輸出就行。保存帶循環的表時就需要有一個保存表爲key,表名爲value的save表,用來對已經處理過的表直接返回表名進行輸出。代碼如下:

--保存不帶循環的表
function Serialize(o)
	local t = type(o)
	if t == "number" or t == "string" or t == "boolean" or t == "nil" then
		io.write(string.format("%q", o))
	elseif t == "table" then
		io.write("{\n")
		for k, v in pairs(o) do
			io.write("    ", k, " = ")
			Serialize(v)
			io.write(",\n")
		end
		io.write("}\n")
	else
		error("cannot serialize a " .. t)
	end
end

-- 保存帶有循環表或者共享字表的表
function BasicSerialize(o)
	return string.format("%q", o)
end

function Serialize(name, value, saved)
	saved = saved or {}
	io.write(name, " = ")
	local t = type(value)
	if t == "number" or t == "string" then
		io.write(BasicSerialize(value), "\n")
	elseif  t == "table" then
		if saved[value] then
			io.write(saved[value], "\n")	-- 使用之前的名稱
		else
			saved[value] = name	-- 保存名稱供後續使用
			io.write("{}\n")	-- 創建新表
			for k, v in pairs(value) do		-- 保存表字段
				k = BasicSerialize(k)
				local fname = string.format("%s[%s]", name, k)
				Serialize(fname, v, saved)
			end
		end
	else
		error("cannot serialize a " .. t)
	end
end

17.編譯和異常處理時,具有以下特點:
1>.luac是獨立解釋器中提供預編譯lua源碼的命令,在lua api中也可以使用string.dump函數來對參數表示的函數進行預編譯,預編譯後生成的是二進制文件(也稱爲字節碼文件)。由於lua的load,loadfile等接口中支持二進制文件和文本文件的加載方式,所以預編譯生成的二進制文件可以像lua源碼文件一樣被lua虛擬機解釋執行的。
2>.assert函數會對第一個參數進行真假判定,當爲假(false或者nil)時就會斷言失敗並將第二個參數作爲錯誤信息輸出,沒有第二個參數時就默認錯誤信息爲"Assertion failed!";當爲真時就會斷言成功並將所有的參數進行返回。
3>.lua語言將所有的獨立代碼段當作匿名可變長參數函數的函數體。如:“a = a + 1"等價於"function(…) a = a + 1 end”。且這些獨立的代碼段在編譯時不會進行定義,只有在運行時才進行定義。
4>.load函數從字符串或者函數中編譯代碼段。編譯失敗時返回nil和異常信息;編譯成功時返回包含編譯代碼段的函數。load這個函數要慎用,因爲它性能低,有時還會出現莫名奇妙的問題。參數定義如下:
1>>.第一個參數表示需要編譯的代碼段(也就是字符串或者函數)。
2>>.第二個參數表示代碼段名稱,在發生異常時隨異常信息一起返回。
3>>.第三個參數表示代碼段編譯方式(b表示二進制方式,t表示文本方式,bt表示默認的二進制以及文本方式)。
4>>.第四個參數表示代碼段編譯環境(默認是全局環境)。
5>.loadfile函數從文件中編譯代碼段。編譯失敗時返回nil和異常信息;編譯成功時返回包含編譯代碼段的函數。
6>.dofile函數實際上就是執行了一次loadfile函數進行編譯代碼段,然後調用loadfile返回的函數從而達到執行文件的目的。
7>.error函數用來拋出異常對象。其中參數一表示異常對象;參數二表示出錯層級,error函數所在層級爲1,調用error函數的函數層級爲2,以此類推。
8>.pcall函數用來以保護形式調用參數表示的函數。當被調用函數拋出異常(自己主動調用或者系統調用error函數)時,pcall會返回false以及異常對象;相反就會返回true以及被調用函數的所有返回值。
9>.xpcall函數用來以保護形式調用參數一表示的函數。當被調用函數拋出異常(自己主動調用或者系統調用error函數)時,xpcall會返回false以及由第二個參數表示的異常處理函數返回的異常對象; 相反就會返回true以及被調用函數的所有返回值。

18.模塊和包相關特點如下:
1>.lua中的搜索路徑是一組模板,其中每個模板就是一個查找路徑。模板之間使用;分割,並且模板內包含一個?用來被模塊名所替換。常見特性如下:
1>>.package.path是lua文件的搜索路徑。如果沒有定義LUA_PATH_5_3或者LUA_PATH環境變量,搜索路徑值就是默認路徑值,否則就是環境變量中的;;替換成默認路徑後獲取的路徑值。
2>>.package.cpath是c標準庫的搜索路徑。如果沒有定義LUA_CPATH_5_3或者LUA_CPATH環境變量,搜索路徑就是默認路徑值,否則就是環境變量中的;;替換成默認路徑後獲取的路徑值。
3>>.package.searchpath函數會從第二個參數表示的搜索路徑中來查找第一個參數表示的模塊名。查找成功時返回該模塊名的路徑,否則就返回nil和錯誤信息。
2>.package.searchers表中記錄着全部搜索器,且每個搜索器都會返回一個加載函數。可以使用默認的4個搜索器,也可以根據自己的需求定義搜索器並添加到這個表中。其中默認4個搜索器功能分別如下:
1>>.package.searchers[1]存儲的是預加載的搜索器。當搜索指定的模塊名時,該搜索器會從package.preload表中查找該模塊名的存儲結果作爲加載函數返回。
2>>.package.searchers[2]存儲的是lua文件的搜索器。當搜索指定的模塊名時,該搜索器會從package.path查找路徑下查找該模塊名的lua文件是否存在,如果存在就會以loadfile函數編譯lua文件並返回包含該lua代碼段的加載函數,否則返回nil。
3>>.package.searchers[3]存儲的是c標準庫的搜索器。當搜索指定的模塊名時,該搜索器會從package.cpath查找路徑下查找該模塊名的c標準庫是否存在,如果存在就會以package.loadlib函數編譯c標準庫並返回包含"luaopen_模塊名"的加載函數,否則返回nil。
4>>.package.searchers[4]存儲的是子模塊的搜索器。當搜索指定的模塊名時,該搜索器會將模塊名中的.替換成/,然後從package.path查找路徑下查找替換後模塊名的lua文件是否存在,如果存在就會以loadfile函數編譯lua文件並返回包含該lua代碼段的加載函數;否則該搜索器會將模塊名中的.替換成_,然後 從package.cpath查找路徑下查找替換後模塊名以及每個子項模塊名的c標準庫是否存在,如果存在就會以package.loadlib函數編譯c標準庫並返回包含"luaopen_替換後模塊名"的加載函數,否則返回nil。
3>.require函數在加載模塊名時會先從package.loaded表中查看該模塊名是否已經被加載過(就是"package.loaded.模塊名"不等於nil也不等於false),如果已經被加載過就直接返回結果值;否則就會遍歷package.searchers表中所有的搜索器。當所有搜索器都沒有獲取到加載函數時,require就會拋出異常;否則就會得到一個加載函數並結束遍歷搜索器。此時require會以模塊名爲key,加載函數的返回值(加載函數的代碼段中沒有返回值或者返回nil時,如果存在"package.loaded.模塊名"的賦值操作的話,加載函數的返回值就等於該賦值,否則加載函數的返回值就等於true)爲value形式保存在package.loaded表中並返回該value值。
4>.當要進行模塊重命名時,如果是lua文件只需要將模塊名修改成不一樣的模塊名就行;如果是c標準庫文件就必須將模塊名後面使用"-擴展模塊名"來組合使用。

19.迭代器具有以下特性:
1>.迭代器就是由工廠函數生成的包含狀態變量的閉包函數。
2>.泛型for的結構爲“for varlist in explist do block end”,其中varlist表示變量列表,explist表示爲表達式列表。
泛型for的執行流程如下:
1>>.對in後面的表達式列表進行求值,依次獲取迭代器函數,不可變狀態以及控制變量。當表達式列表中具有函數調用時,除了最後一個函數可以返回多個變量值,其他函數都是隻能返回一個變量值。
2>>.將不可變狀態以及控制變量傳遞給迭代器函數來獲取返回值並賦值給變量列表。當變量列表中第一個變量(也就是控制變量)爲nil時就會結束遍歷,否則就會繼續2>>的過程。
泛型for的執行流程轉換成等價代碼實現如下:

-- _f表示迭代函數,_s表示不可變狀態,_var表示控制變量
do
	local _f, _s, _var = explist
	while true do
		local var_1, ... , var_n = _f(_s, _var)
		_var = _var1
		if _var == nil then break end
		block
	end
end

3>.泛型for的迭代器也叫做生成器(生成迭代的元素),而真正的迭代器是對迭代的元素進行函數調用的。兩者都要經過函數調用,但是生成器模式的更加靈活(可以迭代內部指定break,return等)。

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