使用lua進(jìn)行腳本編程有很多優(yōu)點(diǎn):
1 代碼體積小
2 執(zhí)行速度快
3 安全性較高等
4 但是最大的優(yōu)點(diǎn)是修改后的代碼不需要重新編譯即可生效,而高級(jí)語(yǔ)言的代碼經(jīng)過修改后需要經(jīng)過重新編譯或者解釋后才能生效。
lua主要用于游戲應(yīng)用層的開發(fā)。由于游戲上層的邏輯會(huì)而經(jīng)常發(fā)生變化,比如游戲的任務(wù)系統(tǒng)或者活動(dòng)系統(tǒng)。
我們可以設(shè)想一下,如果每次都要修改我們所用的C#代碼重新編譯,那么玩家根本就無(wú)法進(jìn)行游戲。
lua和python則是兩個(gè)常用的腳本語(yǔ)言,lua相對(duì)于python而言,lua比較輕量級(jí)罷了
1. 腳本在手游中是類于“大腦”的功能,所有游戲相關(guān)的邏輯代碼一般都放在腳本中,而客戶端(前臺(tái))的代碼都則屬于“肢體”,也可以說是“播放器”,
作用只是用戶展示出UI界面的功能;那么腳本的作用那么不僅僅如此,比如地圖數(shù)據(jù)等都可以利用腳本使用;
2. 腳本在手機(jī)網(wǎng)游中的作用尤為重要,比如一款網(wǎng)游“Himi”沒有使用腳本,如果“Himi”1.0版本在發(fā)布后突然發(fā)現(xiàn)客戶端出現(xiàn)一些棘手的bug需要修復(fù),
那么你想修改那么也要等待再次更新客戶端重新提交發(fā)布才可以解決,這樣會(huì)流失一大批用戶,而且游戲每次更新也會(huì)流失掉部分用戶,這是肯定的;
但是如果“Himi”這款網(wǎng)游使用腳本的話,那么解決此類問題很eazy,比如我在“Himi”游戲中的邏輯代碼都放在腳本a.lua 中,那么如果a.lua邏輯中哪里出現(xiàn)了問題,
我們直接可以將修復(fù)后的a.lua腳本更新至服務(wù)器中,因?yàn)橐话隳_本都會(huì)定義version號(hào),比如a.lua有bug的version:1.0,那么我們修復(fù)后的a.lua version改成1.1,
當(dāng)用戶每次啟動(dòng)游戲的時(shí)候,客戶端都會(huì)將腳本的version與服務(wù)器腳本version做對(duì)比,當(dāng)server端腳本version號(hào)比當(dāng)前腳本新,那么自動(dòng)下載并覆蓋當(dāng)前腳本,OK,問題解決;
不僅僅如此,比如游戲中做個(gè)活動(dòng)呀,換個(gè)圖片呀等等都可以即使更新,而不是每次修改前端代碼都要重新發(fā)布新的游戲版本,造成一些損失!
lua代碼都是運(yùn)行時(shí)才編譯的,不運(yùn)行的時(shí)候就如同一張圖片、一段音頻一樣,都是文件;所以更新邏輯只需要更新腳本,不需要再編譯,因而Lua能輕松實(shí)現(xiàn)“熱更新”。
Ulua是一款非常實(shí)用的unity插件,它能讓unity支持Lua語(yǔ)言,而且運(yùn)行效率還不錯(cuò)。
在Lua中,一切都是變量,除了關(guān)鍵字。
lua基礎(chǔ)知識(shí)
1、and or not 邏輯運(yùn)算符
邏輯運(yùn)算符認(rèn)為false 和nil 是假(false),其他為真,0 也是true.
and 和or 的運(yùn)算結(jié)果不是true 和false,而是和它的兩個(gè)操作數(shù)相關(guān)。
Lua中的and和or都使用“短路原則”。
a and b -- 如果a 為false,則返回a,否則返回ba or b -- 如果a 為true,則返回a,否則返回b
例如:print(4 and 5) --> 5print(nil and 13) --> nilprint(false and 13) --> falseprint(4 or 5) --> 4print(false or 5) --> 5--一個(gè)很實(shí)用的技巧:如果x 為false 或者nil 則給x 賦初始值vx = x or v等價(jià)于if not x then --not作用:判斷某個(gè)值是否為true,【if not x : 如果x不為true】 x = vendand的優(yōu)先級(jí)比or高。 --C 語(yǔ)言中的三元運(yùn)算符--a ? b : c--在Lua中可以這樣實(shí)現(xiàn):(a and b) or c --但是這里其實(shí)有個(gè)問題,就是當(dāng)b是false或者nil時(shí)無(wú)論a是什么,最后結(jié)果都會(huì)返回c的值,用這個(gè)(a and or {c})[1]--not 的結(jié)果只返回false 或者trueprint(not nil) --> true --not為否定,nil為否定,兩兩否定為肯定(true)print(not false) --> true --not為否定,false為否定,兩兩否定為肯定(true)print(not 0) --> false --not為否定,0為肯定,一肯一否為否定print(not not nil) --> false --三個(gè)否定為否定
2、注釋
單行注釋中,連續(xù)兩個(gè)減號(hào)"--"表示注釋的開始
多行注釋中,由"--[["表示注釋開始,并且一直延續(xù)到"]]"為止。
3、條件語(yǔ)句
另外、不要在for的循環(huán)體內(nèi)修改變量i的值,否則會(huì)導(dǎo)致不可預(yù)知的結(jié)果。
for的另一種用法,是用來(lái)遍歷迭代器
function abc( arg ) local i = 0 local function cde( ) i = i + 1 return i,arg[i] end return cde endfor k,v in abc{'a','b','c'} do if v==nil then break end print('key = '..k..',value='..v)end --key=1,value=a--key=2,value=b--key=3,value=c
foreach
for k, v in ipairs(a) do --ipairs是Lua自帶的系統(tǒng)函數(shù),返回遍歷數(shù)組的迭代器。k表示是數(shù)組a的key,v表示的是數(shù)組a的value,不能有第三個(gè)變量 print(v) end for k in pairs(t) do --打印table t中的所有key。 print(k) end
見如下示例代碼:
days = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }revDays = {}for k, v in ipairs(days) do revDays[k] = vendfor k in pairs(revDays) do print(k .. " = " .. revDays[k])end----輸出結(jié)果為:--1 = Sunday = 7--2 = Monday = 3--3 = Tuesday = 4--4 = Wednesday = 6--5 = Thursday = 1--6 = Friday= 5--7 = Saturday= 2
4、語(yǔ)句塊
語(yǔ)句塊在C中是用"{"和"}"括起來(lái)的,在Lua中,它是用do 和 end 括起來(lái)的。比如:
do print("Hello") end
可以在 函數(shù) 中和 語(yǔ)句塊 中定局部變量。
5、賦值語(yǔ)句
Lua中的賦值語(yǔ)句和其它編程語(yǔ)言基本相同,唯一的差別是Lua支持“多重賦值”,如:a, b = 10, 2 * x,其等價(jià)于a = 10; b = 2 * x。
然而需要說明的是,Lua在賦值之前需要先計(jì)算等號(hào)右邊的表達(dá)式,在每一個(gè)表達(dá)式都得到結(jié)果之后再進(jìn)行賦值。
因此,我們可以這樣寫變量交互:x,y = y,x。如果等號(hào)右側(cè)的表達(dá)式數(shù)量少于左側(cè)變量的數(shù)量,Lua會(huì)將左側(cè)多出的變量的值置為nil,如果相反,Lua將忽略右側(cè)多出的表達(dá)式。
6、數(shù)值運(yùn)算
和C語(yǔ)言一樣,支持 +, -, *, /。但Lua還多了一個(gè)"^"。這表示指數(shù)乘方運(yùn)算。比如2^3 結(jié)果為8, 2^4結(jié)果為16。
連接兩個(gè)字符串,可以用".."運(yùn)處符。如:"This a " .. "string." -- 等于 "this a string"
7、比較運(yùn)算
所有這些操作符總是返回true或false。
操作符==用于相等性測(cè)試,操作符~=用于不等性測(cè)試。這兩個(gè)操作符可以應(yīng)用于任意兩個(gè)值。如果兩個(gè)值的類型不同,Lua就認(rèn)為他們不等。nil值與其自身相等。
對(duì)于字符串的比較,Lua是按照字符次序比較的。
a="123";print(a=b); --falseprint(c==b); --true
對(duì)于Table,F(xiàn)unction和Userdata類型的數(shù)據(jù),只有 == 和 ~=可以用。相等表示兩個(gè)變量引用的是同一個(gè)數(shù)據(jù)。比如:
a={1,2}b=aprint(a==b, a~=b) --輸出 true, falsea={1,2}b={1,2}print(a==b, a~=b) --輸出 false, true
8、變量類型
> print(type("hello world")) string > print(type(10.4)) number > print(type(print)) function > print(type(true)) boolean > print(type(nil)) nil > print(type(type(X))) string > print(type({1,2,3})) table
9、 變量的定義
在Lua中還有一個(gè)特殊的規(guī)則,即以下劃線(_)開頭,后面緊隨多個(gè)大寫字母(_VERSION),這些變量一般被Lua保留并用于特殊用途,因此我們?cè)诼暶髯兞繒r(shí)需要盡量避免這樣的聲明方式,以免給后期的維護(hù)帶來(lái)不必要的麻煩。
Lua是大小寫敏感的,因此對(duì)于一些Lua保留關(guān)鍵字的使用要特別小心,如and。但是And和AND則不是Lua的保留字。
在Lua中,不管在什么地方使用變量,都不需要聲明,并且所有的這些變量總是全局變量,除非我們?cè)谇懊婕由?local"。這一點(diǎn)要特別注意,因?yàn)槲覀兛赡芟朐诤瘮?shù)里使用局部變量,卻忘了用local來(lái)說明。
和其它編程語(yǔ)言一樣,如果有可能盡量使用局部變量,以免造成全局環(huán)境的變量名污染。同時(shí)由于局部變量的有效期更短,這樣垃圾收集器可以及時(shí)對(duì)其進(jìn)行清理,從而得到更多的可用內(nèi)存。
不同lua文件之間的require的情況:如果a文件require了b文件,那么b文件里的全局變量在a文件里是能訪問到的,而局部變量是訪問不到的!
function fun1 local a=10;endfunction fun2 print("a's value is "..a) -- 報(bào)錯(cuò):attempt to concatenate global 'a' (a nil value)end
(1)Nil
正如前面所說的,沒有使用過的變量的值,都是Nil。有時(shí)候我們也需要將一個(gè)變量清除,這時(shí)候,我們可以直接給變量賦以nil值。
將nil賦予一個(gè)全局變量等同于刪除它。如:
var1=nil -- 請(qǐng)注意 nil 一定要小寫
另外還可以像全局變量一樣,將nil賦予table的某個(gè)元素(key)來(lái)刪除該元素。
(2)Boolean
布爾值通常是用在進(jìn)行條件判斷的時(shí)候。布爾值有兩種:true 和 false。在Lua中,只有false和nil才被計(jì)算為false,而所有任何其它類型的值,都是true。比如0,空串等等,都是true。
0在Lua中的的確確是true。你也可以直接給一個(gè)變量賦以Boolean類型的值,如:
theBoolean = true
(3)Number
Lua中的number用于表示實(shí)數(shù)。Lua中沒有專門的類型表示整數(shù)。
(4)String
字符串,總是一種非常常用的高級(jí)類型。在Lua中,我們可以非常方便的定義很長(zhǎng)很長(zhǎng)的字符串。
字符串在Lua中有幾種方法來(lái)表示,最通用的方法,是用雙引號(hào)或單引號(hào)來(lái)括起一個(gè)字符串的,如:
"That's go!"
或
'Hello world!'
和C語(yǔ)言相同的,它支持一些轉(zhuǎn)義字符,列表如下:
\a bell
\b back space
\f form feed
\n newline
\r carriage return
\t horizontal tab
\v vertical tab
\\ backslash
\" double quote
\' single quote
\[ left square bracket
\] right square bracket
由于這種字符串只能寫在一行中,因此,不可避免的要用到轉(zhuǎn)義字符。加入了轉(zhuǎn)義字符的串,看起來(lái)實(shí)在是不敢恭維,比如:
"one line\nnext line\n\"in quotes\", "in quotes""
一大堆的"\"符號(hào)讓人看起來(lái)很倒胃口。如果你與我有同感,那么,我們?cè)贚ua中,可以用另一種表示方法:用"[["和"]]"將多行的字符串括起來(lái)。(lua5.1: 中括號(hào)中間可以加入若干個(gè)"="號(hào),如 [==[ ... ]==],詳見下面示例)
示例:下面的語(yǔ)句所表示的是完全相同的字符串:
值得注意的是,在這種字符串中,如果含有單獨(dú)使用的"[["或"]]"就仍然得用"\["或"\]"來(lái)避免歧義。當(dāng)然,這種情況是極少會(huì)發(fā)生的。
在Lua中還可以通過[[ all strings ]]的方式來(lái)禁用[[ ]]中轉(zhuǎn)義字符,如:
page = [[abc\n123]] --abc\n123
如果兩個(gè)方括號(hào)中包含這樣的內(nèi)容:a = b[c[i]],這樣將會(huì)導(dǎo)致Lua的誤解析,[[a = b[c[i]]]],lua會(huì)認(rèn)為倒數(shù)第二個(gè)]]為結(jié)束標(biāo)志,因此在這種情況下,我們可以將其改為[===[ 和 ]===]的形式,從而避免了誤解析的發(fā)生。
(5)Table
A、 如果鍵是int類型的,那么鍵只能為這種格式的:[1]或者['1']
B、 如果鍵是string類型的,那么鍵只能為這種格式的:['name']或者name
關(guān)系表類型,這是一個(gè)很強(qiáng)大的類型。我們可以把這個(gè)類型看作是一個(gè)數(shù)組。只是C#語(yǔ)言的數(shù)組,只能用正整數(shù)來(lái)作索引;在Lua中,你可以用任意類型來(lái)作數(shù)組的索引,除了nil。
同樣,在C#語(yǔ)言中,數(shù)組的內(nèi)容只允許一種類型;在Lua中,你也可以用任意類型的值來(lái)作數(shù)組的內(nèi)容,除了nil。
Table的定義很簡(jiǎn)單,它的主要特征是用"{"和"}"來(lái)括起一系列數(shù)據(jù)元素的。比如:
T1 = {} -- 定義一個(gè)空表T1[1]=10 --然后我們就可以象C#語(yǔ)言一樣來(lái)使用它了。
T1["John"]={Age=27, Gender="Male"}--這一句相當(dāng)于:T1["John"]={} -- 必須先定義成一個(gè)表,還記得未定義的變量是nil類型嗎T1["John"]["Age"]=27T1["John"]["Gender"]="Male"--當(dāng)表的索引是字符串的時(shí)候,我們可以簡(jiǎn)寫成:T1.John={}T1.John.Age=27T1.John.Gender="Male"--或T1.John = {Age=27, Gender="Male"}
在定義表的時(shí)候,我們可以把所有的數(shù)據(jù)內(nèi)容一起寫在"{"和"}"之間,這樣子是非常方便,而且很好看。比如,前面的T1的定義,我們可以這么寫:
T1= { 10, -- 相當(dāng)于 [1] = 10 ,如果不寫索引,則索引就會(huì)被認(rèn)為是數(shù)字,并按順序自動(dòng)從1往后編 [100] = 40, --相當(dāng)于指定索引為100對(duì)應(yīng)的值為40 John= -- 如果你原意,你還可以寫成:["John"] = { Age=27, -- 如果你原意,你還可以寫成:["Age"] =27 Gender=Male -- 如果你原意,你還可以寫成:["Gender"] =Male }, 20 -- 相當(dāng)于 [2] = 20 }
看起來(lái)很漂亮,不是嗎?我們?cè)趯懙臅r(shí)候,需要注意三點(diǎn):
第一,所有元素之間,總是用逗號(hào)","隔開;
第二,所有索引值都需要用["和"]括起來(lái);如果是字符串,還可以去掉引號(hào)和中括號(hào);
第三,如果不寫索引,則索引就會(huì)被認(rèn)為是數(shù)字,并按順序自動(dòng)從1往后編
表類型的構(gòu)造是如此的方便,以致于常常被人用來(lái)代替配置文件。是的,不用懷疑,它比ini文件要漂亮,并且強(qiáng)大的多。
對(duì)于table的構(gòu)造器,還有兩個(gè)需要了解的語(yǔ)法規(guī)則,如:
a = { [1] = "red", [2] = "green", [3] = "blue", }
這里需要注意最后一個(gè)元素的后面仍然可以保留逗號(hào)(,)
table可視為數(shù)組,數(shù)組下標(biāo)不同于其他大多數(shù)語(yǔ)言,其下標(biāo)從1
開始。
(6)Function
函數(shù),在Lua中,函數(shù)的定義也很簡(jiǎn)單。典型的定義如下:
function add(a,b) -- add 是函數(shù)名字,a和b是參數(shù)名字return a+b -- return 用來(lái)返回函數(shù)的運(yùn)行結(jié)果end
還記得前面說過,函數(shù)也是變量類型嗎?上面的函數(shù)定義,其實(shí)相當(dāng)于:
add = function (a,b) return a+b end
當(dāng)重新給add賦值時(shí),它就不再表示這個(gè)函數(shù)了。我們甚至可以賦給add任意數(shù)據(jù),包括nil (這樣,賦值為nil,將會(huì)把該變量清除)。
Lua的函數(shù)可以接受可變參數(shù)個(gè)數(shù),它同樣是用"..."來(lái)定義的,比如:
function sum (a,b,...)
如果想取得...所代表的參數(shù),可以在函數(shù)中訪問arg局部變量(表類型)得到 (lua5.1: 取消arg,并直接用"..."來(lái)代表可變參數(shù)了,本質(zhì)還是arg)。
如 sum(1,2,3,4),則在函數(shù)中,a = 1, b = 2, arg = {3, 4} (lua5.1: a = 1, b = 2, ... = {3, 4})
在含有變長(zhǎng)參數(shù)的函數(shù)中,同樣可以帶有固定參數(shù),但是固定參數(shù)一定要在變長(zhǎng)參數(shù)之前聲明,如:
function test(arg1,arg2,...) ...end
關(guān)于Lua的變長(zhǎng)參數(shù)最后需要說明的是,由于變長(zhǎng)參數(shù)中可能包含nil值,因此在使用類似獲取table元素?cái)?shù)量(#)的方式獲取變參的數(shù)量就會(huì)出現(xiàn)問題。如果要想始終獲得正確的參數(shù)數(shù)量,可以使用Lua提供的select函數(shù),如:
function Start() MyTest(2,4,6,a,b);endfunction MyTest(...)print(select('#',...)) -- 5 , 這里'#'值表示讓select返回變參的數(shù)量(其中包括nil)。for i = 1, select('#',...) do local arg = select(i, ...) --這里的i表示獲取第i個(gè)變參,1為第一個(gè)。 print(arg);endend--輸出結(jié)果--5 =--2--4--6--nil--nil
更可貴的是,它可以同時(shí)返回多個(gè)結(jié)果,比如:
function s()return 1,2,3,4enda,b,c,d = s() -- 此時(shí),a = 1, b = 2, c = 3, d = 4
function maximum(a) local mi = 1 local m = a[mi] for i, val in ipairs(a) do if val > m then mi,m = i,val end end return m,miendprint(maximum{8,10,23,12,5})
-- 輸出結(jié)果為:-- 23
-- 233
前面說過,表類型可以擁有任意類型的值,包括函數(shù)!因此,有一個(gè)很強(qiáng)大的特性是,擁有函數(shù)的表,哦,我想更恰當(dāng)?shù)膽?yīng)該說是對(duì)象吧。Lua可以使用面向?qū)ο缶幊塘?。不信?舉例如下:
t = { Age = 27, add = function(self, n) self.Age = self.Age+n end } print(t.Age) -- 27 t.add(t, 10) print(t.Age) -- 37
不過,t.add(t,10) 這一句實(shí)在是有點(diǎn)土對(duì)吧?沒關(guān)系,在Lua中,我們可以簡(jiǎn)寫成:
t:add(10) -- 相當(dāng)于 t.add(t,10)
1、在Lua中函數(shù)的調(diào)用方式和C語(yǔ)言基本相同,如:print("Hello World")和a = add(x, y)。唯一的差別是,如果函數(shù)只有一個(gè)參數(shù),并且該參數(shù)的類型為字符串常量或table的構(gòu)造器,那么圓括號(hào)可以省略,如print "Hello World"和f {x = 20, y = 20}。
2、Lua為面對(duì)對(duì)象式的調(diào)用也提供了一種特殊的語(yǔ)法--冒號(hào)操作符。表達(dá)式o.foo(o,x)的另一種寫法是o:foo(x)。冒號(hào)操作符使調(diào)用o.foo時(shí)將o隱含的作為函數(shù)的第一個(gè)參數(shù)。
3、需要說明的是,Lua中實(shí)參和形參的數(shù)量可以不一致,一旦出現(xiàn)這種情況,Lua的處理規(guī)則等同于多重賦值,即實(shí)參多于形參,多出的部分被忽略,如果相反,沒有被初始化的形參的缺省值為nil。
4、 Lua會(huì)調(diào)整一個(gè)函數(shù)的返回值數(shù)量以適應(yīng)不同的調(diào)用情況。若將函數(shù)調(diào)用作為一條單獨(dú)語(yǔ)句時(shí),Lua會(huì)丟棄函數(shù)的所有返回值。若將函數(shù)作為表達(dá)式的一部分來(lái)調(diào)用時(shí),Lua只保留函數(shù)的第一個(gè)返回值。
只有當(dāng)一個(gè)函數(shù)調(diào)用是一系列表達(dá)式中的最后一個(gè)元素時(shí),才能獲得所有返回值。這里先給出三個(gè)樣例函數(shù),如:
function foo0() endfunction foo1() return "a" endfunction foo2() return "a","b" end
最后一個(gè)需要介紹的是Lua中unpack函數(shù),該函數(shù)將接收數(shù)組作為參數(shù),并從下標(biāo)1開始返回該數(shù)組(之所以不稱之為table,因?yàn)檎f成數(shù)組,更好理解,數(shù)組的特性就是從1開始,并以1為增量排序的集合)的所有元素。如:
> lua> print(unpack{10,20,30}) 10 20 30> a,b = unpack{10,20,30}> print(a,b) 10 20> string.find(unpack{"hello","ll"}) --等同于string.find("hello","ll"),find函數(shù)的作用是尋找第二個(gè)參數(shù)ll在第一個(gè)參數(shù)中的索引位置
在Lua中unpack函數(shù)是用C語(yǔ)言實(shí)現(xiàn)的。為了便于理解,下面給出在Lua中通過遞歸實(shí)現(xiàn)一樣的效果,如:
function unpack(t,i) i = i or 1 if t[i] then return t[i], unpack(t,i + 1) endend
具名實(shí)參:
在函數(shù)調(diào)用時(shí),Lua的傳參規(guī)則和C語(yǔ)言相同,并不真正支持具名實(shí)參。但是我們可以通過table來(lái)模擬,比如:
function rename1(old,new) ...end
這里我們可以讓上面的rename函數(shù)只接收一個(gè)參數(shù),即table類型的參數(shù),與此同時(shí),該table對(duì)象將含有old和new兩個(gè)key。如:
function rename2(arg) local old = arg.old local new = arg.new ...end
這種修改方式有些類似于JavaBean,即將多個(gè)參數(shù)合并為一個(gè)JavaBean。
在Lua中函數(shù)和所有其它值一樣都是匿名的,即它們都沒有名稱。在使用時(shí)都是操作持有該函數(shù)的變量,如:
a = { p = print }
a.p("Hello World")
b = print
b("Hello World")
在聲明Lua函數(shù)時(shí),可以直接給出所謂的函數(shù)名,如:
function foo(x) return 2 * x end
我們同樣可以使用下面這種更為簡(jiǎn)化的方式聲明Lua中的函數(shù),如:
foo = function(x) return 2 * x end
我們將這種函數(shù)構(gòu)造式的結(jié)果稱為一個(gè)"匿名函數(shù)"。下面的示例顯示了匿名函數(shù)的方便性,它的使用方式有些類似于Java中的匿名類,如:
table.sort(test_table,function(a,b) return (a.name > b.name) end)
test_table={{age=5},{age=11},{age=1},{age=3},{age=9}}table.sort(test_table,function(a,b) return (a.age > b.age) end)for i=1,5 do print(test_table[i].age)endend--輸出結(jié)果--11--9--5--3--1
1. closure(閉合函數(shù)):
若將一個(gè)函數(shù)寫在另一個(gè)函數(shù)之內(nèi),那么這個(gè)位于內(nèi)部的函數(shù)便可以訪問外部函數(shù)中的局部變量,見如下示例:
function newCounter() local i = 0 return function() --匿名函數(shù) i = i + 1 return i endendc1 = newCounter()print("The return value of first call is " .. c1())print("The return value of second call is " .. c1())--輸出結(jié)果為:--The return value of first call is 1--The return value of second call is 2
在上面的示例中,我們將newCounter()函數(shù)稱為閉包函數(shù)。其函數(shù)體內(nèi)的局部變量i被稱為"非局部變量",和普通局部變量不同的是該變量被newCounter函數(shù)體內(nèi)的匿名函數(shù)訪問并操作。
再有就是在函數(shù)newCounter返回后,其值仍然被保留并可用于下一次計(jì)算。再看一下下面的調(diào)用方式。
function newCounter() local i = 0 return function() --匿名函數(shù) i = i + 1 return i endendc1 = newCounter()c2 = newCounter()print("The return value of first call with c1 is " .. c1())print("The return value of first call with c2 is " .. c2())print("The return value of second call with c1 is " .. c1())--輸出結(jié)果為:--The return value of first call with c1 is 1--The return value of first call with c2 is 1--The return value of second call with c1 is 2
由此可以推出,Lua每次在給新的閉包變量賦值時(shí),都會(huì)讓不同的閉包變量擁有獨(dú)立的"非局部變量"。下面的示例將給出基于閉包的更為通用性的用法:
do --這里將原有的文件打開函數(shù)賦值給"私有變量"oldOpen,該變量在塊外無(wú)法訪問。 local oldOpen = io.open --新增一個(gè)匿名函數(shù),用于判斷本次文件打開操作的合法性。 local access_OK = function(filename,mode) <檢查訪問權(quán)限> end --將原有的io.open函數(shù)變量指向新的函數(shù),同時(shí)在新函數(shù)中調(diào)用老函數(shù)以完成真正的打開操作。 io.open = function(filename,mode) if access_OK(filename,mode) then return oldOpen(filename,mode) else return nil,"Access denied" end endend
非全局函數(shù):
從上一小節(jié)中可以看出,Lua中的函數(shù)不僅可以直接賦值給全局變量,同時(shí)也可以賦值給其他類型的變量,如局部變量和table中的字段等。
事實(shí)上,Lua庫(kù)中大多數(shù)table都帶有函數(shù),如io.read、math.sin等。這種寫法有些類似于C++中的結(jié)構(gòu)體。如:
Lib = {}
Lib.add = function(x,y) return x + y end
Lib.sub = function(x,y) return x - y end
或者是在table的構(gòu)造式中直接初始化,如:
Lib = { add = function(x,y) return x + y end,
sub = function(x,y) return x - y end
}
除此之外,Lua還提供另外一種語(yǔ)法來(lái)定義此類函數(shù),如:
Lib = {}
function Lib.add(x,y) return x + y end
function Lib.sub(x,y) return x - y end
對(duì)于Lua中的局部函數(shù),其語(yǔ)義在理解上也是非常簡(jiǎn)單的。由于Lua中都是以程序塊作為執(zhí)行單元,因此程序塊內(nèi)的局部函數(shù)在程序塊外是無(wú)法訪問的,如:
do local f = function(x,y) return x + y end --do something with f. f(4,5)end
對(duì)于這種局部函數(shù),Lua還提供另外一種更為簡(jiǎn)潔的定義方式,如:
local function f(x,y) return x + y end
該寫法等價(jià)于:
local f
f = function(x,y) return x + y end
正確的尾調(diào)用:
在Lua中支持這樣一種函數(shù)調(diào)用的優(yōu)化,即“尾調(diào)用消除”。我們可以將這種函數(shù)調(diào)用方式視為goto語(yǔ)句,如:
function f(x) return g(x) end
由于g(x)函數(shù)是f(x)函數(shù)的最后一條語(yǔ)句,在函數(shù)g返回之后,f()函數(shù)將沒有任何指令需要被執(zhí)行,因此在函數(shù)g()返回時(shí),可以直接返回到f()函數(shù)的調(diào)用點(diǎn)。
由此可見,Lua解釋器一旦發(fā)現(xiàn)g()函數(shù)是f()函數(shù)的尾調(diào)用,那么在調(diào)用g()時(shí)將不會(huì)產(chǎn)生因函數(shù)調(diào)用而引起的棧開銷。這里需要強(qiáng)調(diào)的是,尾調(diào)用函數(shù)一定是其調(diào)用函數(shù)的最后一條語(yǔ)句,否則Lua不會(huì)進(jìn)行優(yōu)化。
然而事實(shí)上,我們?cè)诤芏嗫此剖俏舱{(diào)用的場(chǎng)景中,實(shí)際上并不是真正的尾調(diào)用,如:
function f(x) g(x) end --沒有return語(yǔ)句的明確提示
function f(x) return g(x) + 1 --在g()函數(shù)返回之后仍需執(zhí)行一次加一的指令。
function f(x) return x or g(x) --如果g()函數(shù)返回多個(gè)值,該操作會(huì)強(qiáng)制要求g()函數(shù)只返回一個(gè)值。
function f(x) return (g(x)) --原因同上。
在Lua中,只有"return <func>(<args>)"形式才是標(biāo)準(zhǔn)的尾調(diào)用,至于參數(shù)中(args)是否包含表達(dá)式,由于表達(dá)式的執(zhí)行是在函數(shù)調(diào)用之前完成的,因此不會(huì)影響該函數(shù)成為尾調(diào)用函數(shù)。
(7)Userdata 和 Thread
10、字符串
(1)"#"標(biāo)識(shí)符,該標(biāo)識(shí)符在字符串變量的前面將返回其后字符串的長(zhǎng)度
a = "hello"print(#a) --5
(2)Lua提供了運(yùn)行時(shí)的數(shù)字與字符串的自動(dòng)轉(zhuǎn)換。如:
> print("10" + 1)
11
> print("10" + "1")
11
> print(10 + 1)
11
> print("10 + 1")
10 + 1
如果在實(shí)際編程中,不希望兩個(gè)數(shù)字字符串被自動(dòng)轉(zhuǎn)換,而是實(shí)現(xiàn)字符串之間的連接,可以通過" .. "操作符來(lái)完成。如:
> print(10 .. 20)
1020
注意..和兩邊的數(shù)字之間必須留有空格,否則就會(huì)被Lua誤解析為小數(shù)點(diǎn)兒。
盡管Lua提供了這種自動(dòng)轉(zhuǎn)換的功能,為了避免一些不可預(yù)測(cè)的行為發(fā)生,特別是因?yàn)長(zhǎng)ua版本升級(jí)而導(dǎo)致的行為不一致現(xiàn)象。
鑒于此,還是應(yīng)該盡可能使用顯示的轉(zhuǎn)換,如字符串轉(zhuǎn)數(shù)字的函數(shù)tonumber(),或者是數(shù)字轉(zhuǎn)字符串的函數(shù)tostring()。對(duì)于前者,如果函數(shù)參數(shù)不能轉(zhuǎn)換為數(shù)字,該函數(shù)返回nil。如:
line = io.read()n = tonumber(line)if n == nil then error(line .. " is not a valid number")else print(n * 2)end
(3)由于數(shù)組實(shí)際上仍為一個(gè)table,所以對(duì)于數(shù)組大小的計(jì)算需要留意某些特殊的場(chǎng)景,如:
a = {}a[1000] = 1
在上面的示例中,數(shù)組a中索引值為1--999的元素的值均為nil。而Lua則將nil作為界定數(shù)據(jù)結(jié)尾的標(biāo)志。當(dāng)一個(gè)數(shù)組含有“空隙”時(shí),即中間含有nil值,長(zhǎng)度操作符#會(huì)認(rèn)為這些nil元素就是結(jié)尾標(biāo)志。
當(dāng)然這肯定不是我們想要的結(jié)果。因此對(duì)于這些含有“空隙”的數(shù)組,我們可以通過函數(shù)table.maxn()返回table的最大正數(shù)索引值。如:
table.maxn()是返回table的最大正數(shù)索引值
一般來(lái)說#是獲得一個(gè)table的長(zhǎng)度(即元素?cái)?shù)),但這個(gè)操作符實(shí)際上陷阱很多,#僅對(duì)鍵值為連續(xù)的數(shù)值才有效,并且鍵值必須從1開始!
如果你的table是純粹當(dāng)一個(gè)連續(xù)的數(shù)組在用,那么#t是很方便的獲得table長(zhǎng)度的方法;但如果你的table中key的數(shù)值不連續(xù),或者有其他類型的key那么還是不要指望#能給出多有意義的結(jié)果來(lái)……
連續(xù)的數(shù)組指的就是往一個(gè)空的table中插入數(shù)據(jù),由此table自動(dòng)分配鍵值,由1開始,遞增量為1
類似這樣:t = {"a", "b", "c"},只有這種情況用#運(yùn)算符才最保險(xiǎn)!不過#運(yùn)算符主要用于計(jì)算字符串的長(zhǎng)度
table遍歷:
for key, value in pairs(tbtest) do XXX end
這樣的遍歷順序并非是tbtest中table的排列順序,而是根據(jù)tbtest中key的hash值排列的順序來(lái)遍歷的。
如果不需要保證遍歷的順序,就用pairs
pairs也可以用來(lái)遍歷連續(xù)的數(shù)組(之所以不稱之為table,因?yàn)檎f成數(shù)組,更好理解,數(shù)組的特性就是從1開始,并以1為增量排序的集合),會(huì)按順序輸出
也可以遍歷不是以1開始,也不是按1遞增的連續(xù)的key的table,也會(huì)按順序輸出的,例如:
a = {[4] = "red1",[3] = "red2",[5] = "red3",[1] = "red4"}for k,v in pairs(a) do print(v) -- red4 red2 red1 red3end
for key, value in ipairs(tbtest) do XXX end
這樣的循環(huán)必須要求tbtest中的key為順序的,而且必須是從1開始,ipairs只會(huì)從1開始按連續(xù)的key順序遍歷到key不連續(xù)為止。
如果需要保證遍歷的順序,就用ipairs,前提是table是一個(gè)連續(xù)的數(shù)組
for i=1, #(tbtest) do XXX end
這種遍歷,只能遍歷連續(xù)的數(shù)組(鍵從1開始,以增量為1遞增的鍵的table),不然遍歷會(huì)出現(xiàn)意想不到的結(jié)果!
for i=1, table.maxn(tbtest) do XXX end
這種效率太低了,不建議用于遍歷table
參考:http://www.cnblogs.com/ly4cn/archive/2006/08/04/467550.html
http://www.cnblogs.com/stephen-liu74/archive/2012/06/15/2409324.html
聯(lián)系客服