Lua基礎


局部定義與代碼塊:

  使用local聲明一個局部變量或局部函數,局部對象只在被聲明的那個代碼塊中有效。

  代碼塊:一個控制結構、一個函數體、一個chunk(一個文件或文本串)(Lua把chunk當做函數處理)

  這樣,可以在chunk內部聲明局部函數,該函數僅在chunk內可見,並且詞法定界保證了包內其他函數可以調用此函數。  

    在chunk內部定義多個local function並且相互調用(或定義local遞歸調用函數)時,最好先聲明,再定義。

  應該盡可能的使用局部變量(使用關鍵詞local聲明的變量),有兩個好處:

  1. 避免命名沖突

  2. 訪問局部變量的速度比全局變量更快.

  可以用do..end來顯示的控制local的作用范圍,Lua中的do...end就相當於C++中的{},它定義了一個作用域。

 

多返回值函數:

  第一,當作為表達式調用函數時,有以下幾種情況:

  1. 當調用作為表達式最后一個參數或者僅有一個參數時,根據變量個數函數盡可能多地返回多個值,不足補nil,超出舍去。

  2. 其他情況下,函數調用僅返回第一個值(如果沒有返回值為nil)

  第二,函數調用作為函數參數被調用時,和多值賦值是相同。

  第三,函數調用在表構造函數中初始化時,和多值賦值時相同。

  另外,return f()這種形式,則返回“f()的返回值”:

  可以使用圓括號強制使調用返回一個值。

  unpack:函數多值返回的特殊函數,接受一個數組作為輸入參數,返回數組的所有元素。

  函數可變參數:...表示可變參數,函數體中用arg訪問,同時arg還有一個域n表示參數的個數。

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

調用函數的時候,如果參數列表為空,必須使用()表明是函數調用。

上述規則有一個例外,當函數只有一個參數並且這個參數是字符串或者表構造的時候,()可有可無

Lua使用的函數,既可是Lua編寫的,也可以是其他語言編寫的,對於Lua程序員,用什么語言實現的函數使用起來都一樣。(優勢就在此處)

 

Lua中的函數是帶有詞法定界(lexical scoping)的第一類值(first-class values)。

  第一類值指:在Lua中函數和其他值(數值、字符串)一樣,函數可以被存放在變量中,也可以存放在表中,可以作為函數的參數,還可以作為函數的返回值。

  詞法定界指:嵌套的函數可以訪問他外部函數中的變量。這一特性給Lua提供了強大的編程能力。

  函數是第一類值(first-class values),也就是說函數名字比如print,實際上是一個指向函數的變量,像持有其他類型值的變量一樣。

  高級函數(higher-order function):以其他函數作為參數的函數,其實和普通函數沒啥區別,只是參數類型是一個函數而已。

  將第一類值函數應用在表中是Lua實現面向對象和包機制的關鍵。

  函數的一般化定義應該是這樣的:

  foo = function (x) return 2*x end

  而

  function foo (x) return 2*x end

  只是函數定義的一個特例。

    Lua中的函數也可以沒有名字,也就是匿名函數。

 

閉包:閉包就是一個函數以及它的upvalues;閉包就是將局部變量和一個函數進行打包維護。

  因為Lua函數是帶有詞法界定的第一類值,閉包才得以實現,而且閉包是一個強大的功能。

  upvalues:外部的局部變量(external local variable),也就是被內嵌的函數可以訪問的外部那個函數中的變量。這些外部函數的局部變量和內嵌的函數就形成了一個閉包。

 

迭代器:Lua中迭代器是一個用閉包實現的函數。 

 

尾調用(Tail Calls):當函數的最后一個動作是調用一個函數時,我們稱這種調用為尾調用(明確什么才是尾調用,類似return func(...)這種格式的就是尾調用)。

  尾調用不需要再回到調用者函數中,所以不適用額外的棧,這一點很重要,因為正確的尾調用是可以無限制的,並且不會導致棧溢出。

  尤其是在寫遞歸邏輯時,不要寫成嵌套調用的形式(會不停的壓棧),而建議用尾調用的方式來實現,這樣效率會高很多。

 

Lua編譯與運行:

  Lua是解釋性語言,但Lua會首先把代碼預編譯成中間碼然后再執行。不要以為需要編譯就不是解釋型語言,Lua的編譯器是語言運行時的一部分,所以,執行編譯產生中間碼速度會更快。

  dofile/dostring和loadfile/loadstring的區別

  (1)do*會編譯並執行;load*只編譯代碼生成中間碼並且返回編譯后的chunk作為一個函數,但不執行代碼。

  (2)load*較為靈活,發生錯誤時load*會返回nil和錯誤信息(可以打印出來)。

  (3)如果要運行一個文件多次,load*只需要編譯一次,但可以多次運行,do*每次都需要編譯。

  (4)dostring(str)等價於loadstring(str)()

  Lua把chunk作為匿名函數處理,例如:chunk "a = 1",loadstring返回與其等價的function () a = 1 end

  loadfile和loadstring只是編譯chunk成為自己內部實現的一個匿名函數,但是這個過程沒有定義函數的行為。Lua中的函數定義是發生在運行時的賦值而不是發生在編譯時。也就是說loadstring以后,其中的函數還沒有被定義,而dostring以后函數就定義好並且可以調用了。

  loadstring編譯的時候不關心詞法范圍,也就是說loadstring總是在全局環境中編譯他的串,這一點很重要。

  local i = 0

  f = loadstring("i = i + 1") --使用全局變量i

  g = function () i = i + 1 end --使用局部變量i

  注意:chunks內部可以定義局部變量也可以返回值:

  利用asset獲取更多的錯誤信息是個好習慣。

 

Lua中的錯誤與異常:

  Lua中error的處理:Lua經常作為擴展語言嵌入在別的應用中,所以不能當錯誤發生時簡單的崩潰或者退出。相反,當錯誤發生時Lua結束當前的chunk並返回到應用中。

  當Lua遇到不期望的情況時就會拋出錯誤,你也可以通過調用error函數顯式地拋出錯誤。

  當函數遇到異常有兩個基本的動作:返回錯誤代碼或者拋出錯誤。選擇哪一種方式,沒有固定的規則,不過基本的原則是:對於程序邏輯上能夠避免的異常,以拋出錯誤的方式處理之,否則返回錯誤代碼。

  error函數:顯示的拋出一個錯誤,終止正在執行的函數,並返回錯誤信息

  assert函數:如果表達式出現錯誤,則觸發一個錯誤,返回出錯信息

  assert首先檢查第一個參數,若沒問題,assert不做任何事情;否則,assert以第二個參數作為錯誤信息拋出。第二個參數是可選的。注意,assert會首先處理兩個參數,

后才調用函數,所以下面代碼,無論n是否為數字,字符串連接操作總會執行:

  n = io.read()

  assert(tonumber(n), "invalid input: " .. n .. " is not a number")

  pcall函數:保護模式下調用函數(即發生的錯誤將不會反射給調用者),當調用函數成功能返回true,失敗時將返回false加錯誤信息。pcall不會終止函數的繼續運行。我們通過error拋出異常,然后通過pcall捕獲之。這種機制提供了強大的能力,足以應付Lua中的各種異常和錯誤情況。pcall返回錯誤信息時,已經釋放了保存錯誤發生情況的棧信息。

  if pcall(foo) then  --foo是一個函數,pcall(foo)表示調用此函數

  ...  -- no errors while running `foo'

  else

  ...  -- `foo' raised an error: take appropriate actions

  end

    如果遇到內部錯誤(比如對一個非table的值使用索引下標訪問)Lua將自己產生錯誤信息,否則Lua使用傳遞給error函數的參數作為錯誤信息。

  xpcall函數:xpcall接受兩個參數:調用函數、錯誤處理函數。當錯誤發生時,Lua會在棧釋放以前調用錯誤處理函數,因此可以使用debug庫收集錯誤相關信息。

  debug.debug函數:debug處理函數,可以自己動手察看錯誤發生時的情況。

  debug.trackback函數:debug處理函數,過traceback創建更多的錯誤信息,也是控制台解釋器用來構建錯誤信息的函數。

 

 動態鏈接庫:

  通常Lua不包含任何不能用標准C實現的機制,動態鏈接庫是一個特例(動態鏈接庫不是ANSI C的一部分,也就是說在標准C中試線動態鏈接是很困難的)。

  我們可以將動態連接庫機制視為其他機制之母:一旦我們擁有了動態連接機制,我們就可以動態的加載Lua中不存在的機制。

  loadlib函數提供了Lua中動態鏈接庫的功能。

  local path = "/usr/local/lua/lib/libluasocket.so"

  local f = assert(loadlib(path, "luaopen_socket"))

 

數據結構:

  table是Lua中唯一的數據結構,可以通過table來實現其他常用的數據結構。

  數組:table下標訪問就是數組。習慣上,Lua下標從1開始,Lua的標准庫都遵循此慣例,因此自定義的數組下標也最好從1 開始,這樣才能使用標准庫函數。

  矩陣和多維數組:table天生具有稀疏的特性,在表示矩陣時可以節省很多空間,因為那些值為nil的元素不需要存儲。

  鏈表:lua中很少會使用這種結構,因為用其他的結構都可以替代。鏈表的簡單實現如下所示:

--根節點
list = nil
--插入一個值v
list = {next = list, value = v}
--遍歷
local l = list
while l do
    print(l.value)
    l = l.next
end

  隊列和雙向隊列:雖然可以使用Lua的table庫提供的insert和remove操作來實現隊列,但這種方式實現的隊列針對大數據量時效率太低,有效的方式是使用兩個索引下標,一個表示第一個元素,另一個表示最后一個元素。

List = {}
List.new = function ()
    return {first = 0, last = -1}
end

List.pushleft = function (list, value)
    local first = list.first - 1
    last.first = first
    list[first] = value
end

List.pushright = function (list, value)
    local last = list.last + 1
    list.last = last
    list[last] = value
end

List.popleft = function (list)
    local first = list.first
    if first > list.last then error("list is empty") end
    local value = list[first]
    list[first] = nil
    list.first = first + 1
    return value
end

List.popright = function (list)
    local last = list.last
    if first > last then error("list is empty") end
    local value = list[last]
    list[last] = nil
    list.last = last - 1
    return value
end

  集合和包:Lua中表示集合有一個簡單有效的方法,將所有集合中的元素作為下標存放在一個table里,對於給定的元素,測試該表的對應下標的元素值是否為nil,即可知道該元素是否存在。

  table.concat可以將一個列表的所有串合並。

  字符串合並操作,會產生賦值字符串操作,用的時候需要注意。比如str1..str2,會道指創建一個新的字符串,並將str1 和str2都拷貝過去,如果字符串很大,是比較費的操作。

 

 Packages:

  Lua並沒有提供明確的機制來實現packages。然而,我們通過語言提供的基本的機制很容易實現他。主要的思想是:像標准庫一樣,使用表來描述package

  package和真正地命名空間的區別:首先,我們對每一個函數定義都必須顯示的在前面加上包的名稱。第二,同一包內的函數相互調用必須在被調用函數前指定包名。

  在包定義結尾加上一個return語句:package打開的時候返回本身是一個很好的習慣。額外的返回語句並不會花費什么代價,並且提供了另一種操作package的可選方式。

  私有成員:在Lua中一個傳統的方法是將私有部分定義為局部變量來實現。我們野可以將package內的所有函數都聲明為局部的,最后將他們放在最終的表中。但是注意:一個package的私有成員必須限制在一個文件之內,我認為這是一件好事。

  自動加載:只在需要的時候加載對應的函數

 

table庫:

  table標准庫提供了一些方便的函數,用來處理比如數組大小、插入/刪除元素、排序等。其中有一些需要注意的點:

  (1)一個常見的錯誤是企圖對表的下標域進行排序。在一個表中,所有下標組成一個集合,但是無序的。如果你想對他們排序,必須將他們復制到一個array然后對這個array排序。

  (2)對於Lua來說,數組是無序的。但是我們知道怎樣去計數,因此只要我們使用排序好的下標訪問數組就可以得到排好序的函數名。這就是為什么我們一直使用ipairs而不是pairs遍歷數組的原因。前者使用key的順序1、2、……,后者表的自然存儲順序。

  (3)table.sort這個函數只有table是數組的時候才適用。

  (4)table的instert和remove函數,如果不帶位置參數,都表示操作array的最后一個元素(這樣不會移動元素)。

  (5)setn函數已過時,不要在lua的table中使用nil值,如果一個元素要刪除,直接remove,不要用nil去代替。

 

string庫:

  Lua中的字符串是恆定不變的。String.sub函數以及Lua中其他的字符串操作函數都不會改變字符串的值,而是返回一個新的字符串。

  Lua中,字符串的第一個字符索引從1開始。

  string.sub(s,i,j)函數可以使用負索引,負索引從字符串的結尾向前計數:-1指向最后一個字符,-2指向倒數第二個,以此類推。第3個參數,默認為-1。

  string.len(s)返回字符串s的長度;

  string.rep(s, n)返回重復n次字符串s的串;

  string.lower(s)將s中的大寫字母轉換成小寫(string.upper將小寫轉換成大寫);

  string.char函數和string.byte函數用來將字符在字符和數字之間轉換;

  string.format在用來對字符串進行格式化的時候,特別是字符串輸出,是功能強大的工具;

  string.find(字符串查找);

  string.gsub(全局字符串替換);

  string.gfind(全局字符串查找)。

  特殊字符:在模式匹配中有一些特殊字符,他們有特殊的意義,Lua中的特殊字符如下:( ) . % + - * ? [ ^ $

  '%' 用作特殊字符的轉義字符,轉義字符 '%'不僅可以用來轉義特殊字符,還可以用於所有的非字母的字符。當對一個字符有疑問的時候,為安全起見請使用轉義字符轉義他。只有字符串被用作模式串用於函數的時候,'%' 才作為轉義字符。

  []方括號表示匹配字符集

  -中橫線連接符

  ^表示補集

  Lua的字符類依賴於本地環境

  模式修飾符:Lua中的模式修飾符有四個:

  + 匹配前一字符1次或多次 最長匹配

  * 匹配前一字符0次或多次

  - 匹配前一字符0次或多次 最短匹配

  ? 匹配前一字符0次或1次

  Capture是這樣一種機制:可以使用模式串的一部分匹配目標串的一部分。將你想捕獲的模式用圓括號括起來,就指定了一個capture。

 

IO庫:

  I/O操作兩種模式:

  (1)簡單模式:當前輸入文件、當前輸出文件,使用io.input和io.output來設定;

  (2)完全模式:使用文件句柄,它以一種面對對象的形式,將所有的文件操作定義為文件句柄的方法。

  IO庫的所有函數都放在表io中。

  I/O庫將當前輸入文件作為標准輸入(stdin),將當前輸出文件作為標准輸出(stdout)。

  io.read函數:從當前文件讀入串,用參數來設定讀取的模式:

  (1)io.read("*all")函數從當前位置讀取整個輸入文件,注意是從當前位置開始讀入。

  (2)io.read("*line")函數返回當前輸入文件的下一行(不包含最后的換行符)。

  (3)io.read("*number")函數從當前輸入文件中讀取出一個數值。只有在該參數下read函數才返回數值,而不是字符串。可以這樣使用,一次讀取多個數字:

  local n1, n2, n3 = io.read("*number", "*number", "*number")

  (4)io.read(n)讀取num個字符到串,io.read(0)函數的可以用來測試是否到達了文件末尾。如果不是返回一個空串,如果已是文件末尾返回nil。

  由於Lua對長串類型值的有效管理,在Lua中使用過濾器的簡單方法就是讀取整個文件到串中去,處理完之后(例如使用函數gsub),接着寫到輸出中去。在任何情況下,都應該考慮選擇使用io.read函數的 " *.all " 選項讀取整個文件,然后使用gfind函數來分解。

  通常Lua中讀取整個文件要比一行一行的讀取一個文件快的多。

  盡量使用第二種方法,它避免了串聯操作,消耗較少的資源

  io.write(a..b..b)
  io.write(a, b, c)

  write和print的不同之處:

  (1)write不附加任何額外的字符到輸出中去;

  (2)write函數是使用當前輸出文件,而print始終使用標准輸出;

  (3)print函數會自動調用參數的tostring方法,可以顯示出table、function和nil。

 

  將我們的數據文件內容作為Lua代碼寫到Lua程序中去。通過使用table構造器,這些存放在Lua代碼中的數據可以像其他普通的文件一樣看起來引人注目。

  Lua構造器,自描述數據格式,數據描述是Lua的主要應用之一。

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM